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