1# OneLogin's SAML Python Toolkit (compatible with Python3)
2
3[![Build Status](https://api.travis-ci.org/onelogin/python3-saml.png?branch=master)](http://travis-ci.org/onelogin/python3-saml)
4[![Coverage Status](https://coveralls.io/repos/github/onelogin/python3-saml/badge.svg?branch=master)](https://coveralls.io/github/onelogin/python3-saml?branch=master)
5[![PyPi Version](https://img.shields.io/pypi/v/python3-saml.svg)](https://pypi.python.org/pypi/python3-saml)
6![Python versions](https://img.shields.io/pypi/pyversions/python3-saml.svg)
7
8
9Add SAML support to your Python software using this library.
10Forget those complicated libraries and use the open source library provided
11and supported by OneLogin Inc.
12
13This version supports Python3. There is a separate version that only support Python2: [python-saml](https://github.com/onelogin/python-saml)
14
15#### Warning ####
16
17Version 1.8.0 sets strict mode active by default
18
19Update ``python3-saml`` to ``1.5.0``, this version includes security improvements for preventing XEE and Xpath Injections.
20
21Update ``python3-saml`` to ``1.4.0``, this version includes a fix for the [CVE-2017-11427](https://www.cvedetails.com/cve/CVE-2017-11427/) vulnerability.
22
23This version also changes how the calculate fingerprint method works, and will expect as input a formatted X.509 certificate.
24
25Update ``python3-saml`` to ``1.2.6`` that adds the use defusedxml that will prevent XEE and other attacks based on the abuse of XML. (CVE-2017-9672)
26
27Update ``python3-saml`` to ``>= 1.2.1``, ``1.2.0`` had a bug on signature validation process (when using ``wantAssertionsSigned`` and ``wantMessagesSigned``). [CVE-2016-1000251](https://github.com/distributedweaknessfiling/DWF-Database-Artifacts/blob/master/DWF/2016/1000251/CVE-2016-1000251.json)
28
29``1.2.0`` version includes a security patch that contains extra validations that will prevent signature wrapping attacks.
30
31``python3-saml < v1.2.0`` is vulnerable and allows signature wrapping!
32
33#### Security Guidelines ####
34
35If you believe you have discovered a security vulnerability in this toolkit, please report it at https://www.onelogin.com/security with a description. We follow responsible disclosure guidelines, and will work with you to quickly find a resolution.
36
37Why add SAML support to my software?
38------------------------------------
39
40SAML is an XML-based standard for web browser single sign-on and is defined by
41the OASIS Security Services Technical Committee. The standard has been around
42since 2002, but lately it is becoming popular due its advantages:
43
44 * **Usability** - One-click access from portals or intranets, deep linking,
45   password elimination and automatically renewing sessions make life
46   easier for the user.
47 * **Security** - Based on strong digital signatures for authentication and
48   integrity, SAML is a secure single sign-on protocol that the largest
49   and most security conscious enterprises in the world rely on.
50 * **Speed** - SAML is fast. One browser redirect is all it takes to securely
51   sign a user into an application.
52 * **Phishing Prevention** - If you don’t have a password for an app, you
53   can’t be tricked into entering it on a fake login page.
54 * **IT Friendly** - SAML simplifies life for IT because it centralizes
55   authentication, provides greater visibility and makes directory
56   integration easier.
57 * **Opportunity** - B2B cloud vendor should support SAML to facilitate the
58   integration of their product.
59
60General Description
61-------------------
62
63OneLogin's SAML Python toolkit lets you turn your Python application into a SP
64(Service Provider) that can be connected to an IdP (Identity Provider).
65
66**Supports:**
67
68 * SSO and SLO (SP-Initiated and IdP-Initiated).
69 * Assertion and nameId encryption.
70 * Assertion signatures.
71 * Message signatures: ``AuthNRequest``, ``LogoutRequest``, ``LogoutResponses``.
72 * Enable an Assertion Consumer Service endpoint.
73 * Enable a Single Logout Service endpoint.
74 * Publish the SP metadata (which can be signed).
75
76**Key Features:**
77
78 * **saml2int** - Implements the SAML 2.0 Web Browser SSO Profile.
79 * **Session-less** - Forget those common conflicts between the SP and
80   the final app, the toolkit delegate session in the final app.
81 * **Easy to use** - Programmer will be allowed to code high-level and
82   low-level programming, 2 easy to use APIs are available.
83 * **Tested** - Thoroughly tested.
84 * **Popular** - OneLogin's customers use it. Add easy support to your Django/Flask web projects.
85
86Installation
87------------
88
89### Dependencies ###
90
91 * python 2.7 // python 3.6
92 * [xmlsec](https://pypi.python.org/pypi/xmlsec) Python bindings for the XML Security Library.
93 * [isodate](https://pypi.python.org/pypi/isodate) An ISO 8601 date/time/
94 duration parser and formatter
95
96Review the ``setup.py`` file to know the version of the library that ``python3-saml`` is using
97
98### Code ###
99
100#### Option 1. Download from GitHub ####
101
102The toolkit is hosted on GitHub. You can download it from:
103
104 * Latest release: https://github.com/onelogin/python3-saml/releases/latest
105 * Master repo: https://github.com/onelogin/python3-saml/tree/master
106
107Copy the core of the library ``(src/onelogin/saml2 folder)`` and merge the ``setup.py`` inside the Python application. (Each application has its structure so take your time to locate the Python SAML toolkit in the best place).
108
109#### Option 2. Download from pypi ####
110
111The toolkit is hosted in pypi, you can find the ``python3-saml`` package at https://pypi.python.org/pypi/python3-saml
112
113You can install it executing:
114```
115$ pip install python3-saml
116```
117
118If you want to know how a project can handle python packages review this [guide](https://packaging.python.org/en/latest/tutorial.html) and review this [sampleproject](https://github.com/pypa/sampleproject)
119
120Security Warning
121----------------
122
123In production, the **strict** parameter MUST be set as **"true"**. Otherwise
124your environment is not secure and will be exposed to attacks.
125
126In production also we highly recommend to register on the settings the IdP certificate instead of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism, we maintain it for compatibility and also to be used on test environment.
127
128Getting Started
129---------------
130
131### Knowing the toolkit ###
132
133The new OneLogin SAML Toolkit contains different folders (``certs``, ``lib``, ``demo-django``, ``demo-flask`` and ``tests``) and some files.
134
135Let's start describing them:
136
137#### src ####
138
139This folder contains the heart of the toolkit, **onelogin/saml2** folder contains the new version of
140the classes and methods that are described in a later section.
141
142#### demo-django ####
143
144This folder contains a Django project that will be used as demo to show how to add SAML support to the Django Framework. **demo** is the main folder of the Django project (with its ``settings.py``, ``views.py``, ``urls.py``), **templates** is the Django templates of the project and **saml** is a folder that contains the ``certs`` folder that could be used to store the X.509 public and private key, and the SAML toolkit settings (``settings.json`` and ``advanced_settings.json``).
145
146***Notice about certs***
147
148SAML requires a X.509 cert to sign and encrypt elements like ``NameID``, ``Message``, ``Assertion``, ``Metadata``.
149
150If our environment requires sign or encrypt support, the certs folder may contain the X.509 cert and the private key that the SP will use:
151
152* sp.crt The public cert of the SP
153* sp.key The private key of the SP
154
155Or also we can provide those data in the setting file at the ``x509cert`` and the ``privateKey`` JSON parameters of the ``sp`` element.
156
157Sometimes we could need a signature on the metadata published by the SP, in this case we could use the X.509 cert previously mentioned or use a new X.509 cert: ``metadata.crt`` and ``metadata.key``.
158
159Use ``sp_new.crt`` if you are in a key rollover process and you want to
160publish that X.509 certificate on Service Provider metadata.
161
162If you want to create self-signed certs, you can do it at the https://www.samltool.com/self_signed_certs.php service, or using the command:
163
164```bash
165openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout sp.key
166```
167
168#### demo-flask ####
169
170This folder contains a Flask project that will be used as demo to show how to add SAML support to the Flask Framework. ``index.py`` is the main Flask file that has all the code, this file uses the templates stored at the ``templates`` folder. In the ``saml`` folder we found the ``certs`` folder to store the X.509 public and private key, and the SAML toolkit settings (``settings.json`` and ``advanced_settings.json``).
171
172#### demo_pyramid ####
173
174This folder contains a Pyramid project that will be used as demo to show how to add SAML support to the [Pyramid Web Framework](http://docs.pylonsproject.org/projects/pyramid/en/latest/).  ``\_\_init__.py`` is the main file that configures the app and its routes, ``views.py`` is where all the logic and SAML handling takes place, and the templates are stored in the ``templates`` folder. The ``saml`` folder is the same as in the other two demos.
175
176#### demo-tornado ####
177
178This folder contains a Tornado project that will be used as demo to show how to add SAML support to the Tornado Framework. ``views.py`` (with its ``settings.py``) is the main Flask file that has all the code, this file uses the templates stored at the ``templates`` folder. In the ``saml`` folder we found the ``certs`` folder to store the X.509 public and private key, and the SAML toolkit settings (``settings.json`` and ``advanced_settings.json``).
179
180It requires python3.5 (it's using tornado 6.0.3)
181
182#### setup.py ####
183
184Setup script is the centre of all activity in building, distributing, and installing modules.
185Read more at https://pythonhosted.org/an_example_pypi_project/setuptools.html
186
187#### tests ####
188
189Contains the unit test of the toolkit.
190
191In order to execute the test you only need to load the virtualenv with the toolkit installed on it and execute:
192```
193python setup.py test
194```
195The previous line will run the tests for the whole toolkit. You can also run the tests for a specific module. To do so for the auth module you would have to execute this:
196```
197python setup.py test --test-suite tests.src.OneLogin.saml2_tests.auth_test.OneLogin_Saml2_Auth_Test
198```
199
200With the ``--test-suite`` parameter you can specify the module to test. You'll find all the module available and their class names at ``tests/src/OneLogin/saml2_tests/``.
201
202### How It Works ###
203
204#### Settings ####
205
206First of all we need to configure the toolkit. The SP's info, the IdP's info, and in some cases, configure advanced security issues like signatures and encryption.
207
208There are two ways to provide the settings information:
209
210* Use a ``settings.json`` file that we should locate in any folder, but indicates its path with the ``custom_base_path`` parameter.
211
212* Use a JSON object with the setting data and provide it directly to the constructor of the class (if your toolkit integation requires certs, remember to provide the ``custom_base_path`` as part of the settings or as a parameter in the constructor).
213
214In the demo-django and in the demo-flask folders you will find a ``saml`` folder, inside there is a ``certs`` folder and a ``settings.json`` and ``advanced_settings.json`` file. Those files contain the settings for the SAML toolkit. Copy them in your project and set the correct values.
215
216This is the ``settings.json`` file:
217
218```javascript
219{
220    // If strict is True, then the Python Toolkit will reject unsigned
221    // or unencrypted messages if it expects them to be signed or encrypted.
222    // Also it will reject the messages if the SAML standard is not strictly
223    // followed. Destination, NameId, Conditions ... are validated too.
224    "strict": true,
225
226    // Enable debug mode (outputs errors).
227    "debug": true,
228
229    // Service Provider Data that we are deploying.
230    "sp": {
231        // Identifier of the SP entity  (must be a URI)
232        "entityId": "https://<sp_domain>/metadata/",
233        // Specifies info about where and how the <AuthnResponse> message MUST be
234        // returned to the requester, in this case our SP.
235        "assertionConsumerService": {
236            // URL Location where the <Response> from the IdP will be returned
237            "url": "https://<sp_domain>/?acs",
238            // SAML protocol binding to be used when returning the <Response>
239            // message. OneLogin Toolkit supports this endpoint for the
240            // HTTP-POST binding only.
241            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
242        },
243        // Specifies info about where and how the <Logout Request/Response> message MUST be sent.
244        "singleLogoutService": {
245            // URL Location where the <LogoutRequest> from the IdP will be sent (IdP-initiated logout)
246            "url": "https://<sp_domain>/?sls",
247            // URL Location where the <LogoutResponse> from the IdP will sent (SP-initiated logout, reply)
248            // OPTIONAL: only specify if different from url parameter
249            //"responseUrl": "https://<sp_domain>/?sls",
250            // SAML protocol binding to be used when returning the <Response>
251            // message. OneLogin Toolkit supports the HTTP-Redirect binding
252            // only for this endpoint.
253            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
254        },
255        // If you need to specify requested attributes, set a
256        // attributeConsumingService. nameFormat, attributeValue and
257        // friendlyName can be ommited
258        "attributeConsumingService": {
259                // OPTIONAL: only specifiy if SP requires this.
260                // index is an integer which identifies the attributeConsumingService used
261                // to the SP. OneLogin toolkit supports configuring only one attributeConsumingService
262                // but in certain cases the SP requires a different value.  Defaults to '1'.
263                // "index": '1',
264                "serviceName": "SP test",
265                "serviceDescription": "Test Service",
266                "requestedAttributes": [
267                    {
268                        "name": "",
269                        "isRequired": false,
270                        "nameFormat": "",
271                        "friendlyName": "",
272                        "attributeValue": []
273                    }
274                ]
275        },
276        // Specifies the constraints on the name identifier to be used to
277        // represent the requested subject.
278        // Take a look on src/onelogin/saml2/constants.py to see the NameIdFormat that are supported.
279        "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
280        // Usually X.509 cert and privateKey of the SP are provided by files placed at
281        // the certs folder. But we can also provide them with the following parameters
282        "x509cert": "",
283        "privateKey": ""
284
285        /*
286         * Key rollover
287         * If you plan to update the SP X.509cert and privateKey
288         * you can define here the new X.509cert and it will be
289         * published on the SP metadata so Identity Providers can
290         * read them and get ready for rollover.
291         */
292        // 'x509certNew': '',
293    },
294
295    // Identity Provider Data that we want connected with our SP.
296    "idp": {
297        // Identifier of the IdP entity  (must be a URI)
298        "entityId": "https://app.onelogin.com/saml/metadata/<onelogin_connector_id>",
299        // SSO endpoint info of the IdP. (Authentication Request protocol)
300        "singleSignOnService": {
301            // URL Target of the IdP where the Authentication Request Message
302            // will be sent.
303            "url": "https://app.onelogin.com/trust/saml2/http-post/sso/<onelogin_connector_id>",
304            // SAML protocol binding to be used when returning the <Response>
305            // message. OneLogin Toolkit supports the HTTP-Redirect binding
306            // only for this endpoint.
307            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
308        },
309        // SLO endpoint info of the IdP.
310        "singleLogoutService": {
311            // URL Location where the <LogoutRequest> from the IdP will be sent (IdP-initiated logout)
312            "url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/<onelogin_connector_id>",
313            // URL Location where the <LogoutResponse> from the IdP will sent (SP-initiated logout, reply)
314            // OPTIONAL: only specify if different from url parameter
315            "responseUrl": "https://app.onelogin.com/trust/saml2/http-redirect/slo_return/<onelogin_connector_id>",
316            // SAML protocol binding to be used when returning the <Response>
317            // message. OneLogin Toolkit supports the HTTP-Redirect binding
318            // only for this endpoint.
319            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
320        },
321        // Public X.509 certificate of the IdP
322        "x509cert": "<onelogin_connector_cert>"
323        /*
324         *  Instead of using the whole X.509cert you can use a fingerprint in order to
325         *  validate a SAMLResponse (but you still need the X.509cert to validate LogoutRequest and LogoutResponse using the HTTP-Redirect binding).
326         *  But take in mind that the algortithm for the fingerprint should be as strong as the algorithm in a normal certificate signature
327	 *  (e.g. SHA256 or strong)
328         *
329         *  (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
330         *  or add for example the -sha256 , -sha384 or -sha512 parameter)
331         *
332         *  If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
333         *  let the toolkit know which algorithm was used.
334         Possible values: sha1, sha256, sha384 or sha512
335         *  'sha1' is the default value.
336         *
337         *  Notice that if you want to validate any SAML Message sent by the HTTP-Redirect binding, you
338         *  will need to provide the whole X.509cert.
339         */
340        // "certFingerprint": "",
341        // "certFingerprintAlgorithm": "sha1",
342
343        /* In some scenarios the IdP uses different certificates for
344         * signing/encryption, or is under key rollover phase and
345         * more than one certificate is published on IdP metadata.
346         * In order to handle that the toolkit offers that parameter.
347         * (when used, 'X.509cert' and 'certFingerprint' values are
348         * ignored).
349         */
350        // 'x509certMulti': {
351        //      'signing': [
352        //          '<cert1-string>'
353        //      ],
354        //      'encryption': [
355        //          '<cert2-string>'
356        //      ]
357        // }
358    }
359}
360```
361
362In addition to the required settings data (idp, sp), extra settings can be defined in `advanced_settings.json`:
363
364```javascript
365{
366    // Security settings
367    "security": {
368
369        /** signatures and encryptions offered **/
370
371        // Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
372        // will be encrypted.
373        "nameIdEncrypted": false,
374
375        // Indicates whether the <samlp:AuthnRequest> messages sent by this SP
376        // will be signed.  [Metadata of the SP will offer this info]
377        "authnRequestsSigned": false,
378
379        // Indicates whether the <samlp:logoutRequest> messages sent by this SP
380        // will be signed.
381        "logoutRequestSigned": false,
382
383        // Indicates whether the <samlp:logoutResponse> messages sent by this SP
384        // will be signed.
385        "logoutResponseSigned": false,
386
387        /* Sign the Metadata
388         false || true (use sp certs) || {
389                                            "keyFileName": "metadata.key",
390                                            "certFileName": "metadata.crt"
391                                         }
392        */
393        "signMetadata": false,
394
395        /** signatures and encryptions required **/
396
397        // Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest>
398        // and <samlp:LogoutResponse> elements received by this SP to be signed.
399        "wantMessagesSigned": false,
400
401        // Indicates a requirement for the <saml:Assertion> elements received by
402        // this SP to be signed. [Metadata of the SP will offer this info]
403        "wantAssertionsSigned": false,
404
405        // Indicates a requirement for the <saml:Assertion>
406        // elements received by this SP to be encrypted.
407        "wantAssertionsEncrypted": false,
408
409        // Indicates a requirement for the NameID element on the SAMLResponse
410        // received by this SP to be present.
411        "wantNameId": true,
412
413        // Indicates a requirement for the NameID received by
414        // this SP to be encrypted.
415        "wantNameIdEncrypted": false,
416
417        // Indicates a requirement for the AttributeStatement element
418        "wantAttributeStatement": true,
419
420        // Authentication context.
421        // Set to false and no AuthContext will be sent in the AuthNRequest,
422        // Set true or don't present this parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
423        // Set an array with the possible auth context values: array ('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'),
424        "requestedAuthnContext": true,
425	// Allows the authn comparison parameter to be set, defaults to 'exact' if the setting is not present.
426        "requestedAuthnContextComparison": "exact",
427        // Set to true to check that the AuthnContext(s) received match(es) the requested.
428        "failOnAuthnContextMismatch": false,
429
430        // In some environment you will need to set how long the published metadata of the Service Provider gonna be valid.
431        // is possible to not set the 2 following parameters (or set to null) and default values will be set (2 days, 1 week)
432        // Provide the desire TimeStamp, for example 2015-06-26T20:00:00Z
433        "metadataValidUntil": null,
434        // Provide the desire Duration, for example PT518400S (6 days)
435        "metadataCacheDuration": null,
436
437        // If enabled, URLs with single-label-domains will
438        // be allowed and not rejected by the settings validator (Enable it under Docker/Kubernetes/testing env, not recommended on production)
439        "allowSingleLabelDomains": false,
440
441        // Algorithm that the toolkit will use on signing process. Options:
442        //    'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
443        //    'http://www.w3.org/2000/09/xmldsig#dsa-sha1'
444        //    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
445        //    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
446        //    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
447        "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
448
449        // Algorithm that the toolkit will use on digest process. Options:
450        //    'http://www.w3.org/2000/09/xmldsig#sha1'
451        //    'http://www.w3.org/2001/04/xmlenc#sha256'
452        //    'http://www.w3.org/2001/04/xmldsig-more#sha384'
453        //    'http://www.w3.org/2001/04/xmlenc#sha512'
454        'digestAlgorithm': "http://www.w3.org/2001/04/xmlenc#sha256",
455
456        // Specify if you want the SP to view assertions with duplicated Name or FriendlyName attributes to be valid
457        // Defaults to false if not specified
458        'allowRepeatAttributeName': false
459    },
460
461    // Contact information template, it is recommended to suply a
462    // technical and support contacts.
463    "contactPerson": {
464        "technical": {
465            "givenName": "technical_name",
466            "emailAddress": "technical@example.com"
467        },
468        "support": {
469            "givenName": "support_name",
470            "emailAddress": "support@example.com"
471        }
472    },
473
474    // Organization information template, the info in en_US lang is
475    // recomended, add more if required.
476    "organization": {
477        "en-US": {
478            "name": "sp_test",
479            "displayname": "SP test",
480            "url": "http://sp.example.com"
481        }
482    }
483}
484```
485
486In the ``security`` section, you can set the way that the SP will handle the messages and assertions. Contact the admin of the IdP and ask them what the IdP expects, and decide what validations will handle the SP and what requirements the SP will have and communicate them to the IdP's admin too.
487
488Once we know what kind of data could be configured, let's talk about the way settings are handled within the toolkit.
489
490The settings files described (``settings.json`` and ``advanced_settings.json``) are loaded by the toolkit if not other dict with settings info is provided in the constructors of the toolkit. Let's see some examples.
491
492```python
493# Initializes toolkit with settings.json & advanced_settings.json files.
494auth = OneLogin_Saml2_Auth(req)
495# or
496settings = OneLogin_Saml2_Settings()
497
498# Initializes toolkit with settings.json & advanced_settings.json files from a custom base path.
499custom_folder = '/var/www/django-project'
500auth = OneLogin_Saml2_Auth(req, custom_base_path=custom_folder)
501# or
502settings = OneLogin_Saml2_Settings(custom_base_path=custom_folder)
503
504# Initializes toolkit with the dict provided.
505auth = OneLogin_Saml2_Auth(req, settings_data)
506# or
507settings = OneLogin_Saml2_Settings(settings_data)
508```
509
510You can declare the ``settings_data`` in the file that contains the constructor execution or locate them in any file and load the file in order to get the dict available as we see in the following example:
511
512```python
513filename = "/var/www/django-project/custom_settings.json" # The custom_settings.json contains a
514json_data_file = open(filename, 'r')                      # settings_data dict.
515settings_data = json.load(json_data_file)
516json_data_file.close()
517
518auth = OneLogin_Saml2_Auth(req, settings_data)
519```
520
521#### Metadata Based Configuration
522
523The method above requires a little extra work to manually specify attributes about the IdP. (And your SP application)
524
525There's an easier method -- use a metadata exchange.  Metadata is just an XML file that defines the capabilities of both the IdP and the SP application.  It also contains the X.509 public key certificates which add to the trusted relationship.  The IdP administrator can also configure custom settings for an SP based on the metadata.
526
527Using ````parse_remote```` IdP metadata can be obtained and added to the settings without further ado.
528
529Take in mind that the OneLogin_Saml2_IdPMetadataParser class does not validate in any way the URL that is introduced in order to be parsed.
530
531Usually the same administrator that handles the Service Provider also sets the URL to the IdP, which should be a trusted resource.
532
533But there are other scenarios, like a SAAS app where the administrator of the app delegates this functionality to other users. In this case, extra precaution should be taken in order to validate such URL inputs and avoid attacks like SSRF.
534
535
536``
537idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata')
538``
539
540You can specify a timeout in seconds for metadata retrieval, without it is not guaranteed that the request will complete
541
542``
543idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata', timeout=5)
544``
545
546If the Metadata contains several entities, the relevant ``EntityDescriptor`` can be specified when retrieving the settings from the ``IdpMetadataParser`` by its ``entityId`` value:
547
548``idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(https://example.com/metadatas, entity_id='idp_entity_id')``
549
550
551#### How load the library ####
552
553In order to use the toolkit library you need to import the file that contains the class that you will need
554on the top of your python file.
555
556``` python
557from onelogin.saml2.auth import OneLogin_Saml2_Auth
558from onelogin.saml2.settings import OneLogin_Saml2_Settings
559from onelogin.saml2.utils import OneLogin_Saml2_Utils
560```
561
562#### The Request ####
563
564Building a ``OneLogin\_Saml2\_Auth`` object requires a ``request`` parameter:
565
566```python
567auth = OneLogin_Saml2_Auth(req)
568```
569
570This parameter has the following scheme:
571
572```python
573req = {
574    "http_host": "",
575    "script_name": "",
576    "get_data": "",
577    "post_data": "",
578
579    # Advanced request options
580    "https": "",
581    "request_uri": "",
582    "query_string": "",
583    "validate_signature_from_qs": False,
584    "lowercase_urlencoding": False
585}
586```
587
588Each Python framework builds its own ``request`` object, you may map its data to match what the SAML toolkit expects.
589Let`s see some examples:
590
591```python
592def prepare_from_django_request(request):
593    return {
594        'http_host': request.META['HTTP_HOST'],
595        'script_name': request.META['PATH_INFO'],
596        'get_data': request.GET.copy(),
597        'post_data': request.POST.copy()
598    }
599
600def prepare_from_flask_request(request):
601    url_data = urlparse(request.url)
602    return {
603        'http_host': request.netloc,
604        'script_name': request.path,
605        'get_data': request.args.copy(),
606        'post_data': request.form.copy()
607    }
608```
609
610An explanation of some advanced request parameters:
611
612* `https` - Defaults to ``off``. Set this to ``on`` if you receive responses over HTTPS.
613
614* `request_uri` - The path where your SAML server receives requests. Set this if requests are not received at the server's root.
615
616* `query_string` - Set this with additional query parameters that should be passed to the request endpoint.
617
618* `validate_signature_from_qs` - If `True`, use `query_string` to validate request and response signatures. Otherwise, use `get_data`. Defaults to `False`. Note that when using `get_data`, query parameters need to be url-encoded for validation. By default we use upper-case url-encoding. Some IdPs, notably Microsoft AD, use lower-case url-encoding, which makes signature validation to fail. To fix this issue, either pass `query_string` and set `validate_signature_from_qs` to `True`, which works for all IdPs, or set `lowercase_urlencoding` to `True`, which only works for AD.
619
620
621#### Initiate SSO ####
622
623In order to send an ``AuthNRequest`` to the IdP:
624
625```python
626from onelogin.saml2.auth import OneLogin_Saml2_Auth
627
628req = prepare_request_for_toolkit(request)
629auth = OneLogin_Saml2_Auth(req)   # Constructor of the SP, loads settings.json
630                                  # and advanced_settings.json
631
632auth.login()      # Method that builds and sends the AuthNRequest
633```
634
635The ``AuthNRequest`` will be sent signed or unsigned based on the security info of the ``advanced_settings.json`` file (i.e. ``authnRequestsSigned``).
636
637The IdP will then return the SAML Response to the user's client. The client is then forwarded to the **Assertion Consumer Service (ACS)** of the SP with this information.
638
639We can set a ``return_to`` url parameter to the login function and that will be converted as a ``RelayState`` parameter:
640
641```python
642target_url = 'https://example.com'
643auth.login(return_to=target_url)
644```
645The login method can recieve 3 more optional parameters:
646
647* ``force_authn``       When ``true``, the ``AuthNReuqest`` will set the ``ForceAuthn='true'``
648* ``is_passive``        When true, the ``AuthNReuqest`` will set the ``Ispassive='true'``
649* ``set_nameid_policy`` When true, the ``AuthNReuqest`` will set a ``nameIdPolicy`` element.
650
651If a match on the future ``SAMLResponse`` ID and the ``AuthNRequest`` ID to be sent is required, that ``AuthNRequest`` ID must to be extracted and stored for future validation, we can get that ID by
652
653``auth.get_last_request_id()``
654
655#### The SP Endpoints ####
656
657Related to the SP there are 3 important endpoints: The metadata view, the ACS view and the SLS view.
658The toolkit provides examples of those views in the demos, but let's see an example.
659
660***SP Metadata***
661
662This code will provide the XML metadata file of our SP, based on the info that we provided in the settings files.
663
664```python
665req = prepare_request_for_toolkit(request)
666auth = OneLogin_Saml2_Auth(req)
667saml_settings = auth.get_settings()
668metadata = saml_settings.get_sp_metadata()
669errors = saml_settings.validate_metadata(metadata)
670if len(errors) == 0:
671    print(metadata)
672else:
673    print("Error found on Metadata: %s" % (', '.join(errors)))
674```
675
676The ``get_sp_metadata`` will return the metadata signed or not based on the security info of the ``advanced_settings.json`` (``signMetadata``).
677
678Before the XML metadata is exposed, a check takes place to ensure that the info to be provided is valid.
679
680Instead of using the Auth object, you can directly use
681```
682saml_settings = OneLogin_Saml2_Settings(settings=None, custom_base_path=None, sp_validation_only=True)
683```
684to get the settings object and with the ``sp_validation_only=True`` parameter we will avoid the IdP settings validation.
685
686***Assertion Consumer Service (ACS)***
687
688This code handles the SAML response that the IdP forwards to the SP through the user's client.
689
690```python
691req = prepare_request_for_toolkit(request)
692auth = OneLogin_Saml2_Auth(req)
693auth.process_response()
694errors = auth.get_errors()
695if not errors:
696    if auth.is_authenticated():
697        request.session['samlUserdata'] = auth.get_attributes()
698        if 'RelayState' in req['post_data'] and
699          OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
700            auth.redirect_to(req['post_data']['RelayState'])
701        else:
702            for attr_name in request.session['samlUserdata'].keys():
703                print('%s ==> %s' % (attr_name, '|| '.join(request.session['samlUserdata'][attr_name])))
704    else:
705      print('Not authenticated')
706else:
707    print("Error when processing SAML Response: %s %s" % (', '.join(errors), auth.get_last_error_reason()))
708```
709
710The SAML response is processed and then checked that there are no errors. It also verifies that the user is authenticated and stored the userdata in session.
711
712At that point there are 2 possible alternatives:
713
714* If no ``RelayState`` is provided, we could show the user data in this view or however we wanted.
715* If ``RelayState`` is provided, a redirection takes place.
716
717Notice that we saved the user data in the session before the redirection to have the user data available at the ``RelayState`` view.
718
719In order to retrieve attributes we use:
720
721```python
722attributes = auth.get_attributes();
723```
724
725With this method we get a dict with all the user data provided by the IdP in the assertion of the SAML response.
726
727If we execute print attributes we could get:
728
729```python
730{
731    "cn": ["Jhon"],
732    "sn": ["Doe"],
733    "mail": ["Doe"],
734    "groups": ["users", "members"]
735}
736```
737
738Each attribute name can be used as a key to obtain the value. Every attribute is a list of values. A single-valued attribute is a listy of a single element.
739
740The following code is equivalent:
741
742```python
743attributes = auth.get_attributes();
744print(attributes['cn'])
745
746print(auth.get_attribute('cn'))
747```
748
749Before trying to get an attribute, check that the user is authenticated. If the user isn't authenticated, an empty dict will be returned. For example, if we call to ``get_attributes`` before a ``auth.process_response``, the ``get_attributes()`` will return an empty dict.
750
751
752***Single Logout Service (SLS)***
753
754This code handles the Logout Request and the Logout Responses.
755
756```python
757delete_session_callback = lambda: request.session.flush()
758url = auth.process_slo(delete_session_cb=delete_session_callback)
759errors = auth.get_errors()
760if len(errors) == 0:
761    if url is not None:
762        return redirect(url)
763    else:
764        print("Sucessfully Logged out")
765else:
766    print("Error when processing SLO: %s %s" % (', '.join(errors), auth.get_last_error_reason()))
767```
768
769If the SLS endpoints receives a Logout Response, the response is validated and the session could be closed, using the callback.
770
771```python
772# Part of the process_slo method
773logout_response = OneLogin_Saml2_Logout_Response(self.__settings, self.__request_data['get_data']['SAMLResponse'])
774if not logout_response.is_valid(self.__request_data, request_id):
775    self.__errors.append('invalid_logout_response')
776elif logout_response.get_status() != OneLogin_Saml2_Constants.STATUS_SUCCESS:
777    self.__errors.append('logout_not_success')
778elif not keep_local_session:
779    OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)
780```
781
782If the SLS endpoints receives an Logout Request, the request is validated, the session is closed and a Logout Response is sent to the SLS endpoint of the IdP.
783
784```python
785# Part of the process_slo method
786request = OneLogin_Saml2_Utils.decode_base64_and_inflate(self.__request_data['get_data']['SAMLRequest'])
787if not OneLogin_Saml2_Logout_Request.is_valid(self.__settings, request, self.__request_data):
788    self.__errors.append('invalid_logout_request')
789else:
790    if not keep_local_session:
791        OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)
792
793    in_response_to = request.id
794    response_builder = OneLogin_Saml2_Logout_Response(self.__settings)
795    response_builder.build(in_response_to)
796    logout_response = response_builder.get_response()
797
798    parameters = {'SAMLResponse': logout_response}
799    if 'RelayState' in self.__request_data['get_data']:
800        parameters['RelayState'] = self.__request_data['get_data']['RelayState']
801
802    security = self.__settings.get_security_data()
803    if 'logoutResponseSigned' in security and security['logoutResponseSigned']:
804        parameters['SigAlg'] = OneLogin_Saml2_Constants.RSA_SHA1
805        parameters['Signature'] = self.build_response_signature(logout_response, parameters.get('RelayState', None))
806
807    return self.redirect_to(self.get_slo_url(), parameters)
808```
809
810If we don't want that ``process_slo`` to destroy the session, pass a ``true`` parameter to the ``process_slo`` method:
811
812```python
813keepLocalSession = true
814auth.process_slo(keep_local_session=keepLocalSession);
815```
816
817#### Initiate SLO ####
818
819In order to send a Logout Request to the IdP:
820
821The Logout Request will be sent signed or unsigned based on the security info of the ``advanced_settings.json`` (``logoutRequestSigned``).
822
823The IdP will return the Logout Response through the user's client to the Single Logout Service (SLS) of the SP.
824
825We can set a ``return_to`` url parameter to the logout function and that will be converted as a ``RelayState`` parameter:
826
827```python
828target_url = 'https://example.com'
829auth.logout(return_to=target_url)
830```
831
832Also there are another 5 optional parameters that can be set:
833
834* ``name_id``: That will be used to build the ``LogoutRequest``. If no ``name_id`` parameter is set and the auth object processed a
835SAML Response with a ``NameId``, then this ``NameId`` will be used.
836* ``session_index``: ``SessionIndex`` that identifies the session of the user.
837* ``nq``: IDP Name Qualifier.
838* ``name_id_format``: The ``NameID`` Format that will be set in the ``LogoutRequest``.
839* ``spnq``: The ``NameID SP NameQualifier`` will be set in the ``LogoutRequest``.
840
841If no ``name_id`` is provided, the ``LogoutRequest`` will contain a ``NameID`` with the entity Format.
842If ``name_id`` is provided and no ``name_id_format`` is provided, the ``NameIDFormat`` of the settings will be used.
843
844If a match on the ``LogoutResponse`` ID and the ``LogoutRequest`` ID to be sent is required, that ``LogoutRequest`` ID must to be extracted and stored for future validation, we can get that ID by:
845
846```python
847auth.get_last_request_id()
848```
849
850#### Example of a view that initiates the SSO request and handles the response (is the acs target) ####
851
852We can code a unique file that initiates the SSO process, handle the response, get the attributes, initiate the SLO and processes the logout response.
853
854Note: Review the demos, in a later section we explain the demo use case further in detail.
855
856```python
857req = prepare_request_for_toolkit(request)  # Process the request and build the request dict that
858                                            # the toolkit expects
859
860auth = OneLogin_Saml2_Auth(req)             # Initialize the SP SAML instance
861
862if 'sso' in request.args:                   # SSO action (SP-SSO initited).  Will send an AuthNRequest to the IdP
863    return redirect(auth.login())
864elif 'sso2' in request.args:                       # Another SSO init action
865    return_to = '%sattrs/' % request.host_url      # but set a custom RelayState URL
866    return redirect(auth.login(return_to))
867elif 'slo' in request.args:                     # SLO action. Will sent a Logout Request to IdP
868    nameid = request.session['samlNameId']
869    nameid_format = request.session['samlNameIdFormat']
870    nameid_nq = request.session['samlNameIdNameQualifier']
871    nameid_spnq = request.session['samlNameIdSPNameQualifier']
872    session_index = request.session['samlSessionIndex']
873    return redirect(auth.logout(None, nameid, session_index, nameid_nq, nameid_format, nameid_spnq))
874elif 'acs' in request.args:                 # Assertion Consumer Service
875    auth.process_response()                     # Process the Response of the IdP
876    errors = auth.get_errors()              # This method receives an array with the errors
877    if len(errors) == 0:                    # that could took place during the process
878        if not auth.is_authenticated():         # This check if the response was ok and the user
879            msg = "Not authenticated"           # data retrieved or not (user authenticated)
880        else:
881            request.session['samlUserdata'] = auth.get_attributes()     # Retrieves user data
882            request.session['samlNameId'] = auth.get_nameid()
883            request.session['samlNameIdFormat'] = auth.get_nameid_format()
884            request.session['samlNameIdNameQualifier'] = auth.get_nameid_nq()
885            request.session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq()
886            request.session['samlSessionIndex'] = auth.get_session_index()
887            self_url = OneLogin_Saml2_Utils.get_self_url(req)
888            if 'RelayState' in request.form and self_url != request.form['RelayState']:
889                return redirect(auth.redirect_to(request.form['RelayState']))   # Redirect if there is a relayState
890            else:                           # If there is user data we save that to print it later.
891                msg = ''
892                for attr_name in request.session['samlUserdata'].keys():
893                    msg += '%s ==> %s' % (attr_name, '|| '.join(request.session['samlUserdata'][attr_name]))
894elif 'sls' in request.args:                                             # Single Logout Service
895    delete_session_callback = lambda: session.clear()           # Obtain session clear callback
896    url = auth.process_slo(delete_session_cb=delete_session_callback)   # Process the Logout Request & Logout Response
897    errors = auth.get_errors()              #  Retrieves possible validation errors
898    if len(errors) == 0:
899        if url is not None:
900            return redirect(url)
901        else:
902            msg = "Sucessfully logged out"
903
904if len(errors) == 0:
905  print(msg)
906else:
907  print(', '.join(errors))
908```
909
910
911### SP Key rollover ###
912
913If you plan to update the SP ``x509cert`` and ``privateKey`` you can define the new ``x509cert`` as ``settings['sp']['x509certNew']`` and it will be
914published on the SP metadata so Identity Providers can read them and get ready for rollover.
915
916
917### IdP with multiple certificates ###
918
919In some scenarios the IdP uses different certificates for
920signing/encryption, or is under key rollover phase and more than one certificate is published on IdP metadata.
921
922In order to handle that the toolkit offers the ``settings['idp']['x509certMulti']`` parameter.
923
924When that parameter is used, ``x509cert`` and ``certFingerprint`` values will be ignored by the toolkit.
925
926The ``x509certMulti`` is an array with 2 keys:
927- ``signing``: An array of certs that will be used to validate IdP signature
928- ``encryption``: An array with one unique cert that will be used to encrypt data to be sent to the IdP.
929
930
931### Replay attacks ###
932
933In order to avoid replay attacks, you can store the ID of the SAML messages already processed, to avoid processing them twice. Since the Messages expires and will be invalidated due that fact, you don't need to store those IDs longer than the time frame that you currently accepting.
934
935Get the ID of the last processed message/assertion with the ``get_last_message_id/get_last_assertion_id`` method of the ``Auth`` object.
936
937
938### Main classes and methods ###
939
940Described below are the main classes and methods that can be invoked from the SAML2 library.
941
942#### OneLogin_Saml2_Auth - auth.py ####
943
944Main class of OneLogin Python Toolkit
945
946* `__init__` Initializes the SP SAML instance.
947* ***login*** Initiates the SSO process.
948* ***logout*** Initiates the SLO process.
949* ***process_response*** Process the SAML Response sent by the IdP.
950* ***process_slo*** Process the SAML Logout Response / Logout Request sent by the IdP.
951* ***redirect_to*** Redirects the user to the url past by parameter or to the url that we defined in our SSO Request.
952* ***is_authenticated*** Checks if the user is authenticated or not.
953* ***get_attributes*** Returns the set of SAML attributes.
954* ***get_attribute*** Returns the requested SAML attribute.
955* ***get_nameid*** Returns the ``nameID``.
956* ***get_session_index*** Gets the ``SessionIndex`` from the ``AuthnStatement``.
957* ***get_session_expiration*** Gets the ``SessionNotOnOrAfter`` from the ``AuthnStatement``.
958* ***get_errors*** Returns a list with code errors if something went wrong.
959* ***get_last_error_reason*** Returns the reason of the last error
960* ***get_sso_url*** Gets the SSO url.
961* ***get_slo_url*** Gets the SLO url.
962* ***get_last_request_id*** The ID of the last Request SAML message generated (``AuthNRequest``, ``LogoutRequest``).
963* ***get_last_authn_contexts*** Returns the list of authentication contexts sent in the last SAML Response.
964* ***build_request_signature*** Builds the Signature of the SAML Request.
965* ***build_response_signature*** Builds the Signature of the SAML Response.
966* ***get_settings*** Returns the settings info.
967* ***set_strict*** Set the strict mode active/disable.
968* ***get_last_request_xml*** Returns the most recently-constructed/processed XML SAML request (``AuthNRequest``, ``LogoutRequest``)
969* ***get_last_response_xml*** Returns the most recently-constructed/processed XML SAML response (``SAMLResponse``, ``LogoutResponse``). If the SAMLResponse had an encrypted assertion, decrypts it.
970* ***get_last_message_id*** The ID of the last Response SAML message processed.
971* ***get_last_assertion_id*** The ID of the last assertion processed.
972* ***get_last_assertion_not_on_or_after*** The ``NotOnOrAfter`` value of the valid ``SubjectConfirmationData`` node (if any) of the last assertion processed (is only calculated with strict = true)
973
974#### OneLogin_Saml2_Auth - authn_request.py ####
975
976SAML 2 Authentication Request class
977
978* `__init__` This class handles an ``AuthNRequest``. It builds an ``AuthNRequest`` object.
979* ***get_request*** Returns unsigned ``AuthnRequest``.
980* ***get_id*** Returns the ``AuthNRequest`` ID.
981* ***get_xml*** Returns the XML that will be sent as part of the request.
982
983#### OneLogin_Saml2_Response - response.py ####
984
985SAML 2 Authentication Response class
986
987* `__init__` Constructs the SAML Response object.
988* ***is_valid*** Determines if the SAML Response is valid. Includes checking of the signature by a certificate.
989* ***check_status*** Check if the status of the response is success or not
990* ***get_audiences*** Gets the audiences
991* ***get_issuers*** Gets the issuers (from message and from assertion)
992* ***get_nameid_data*** Gets the NameID Data provided by the SAML Response from the IdP (returns a dict)
993* ***get_nameid*** Gets the NameID provided by the SAML Response from the IdP (returns a string)
994* ***get_session_not_on_or_after*** Gets the ``SessionNotOnOrAfter`` from the ``AuthnStatement``
995* ***get_session_index*** Gets the ``SessionIndex`` from the ``AuthnStatement``
996* ***get_attributes*** Gets the Attributes from the ``AttributeStatement`` element.
997* ***validate_num_assertions*** Verifies that the document only contains a single Assertion (encrypted or not)
998* ***validate_timestamps*** Verifies that the document is valid according to Conditions Element
999* ***get_error*** After execute a validation process, if fails this method returns the cause
1000* ***get_xml_document*** Returns the SAML Response document (If contains an encrypted assertion, decrypts it).
1001* ***get_id*** the ID of the response
1002* ***get_assertion_id*** the ID of the assertion in the response
1003* ***get_assertion_not_on_or_after*** the ``NotOnOrAfter`` value of the valid ``SubjectConfirmationData`` if any
1004
1005#### OneLogin_Saml2_LogoutRequest - logout_request.py ####
1006
1007SAML 2 Logout Request class
1008
1009* `__init__` Constructs the Logout Request object.
1010* ***get_request*** Returns the Logout Request deflated, base64-encoded.
1011* ***get_id*** Returns the ID of the Logout Request. (If you have the object you can access to the id attribute)
1012* ***get_nameid_data*** Gets the NameID Data of the the Logout Request (returns a dict).
1013* ***get_nameid*** Gets the NameID of the Logout Request Message (returns a string).
1014* ***get_issuer*** Gets the Issuer of the Logout Request Message.
1015* ***get_session_indexes*** Gets the ``SessionIndexes`` from the Logout Request.
1016* ***is_valid*** Checks if the Logout Request recieved is valid.
1017* ***get_error*** After execute a validation process, if fails this method returns the cause.
1018* ***get_xml*** Returns the XML that will be sent as part of the request or that was received at the SP
1019
1020#### OneLogin_Saml2_LogoutResponse - logout_response.py ####
1021
1022SAML 2 Logout Response class
1023
1024* `__init__` Constructs a Logout Response object.
1025* ***get_issuer*** Gets the Issuer of the Logout Response Message
1026* ***get_status*** Gets the Status of the Logout Response.
1027* ***is_valid*** Determines if the SAML ``LogoutResponse`` is valid
1028* ***build*** Creates a Logout Response object.
1029* ***get_response*** Returns a Logout Response object.
1030* ***get_error*** After execute a validation process, if fails this method returns the cause.
1031* ***get_xml*** Returns the XML that will be sent as part of the response or that was received at the SP
1032
1033#### OneLogin_Saml2_Settings - settings.py ####
1034
1035Configuration of the OneLogin Python Toolkit
1036
1037* `__init__`  Initializes the settings: Sets the paths of the different folders and Loads settings info from settings file or array/object provided.
1038* ***check_settings*** Checks the settings info.
1039* ***check_idp_settings*** Checks the IdP settings info.
1040* ***check_sp_settings*** Checks the SP settings info.
1041* ***get_errors*** Returns an array with the errors, the array is empty when the settings is ok.
1042* ***get_sp_metadata*** Gets the SP metadata. The XML representation.
1043* ***validate_metadata*** Validates an XML SP Metadata.
1044* ***get_base_path*** Returns base path.
1045* ***get_cert_path*** Returns cert path.
1046* ***get_lib_path*** Returns lib path.
1047* ***get_ext_lib_path*** Returns external lib path.
1048* ***get_schemas_path*** Returns schema path.
1049* ***check_sp_certs*** Checks if the X.509 certs of the SP exists and are valid.
1050* ***get_sp_key*** Returns the X.509 private key of the SP.
1051* ***get_sp_cert*** Returns the X.509 public cert of the SP.
1052* ***get_sp_cert_new*** Returns the future X.509 public cert of the SP.
1053* ***get_idp_cert*** Returns the X.509 public cert of the IdP.
1054* ***get_sp_data*** Gets the SP data.
1055* ***get_idp_data*** Gets the IdP data.
1056* ***get_security_data***  Gets security data.
1057* ***get_contacts*** Gets contacts data.
1058* ***get_organization*** Gets organization data.
1059* ***format_idp_cert*** Formats the IdP cert.
1060* ***format_idp_cert_multi*** Formats all registered IdP certs.
1061* ***format_sp_cert*** Formats the SP cert.
1062* ***format_sp_cert_new*** Formats the SP cert new.
1063* ***format_sp_key*** Formats the private key.
1064* ***set_strict*** Activates or deactivates the strict mode.
1065* ***is_strict*** Returns if the ``strict`` mode is active.
1066* ***is_debug_active*** Returns if the debug is active.
1067
1068#### OneLogin_Saml2_Metadata - metadata.py ####
1069
1070A class that contains functionality related to the metadata of the SP
1071
1072* ***builder*** Generates the metadata of the SP based on the settings.
1073* ***sign_metadata*** Signs the metadata with the key/cert provided.
1074* ***add_x509_key_descriptors*** Adds the X.509 descriptors (sign/encryption) to the metadata
1075
1076#### OneLogin_Saml2_Utils - utils.py ####
1077
1078Auxiliary class that contains several methods
1079
1080* ***decode_base64_and_inflate*** Base64 decodes and then inflates according to RFC1951.
1081* ***deflate_and_base64_encode*** Deflates and the base64 encodes a string.
1082* ***format_cert*** Returns a X.509 cert (adding header & footer if required).
1083* ***format_private_key*** Returns a private key (adding header & footer if required).
1084* ***redirect*** Executes a redirection to the provided url (or return the target url).
1085* ***get_self_url_host*** Returns the protocol + the current host + the port (if different than common ports).
1086* ***get_self_host*** Returns the current host.
1087* ***is_https*** Checks if https or http.
1088* ***get_self_url_no_query*** Returns the URL of the current host + current view.
1089* ***get_self_routed_url_no_query*** Returns the routed URL of the current host + current view.
1090* ***get_self_url*** Returns the URL of the current host + current view + query.
1091* ***generate_unique_id*** Generates an unique string (used for example as ID for assertions).
1092* ***parse_time_to_SAML*** Converts a UNIX timestamp to SAML2 timestamp on the form yyyy-mm-ddThh:mm:ss(\.s+)?Z.
1093* ***parse_SAML_to_time*** Converts a SAML2 timestamp on the form yyyy-mm-ddThh:mm:ss(\.s+)?Z to a UNIX timestamp.
1094* ***now*** Returns unix timestamp of actual time.
1095* ***parse_duration*** Interprets a ISO8601 duration value relative to a given timestamp.
1096* ***get_expire_time*** Compares 2 dates and returns the earliest.
1097* ***delete_local_session*** Deletes the local session.
1098* ***calculate_X.509_fingerprint*** Calculates the fingerprint of a X.509 cert.
1099* ***format_finger_print*** Formates a fingerprint.
1100* ***generate_name_id*** Generates a nameID.
1101* ***get_status*** Gets Status from a Response.
1102* ***decrypt_element*** Decrypts an encrypted element.
1103* ***write_temp_file*** Writes some content into a temporary file and returns it.
1104* ***add_sign*** Adds signature key and senders certificate to an element (Message or Assertion).
1105* ***validate_sign*** Validates a signature (Message or Assertion).
1106* ***validate_binary_sign*** Validates signed bynary data (Used to validate GET Signature).
1107
1108#### OneLogin_Saml2_XML- xml_utils.py ####
1109
1110A class that contains methods to handle XMLs
1111
1112* ***to_string*** Serialize an element to an encoded string representation of its XML tree.
1113* ***to_etree*** Parses an XML document or fragment from a string.
1114* ***validate_xml*** Validates a xml against a schema
1115* ***query*** Extracts nodes that match the query from the Element
1116* ***extract_tag_text***
1117
1118#### OneLogin_Saml2_IdPMetadataParser - idp_metadata_parser.py ####
1119
1120A class that contains methods to obtain and parse metadata from IdP
1121
1122* ***get_metadata*** Get the metadata XML from the provided URL
1123* ***parse_remote*** Get the metadata XML from the provided URL and parse it, returning a dict with extracted data
1124* ***parse*** Parse the Identity Provider metadata and returns a dict with extracted data
1125* ***merge_settings*** Will update the settings with the provided new settings data extracted from the IdP metadata
1126
1127
1128For more info, look at the source code. Each method is documented and details about what does and how to use it are provided. Make sure to also check the doc folder where HTML documentation about the classes and methods is provided.
1129
1130Demos included in the toolkit
1131-----------------------------
1132
1133The toolkit includes 3 demos to teach how use the toolkit (A Django, Flask and a Tornado project), take a look on it.
1134Demos require that SP and IdP are well configured before test it, so edit the settings files.
1135
1136Notice that each python framework has it own way to handle routes/urls and process request, so focus on
1137how it deployed. New demos using other python frameworks are welcome as a contribution.
1138
1139### Getting Started ###
1140
1141We said that this toolkit includes a Django application demo and a Flask application demo,
1142let's see how fast is it to deploy them.
1143
1144***Virtualenv***
1145
1146The use of a [virtualenv](http://virtualenv.readthedocs.org/en/latest/) is
1147highly recommended.
1148
1149Virtualenv helps isolating the python enviroment used to run the toolkit. You
1150can find more details and an installation guide in the
1151[official documentation](http://virtualenv.readthedocs.org/en/latest/).
1152
1153Once you have your virtualenv ready and loaded, then you can install the
1154toolkit on it in development mode executing this:
1155```
1156 python setup.py develop
1157```
1158
1159Using this method of deployment the toolkit files will be linked instead of
1160copied, so if you make changes on them you won't need to reinstall the toolkit.
1161
1162If you want install it in a normal mode, execute:
1163```
1164 python setup.py install
1165```
1166
1167### Demo Flask ###
1168
1169You'll need a virtualenv with the toolkit installed on it.
1170
1171To run the demo you need to install the requirements first. Load your
1172virtualenv and execute:
1173
1174```
1175 pip install -r demo-flask/requirements.txt
1176```
1177
1178This will install flask and its dependencies. Once it has finished, you have to complete the configuration
1179of the toolkit. You'll find it at `demo-flask/settings.json`
1180
1181Now, with the virtualenv loaded, you can run the demo like this:
1182```
1183 cd demo-flask
1184 python index.py
1185```
1186
1187You'll have the demo running at http://localhost:8000
1188
1189#### Content ####
1190
1191The flask project contains:
1192
1193
1194* ***index.py*** Is the main flask file, where or the SAML handle take place.
1195
1196* ***templates***. Is the folder where flask stores the templates of the project. It was implemented a base.html template that is extended by index.html and attrs.html, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
1197
1198* ***saml*** Is a folder that contains the 'certs' folder that could be used to store the X.509 public and private key, and the saml toolkit settings (settings.json and advanced_settings.json).
1199
1200
1201#### SP setup ####
1202
1203The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In the ``demo-flask``, it uses the first method.
1204
1205In the ``index.py`` file we define the ``app.config['SAML_PATH']``, that will target to the ``saml`` folder. We require it in order to load the settings files.
1206
1207First we need to edit the ``saml/settings.json`` file, configure the SP part and review the metadata of the IdP and complete the IdP info.  Later edit the ``saml/advanced_settings.json`` files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
1208
1209#### IdP setup ####
1210
1211Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
1212
1213#### How it works ####
1214
1215 1. First time you access to the main view (http://localhost:8000), you can select to login and return to the same view or login and be redirected to ``/?attrs`` (attrs view).
1216
1217 2. When you click:
1218
1219    2.1 in the first link, we access to ``/?sso`` (index view). An ``AuthNRequest`` is sent to the IdP, we authenticate at the IdP and then a Response is sent through the user's client to the SP, specifically the Assertion Consumer Service view: ``/?acs``. Notice that a ``RelayState`` parameter is set to the url that initiated the process, the index view.
1220
1221    2.2 in the second link we access to ``/?attrs`` (attrs view), we will expetience have the same process described at 2.1 with the diference that as ``RelayState`` is set the ``attrs`` url.
1222
1223 3. The SAML Response is processed in the ACS ``/?acs``, if the Response is not valid, the process stops here and a message is shown. Otherwise we are redirected to the ``RelayState`` view. a) / or b) ``/?attrs``
1224
1225 4. We are logged in the app and the user attributes are showed. At this point, we can test the single log out functionality.
1226
1227 The single log out functionality could be tested by 2 ways.
1228
1229    5.1 SLO Initiated by SP. Click on the ``logout`` link at the SP, after that a Logout Request is sent to the IdP, the session at the IdP is closed and replies through the client to the SP with a Logout Response (sent to the Single Logout Service endpoint). The SLS endpoint ``/?sls`` of the SP process the Logout Response and if is valid, close the user session of the local app. Notice that the SLO Workflow starts and ends at the SP.
1230
1231    5.2 SLO Initiated by IdP. In this case, the action takes place on the IdP side, the logout process is initiated at the IdP, sends a Logout Request to the SP (SLS endpoint, ``/?sls``). The SLS endpoint of the SP process the Logout Request and if is valid, close the session of the user at the local app and send a Logout Response to the IdP (to the SLS endpoint of the IdP). The IdP receives the Logout Response, process it and close the session at of the IdP. Notice that the SLO Workflow starts and ends at the IdP.
1232
1233Notice that all the SAML Requests and Responses are handled at a unique view (index) and how GET parameters are used to know the action that must be done.
1234
1235### Demo Tornado ###
1236
1237You'll need a virtualenv with the toolkit installed on it.
1238
1239First of all you need some packages, execute:
1240```
1241apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
1242```
1243
1244To run the demo you need to install the requirements first. Load your
1245virtualenv and execute:
1246```
1247 pip install -r demo-tornado/requirements.txt
1248```
1249
1250
1251This will install tornado and its dependencies. Once it has finished, you have to complete the configuration
1252of the toolkit. You'll find it at `demo-tornado/saml/settings.json`
1253
1254Now, with the virtualenv loaded, you can run the demo like this:
1255```
1256 cd demo-tornado
1257 python views.py
1258```
1259
1260You'll have the demo running at http://localhost:8000
1261
1262#### Content ####
1263
1264The tornado project contains:
1265
1266* ***views.py*** Is the main flask file, where or the SAML handle take place.
1267
1268* ***settings.py*** Contains the base path and the path where is located the ``saml`` folder and the ``template`` folder
1269
1270* ***templates***. Is the folder where tornado stores the templates of the project. It was implemented a base.html template that is extended by index.html and attrs.html, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
1271
1272* ***saml*** Is a folder that contains the 'certs' folder that could be used to store the X.509 public and private key, and the saml toolkit settings (settings.json and advanced_settings.json).
1273
1274#### SP setup ####
1275
1276The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In the ``demo-tornado``, it uses the first method.
1277
1278In the ``settings.py`` file we define the ``SAML_PATH``, that will target to the ``saml`` folder. We require it in order to load the settings files.
1279
1280First we need to edit the ``saml/settings.json`` file, configure the SP part and review the metadata of the IdP and complete the IdP info.  Later edit the ``saml/advanced_settings.json`` files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
1281
1282#### IdP setup ####
1283
1284Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
1285
1286#### How it works ####
1287
12881. First time you access to the main view (http://localhost:8000), you can select to login and return to the same view or login and be redirected to ``/?attrs`` (attrs view).
1289
1290 2. When you click:
1291
1292    2.1 in the first link, we access to ``/?sso`` (index view). An ``AuthNRequest`` is sent to the IdP, we authenticate at the IdP and then a Response is sent through the user's client to the SP, specifically the Assertion Consumer Service view: ``/?acs``. Notice that a ``RelayState`` parameter is set to the url that initiated the process, the index view.
1293
1294    2.2 in the second link we access to ``/?attrs`` (attrs view), we will expetience have the same process described at 2.1 with the diference that as ``RelayState`` is set the ``attrs`` url.
1295
1296 3. The SAML Response is processed in the ACS ``/?acs``, if the Response is not valid, the process stops here and a message is shown. Otherwise we are redirected to the ``RelayState`` view. a) / or b) ``/?attrs``
1297
1298 4. We are logged in the app and the user attributes are showed. At this point, we can test the single log out functionality.
1299
1300 The single log out functionality could be tested by 2 ways.
1301
1302    5.1 SLO Initiated by SP. Click on the ``logout`` link at the SP, after that a Logout Request is sent to the IdP, the session at the IdP is closed and replies through the client to the SP with a Logout Response (sent to the Single Logout Service endpoint). The SLS endpoint ``/?sls`` of the SP process the Logout Response and if is valid, close the user session of the local app. Notice that the SLO Workflow starts and ends at the SP.
1303
1304    5.2 SLO Initiated by IdP. In this case, the action takes place on the IdP side, the logout process is initiated at the IdP, sends a Logout Request to the SP (SLS endpoint, ``/?sls``). The SLS endpoint of the SP process the Logout Request and if is valid, close the session of the user at the local app and send a Logout Response to the IdP (to the SLS endpoint of the IdP). The IdP receives the Logout Response, process it and close the session at of the IdP. Notice that the SLO Workflow starts and ends at the IdP.
1305
1306Notice that all the SAML Requests and Responses are handled at a unique view (index) and how GET parameters are used to know the action that must be done.
1307
1308### Demo Django ###
1309
1310You'll need a virtualenv with the toolkit installed on it.
1311
1312To run the demo you need to install the requirements first. Load your
1313virtualenv and execute:
1314```
1315 pip install -r demo-django/requirements.txt
1316```
1317This will install django and its dependencies. Once it has finished, you have to complete the configuration of the toolkit.
1318
1319Later, with the virtualenv loaded, you can run the demo like this:
1320```
1321 cd demo-django
1322 python manage.py runserver 0.0.0.0:8000
1323```
1324
1325You'll have the demo running at http://localhost:8000.
1326
1327Note that many of the configuration files expect HTTPS. This is not required by the demo, as replacing these SP URLs with HTTP will work just fine. HTTPS is however highly encouraged, and left as an exercise for the reader for their specific needs.
1328
1329If you want to integrate a production django application, take a look on this SAMLServiceProviderBackend that uses our toolkit to add SAML support: https://github.com/KristianOellegaard/django-saml-service-provider
1330
1331#### Content ####
1332
1333The django project contains:
1334
1335* ***manage.py***. A file that is automatically created in each Django project. Is a thin wrapper around django-admin.py that takes care of putting the project’s package on ``sys.path`` and sets the ``DJANGO_SETTINGS_MODULE`` environment variable.
1336
1337* ***saml*** Is a folder that contains the 'certs' folder that could be used to store the X.509 public and private key, and the saml toolkit settings (``settings.json`` and ``advanced_settings.json``).
1338
1339* ***demo*** Is the main folder of the django project, that contains the typical files:
1340  * ***settings.py*** Contains the default parameters of a django project except the ``SAML_FOLDER`` parameter, that may contain the path where is located the ``saml`` folder.
1341  * ***urls.py*** A file that define url routes. In the demo we defined ``'/'`` that is related to the index view, ``'/attrs'`` that is related with the attrs view and ``'/metadata'``, related to the metadata view.
1342  * ***views.py*** This file contains the views of the django project and some aux methods.
1343  * ***wsgi.py*** A file that let as deploy django using WSGI, the Python standard for web servers and applications.
1344
1345* ***templates***. Is the folder where django stores the templates of the project. It was implemented a ``base.html`` template that is extended by ``index.html`` and ``attrs.html``, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
1346
1347#### SP setup ####
1348
1349The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In the demo-django it used the first method.
1350
1351After set the ``SAML_FOLDER`` in the ``demo/settings.py``, the settings of the Python toolkit will be loaded on the Django web.
1352
1353First we need to edit the ``saml/settings.json``, configure the SP part and review the metadata of the IdP and complete the IdP info.  Later edit the ``saml/advanced_settings.json`` files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
1354
1355#### IdP setup ####
1356
1357Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
1358
1359#### How it works ####
1360
1361This demo works very similar to the ``flask-demo`` (We did it intentionally).
1362
1363### Getting up and running on Heroku ###
1364
1365Getting ``python3-saml`` up and running on Heroku will require some extra legwork: ``python3-saml`` depends on ``python-xmlsec`` which depends on headers from the ``xmlsec1-dev`` Linux package to install correctly.
1366
1367First you will need to add the ```apt``` buildpack to your build server:
1368
1369```
1370heroku buildpacks:add --index=1 -a your-app heroku-community/apt
1371heroku buildpacks:add --index=2 -a your-app heroku/python
1372```
1373
1374You can confirm the buildpacks have been added in the correct order with ```heroku buildpacks -a your-app```, you should see the apt buildpack first followed by the Python buildpack.
1375
1376Then add an ```Aptfile``` into the root of your repository containing the ```libxmlsec1-dev``` package, the file should look like:
1377```
1378libxmlsec1-dev
1379
1380```
1381
1382Finally, add ``python3-saml`` to your ``requirements.txt`` and ```git push``` to trigger a build.
1383
1384### Demo Pyramid ###
1385
1386Unlike the other two projects, you don't need a pre-existing virtualenv to get
1387up and running here, since Pyramid comes from the
1388[buildout](http://www.buildout.org/en/latest/) school of thought.
1389
1390To run the demo you need to install Pyramid, the requirements, etc.:
1391```
1392 cd demo_pyramid
1393 python3 -m venv env
1394 env/bin/pip install --upgrade pip setuptools
1395 env/bin/pip install -e ".[testing]"
1396```
1397
1398If you want to make sure the tests pass, run:
1399```
1400 env/bin/pytest
1401```
1402
1403Next, edit the settings in `demo_pyramid/saml/settings.json`. (Pyramid runs on
1404port 6543 by default.)
1405
1406Now you can run the demo like this:
1407```
1408 env/bin/pserve development.ini
1409```
1410
1411If that worked, the demo is now running at http://localhost:6543.
1412
1413#### Content ####
1414
1415The Pyramid project contains:
1416
1417
1418* ***\_\_init__.py*** is the main Pyramid file that configures the app and its routes.
1419
1420* ***views.py*** is where all the SAML handling takes place.
1421
1422* ***templates*** is the folder where Pyramid stores the templates of the project. It was implemented a ``layout.jinja2`` template that is extended by ``index.jinja2`` and ``attrs.jinja2``, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
1423
1424* ***saml*** is a folder that contains the 'certs' folder that could be used to store the X.509 public and private key, and the saml toolkit settings (``settings.json`` and ``advanced_settings.json``).
1425
1426
1427#### SP setup ####
1428
1429The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In ``demo_pyramid`` the first method is used.
1430
1431In the ``views.py`` file we define the ``SAML_PATH``, which will target the ``saml`` folder. We require it in order to load the settings files.
1432
1433First we need to edit the ``saml/settings.json``, configure the SP part and review the metadata of the IdP and complete the IdP info.  Later edit the ``saml/advanced_settings.json`` files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
1434
1435#### IdP setup ####
1436
1437Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
1438
1439#### How it works ####
1440
14411. First time you access to the main view (http://localhost:6543), you can select to login and return to the same view or login and be redirected to ``/?attrs`` (attrs view).
1442
1443 2. When you click:
1444
1445    2.1 in the first link, we access to ``/?sso`` (index view). An ``AuthNRequest`` is sent to the IdP, we authenticate at the IdP and then a Response is sent through the user's client to the SP, specifically the Assertion Consumer Service view: ``/?acs``. Notice that a ``RelayState`` parameter is set to the url that initiated the process, the index view.
1446
1447    2.2 in the second link we access to ``/?attrs`` (attrs view), we will experience the same process described at 2.1 with the diference that as ``RelayState`` is set the ``attrs`` url.
1448
1449 3. The SAML Response is processed in the ACS ``/?acs``, if the Response is not valid, the process stops here and a message is shown. Otherwise we are redirected to the ``RelayState`` view. a) ``/`` or b) ``/?attrs``
1450
1451 4. We are logged in the app and the user attributes are showed. At this point, we can test the single log out functionality.
1452
1453 The single log out funcionality could be tested by 2 ways.
1454
1455    5.1 SLO Initiated by SP. Click on the "logout" link at the SP, after that a Logout Request is sent to the IdP, the session at the IdP is closed and replies through the client to the SP with a Logout Response (sent to the Single Logout Service endpoint). The SLS endpoint /?sls of the SP process the Logout Response and if is valid, close the user session of the local app. Notice that the SLO Workflow starts and ends at the SP.
1456
1457    5.2 SLO Initiated by IdP. In this case, the action takes place on the IdP side, the logout process is initiated at the IdP, sends a Logout Request to the SP (SLS endpoint, /?sls). The SLS endpoint of the SP process the Logout Request and if is valid, close the session of the user at the local app and send a Logout Response to the IdP (to the SLS endpoint of the IdP). The IdP receives the Logout Response, process it and close the session at of the IdP. Notice that the SLO Workflow starts and ends at the IdP.
1458
1459Notice that all the SAML Requests and Responses are handled at a unique view (index) and how GET parameters are used to know the action that must be done.
1460