Package: maykin-python3-saml
SAML Python Toolkit (compatible with Python3)Add SAML support to your Python software using this library.
Forget those complicated libraries and use the open source library provided by the SAML tool community.This version supports Python3. Python 2 support was deprecated on Jan 1st, 2020:python-samlWarningVersion 1.16.X is the latest version supporting Python2, consider its use deprecated. 1.17 won't be Python2 compatible.Version 1.13.0 sets sha256 and rsa-sha256 as default algorithmsVersion 1.8.0 sets strict mode active by defaultUpdatepython3-samlto1.5.0, this version includes security improvements for preventing XEE and Xpath Injections.Updatepython3-samlto1.4.0, this version includes a fix for theCVE-2017-11427vulnerability.This version also changes how the calculate fingerprint method works, and will expect as input a formatted X.509 certificate.Updatepython3-samlto1.2.6that adds the use defusedxml that will prevent XEE and other attacks based on the abuse of XML. (CVE-2017-9672)Updatepython3-samlto>= 1.2.1,1.2.0had a bug on signature validation process (when usingwantAssertionsSignedandwantMessagesSigned).CVE-2016-10002511.2.0version includes a security patch that contains extra validations that will prevent signature wrapping attacks.python3-saml < v1.2.0is vulnerable and allows signature wrapping!Security GuidelinesIf you believe you have discovered a security vulnerability in this toolkit, please report it by mail to the maintainer:sixto.martin.garcia+security@gmail.comWhy add SAML support to my software?SAML is an XML-based standard for web browser single sign-on and is defined by
the OASIS Security Services Technical Committee. The standard has been around
since 2002, but lately it is becoming popular due its advantages:Usability- One-click access from portals or intranets, deep linking,
password elimination and automatically renewing sessions make life
easier for the user.Security- Based on strong digital signatures for authentication and
integrity, SAML is a secure single sign-on protocol that the largest
and most security conscious enterprises in the world rely on.Speed- SAML is fast. One browser redirect is all it takes to securely
sign a user into an application.Phishing Prevention- If you don’t have a password for an app, you
can’t be tricked into entering it on a fake login page.IT Friendly- SAML simplifies life for IT because it centralizes
authentication, provides greater visibility and makes directory
integration easier.Opportunity- B2B cloud vendor should support SAML to facilitate the
integration of their product.General DescriptionSAML Python toolkit lets you turn your Python application into a SP
(Service Provider) that can be connected to an IdP (Identity Provider).Supports:SSO and SLO (SP-Initiated and IdP-Initiated).Assertion and nameId encryption.Assertion signatures.Message signatures:AuthNRequest,LogoutRequest,LogoutResponses.Enable an Assertion Consumer Service endpoint.Enable a Single Logout Service endpoint.Publish the SP metadata (which can be signed).Key Features:saml2int- Implements the SAML 2.0 Web Browser SSO Profile.Session-less- Forget those common conflicts between the SP and
the final app, the toolkit delegate session in the final app.Easy to use- Programmer will be allowed to code high-level and
low-level programming, 2 easy to use APIs are available.Tested- Thoroughly tested.InstallationDependenciespython 3.6xmlsecPython bindings for the XML Security Library.lxmlPython bindings for the libxml2 and libxslt libraries.isodateAn ISO 8601 date/time/
duration parser and formatterReview thesetup.pyfile to know the version of the library thatpython3-samlis usingCodeOption 1. Download from GitHubThe toolkit is hosted on GitHub. You can download it from:Latest release:https://github.com/saml-toolkits/python3-saml/releases/latestMaster repo:https://github.com/saml-toolkits/python3-saml/tree/masterCopy the core of the library(src/onelogin/saml2 folder)and merge thesetup.pyinside the Python application. (Each application has its structure so take your time to locate the Python SAML toolkit in the best place).Option 2. Download from pypiThe toolkit is hosted in pypi, you can find thepython3-samlpackage athttps://pypi.python.org/pypi/python3-samlYou can install it executing:$ pip install python3-samlIf you want to know how a project can handle python packages review thisguideand review thissampleprojectNOTETo avoidlibxml2library version incompatibilities betweenxmlsecandlxmlit is recommended thatlxmlis not installed from binary.This can be ensured by executing:$ pip install --force-reinstall --no-binary lxml lxmlSecurity WarningIn production, thestrictparameter MUST be set as"true". Otherwise
your environment is not secure and will be exposed to attacks.In 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.Avoiding Open Redirect attacksSome implementations uses the RelayState parameter as a way to control the flow when SSO and SLO succeeded. So basically the
user is redirected to the value of the RelayState.If you are using Signature Validation on the HTTP-Redirect binding, you will have the RelayState value integrity covered, otherwise, and
on HTTP-POST binding, you can't trust the RelayState so before
executing the validation, you need to verify that its value belong
a trusted and expected URL.Read more about Open RedirectCWE-601.Avoiding Replay attacksA replay attack is basically try to reuse an intercepted valid SAML Message in order to impersonate a SAML action (SSO or SLO).SAML Messages have a limited timelife (NotBefore, NotOnOrAfter) that
make harder this kind of attacks, but they are still possible.In order to avoid them, the SP can keep a list of SAML Messages or Assertion IDs already validated and processed. Those values only need
to be stored the amount of time of the SAML Message life time, so
we don't need to store all processed message/assertion Ids, but the most recent ones.The OneLogin_Saml2_Auth class contains theget_last_request_id,get_last_message_idandget_last_assertion_idmethods to retrieve the IDsChecking that the ID of the current Message/Assertion does not exists in the list of the ones already processed will prevent replay attacks.Getting StartedKnowing the toolkitThe new SAML Toolkit contains different folders (certs,lib,demo-django,demo-flaskandtests) and some files.Let's start describing them:srcThis folder contains the heart of the toolkit,onelogin/saml2folder contains the new version of
the classes and methods that are described in a later section.demo-djangoThis folder contains a Django project that will be used as demo to show how to add SAML support to the Django Framework.demois the main folder of the Django project (with itssettings.py,views.py,urls.py),templatesis the Django templates of the project andsamlis a folder that contains thecertsfolder that could be used to store the X.509 public and private key, and the SAML toolkit settings (settings.jsonandadvanced_settings.json).Notice about certsSAML requires a X.509 cert to sign and encrypt elements likeNameID,Message,Assertion,Metadata.If 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:sp.crt The public cert of the SPsp.key The private key of the SPOr also we can provide those data in the setting file at thex509certand theprivateKeyJSON parameters of thespelement.Sometimes 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.crtandmetadata.key.Usesp_new.crtif you are in a key rollover process and you want to
publish that X.509 certificate on Service Provider metadata.If you want to create self-signed certs, you can do it at thehttps://www.samltool.com/self_signed_certs.phpservice, or using the command:opensslreq-new-x509-days3652-nodes-outsp.crt-keyoutsp.keydemo-flaskThis folder contains a Flask project that will be used as demo to show how to add SAML support to the Flask Framework.index.pyis the main Flask file that has all the code, this file uses the templates stored at thetemplatesfolder. In thesamlfolder we found thecertsfolder to store the X.509 public and private key, and the SAML toolkit settings (settings.jsonandadvanced_settings.json).demo_pyramidThis folder contains a Pyramid project that will be used as demo to show how to add SAML support to thePyramid Web Framework.\_\_init__.pyis the main file that configures the app and its routes,views.pyis where all the logic and SAML handling takes place, and the templates are stored in thetemplatesfolder. Thesamlfolder is the same as in the other two demos.demo-tornadoThis 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 itssettings.py) is the main Flask file that has all the code, this file uses the templates stored at thetemplatesfolder. In thesamlfolder we found thecertsfolder to store the X.509 public and private key, and the SAML toolkit settings (settings.jsonandadvanced_settings.json).It requires python3.5 (it's using tornado 6.0.3)setup.pySetup script is the centre of all activity in building, distributing, and installing modules.
Read more athttps://pythonhosted.org/an_example_pypi_project/setuptools.htmltestsContains the unit test of the toolkit.In order to execute the test you only need to load the virtualenv with the toolkit installed on it properly:pip install -e ".[test]"and execute:python setup.py testThe 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:python setup.py test --test-suite tests.src.OneLogin.saml2_tests.auth_test.OneLogin_Saml2_Auth_TestWith the--test-suiteparameter you can specify the module to test. You'll find all the module available and their class names attests/src/OneLogin/saml2_tests/.How It WorksSettingsFirst 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.There are two ways to provide the settings information:Use asettings.jsonfile that we should locate in any folder, but indicates its path with thecustom_base_pathparameter.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 thecustom_base_pathas part of the settings or as a parameter in the constructor).In the demo-django and in the demo-flask folders you will find asamlfolder, inside there is acertsfolder and asettings.jsonandadvanced_settings.jsonfile. Those files contain the settings for the SAML toolkit. Copy them in your project and set the correct values.This is thesettings.jsonfile:{// If strict is True, then the Python Toolkit will reject unsigned// or unencrypted messages if it expects them to be signed or encrypted.// Also it will reject the messages if the SAML standard is not strictly// followed. Destination, NameId, Conditions ... are validated too."strict":true,// Enable debug mode (outputs errors)."debug":true,// Service Provider Data that we are deploying."sp":{// Identifier of the SP entity  (must be a URI)"entityId":"https://<sp_domain>/metadata/",// Specifies info about where and how the <AuthnResponse> message MUST be// returned to the requester, in this case our SP."assertionConsumerService":{// URL Location where the <Response> from the IdP will be returned"url":"https://<sp_domain>/?acs",// SAML protocol binding to be used when returning the <Response>// message. SAML Toolkit supports this endpoint for the// HTTP-POST binding only."binding":"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"},// Specifies info about where and how the <Logout Request/Response> message MUST be sent."singleLogoutService":{// URL Location where the <LogoutRequest> from the IdP will be sent (IdP-initiated logout, request)"url":"https://<sp_domain>/?sls",// URL Location where the <LogoutResponse> from the IdP will sent (SP-initiated logout, reply)// OPTIONAL: only specify if different from url parameter//"responseUrl": "https://<sp_domain>/?sls",// SAML protocol binding to be used when returning the <Response>// message. SAML Toolkit supports the HTTP-Redirect binding// only for this endpoint."binding":"urn:oasis:names:tc:SAML:2.0:bindings:SOAP","responseUrl":"https://<sp_domain>/?sls",// SAML protocol binding to be used with SP-initiated logout// It defaults 'sp.singleLogoutService.binding' if not specified but `responseUrl` is specified"responseBinding":"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"},// If you need to specify requested attributes, set a// attributeConsumingService. nameFormat, attributeValue and// friendlyName can be ommited. If more than 1 service is added, the// index parameter is required."attributeConsumingServices":[{// Index is an integer which identifies the attributeConsumingService used// to the SP."index":"1","serviceName":"SP test","serviceDescription":"Test Service","requestedAttributes":[{"name":"","isRequired":false,"nameFormat":"","friendlyName":"","attributeValue":[]}]}],// Specifies the constraints on the name identifier to be used to// represent the requested subject.// Take a look on src/onelogin/saml2/constants.py to see the NameIdFormat that are supported."NameIDFormat":"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",// Usually X.509 cert and privateKey of the SP are provided by files placed at// the certs folder. But we can also provide them with the following parameters"x509cert":"","privateKey":""/** Key rollover* If you plan to update the SP X.509cert and privateKey* you can define here the new X.509cert and it will be* published on the SP metadata so Identity Providers can* read them and get ready for rollover.*/// 'x509certNew': '',},// Identity Provider Data that we want connected with our SP."idp":{// Identifier of the IdP entity  (must be a URI)"entityId":"https://app.onelogin.com/saml/metadata/<onelogin_connector_id>",// SSO endpoint info of the IdP. (Authentication Request protocol)"singleSignOnService":{// URL Target of the IdP where the Authentication Request Message// will be sent."url":"https://app.onelogin.com/trust/saml2/http-post/sso/<onelogin_connector_id>",// SAML protocol binding to be used when returning the <Response>// message. SAML Toolkit supports the HTTP-Redirect binding// only for this endpoint."binding":"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"},// SLO endpoint info of the IdP."singleLogoutService":{// URL Location where the <LogoutRequest> from the IdP will be sent (IdP-initiated logout)"url":"https://app.onelogin.com/trust/saml2/http-redirect/slo/<onelogin_connector_id>",// URL Location where the <LogoutResponse> from the IdP will sent (SP-initiated logout, reply)// OPTIONAL: only specify if different from url parameter"responseUrl":"https://app.onelogin.com/trust/saml2/http-redirect/slo_return/<onelogin_connector_id>",// SAML protocol binding to be used when returning the <Response>// message. SAML Toolkit supports the HTTP-Redirect binding// only for this endpoint."binding":"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"},// Public X.509 certificate of the IdP"x509cert":"<onelogin_connector_cert>"/**  Instead of using the whole X.509cert you can use a fingerprint in order to*  validate a SAMLResponse (but you still need the X.509cert to validate LogoutRequest and LogoutResponse using the HTTP-Redirect binding).*  But take in mind that the algorithm for the fingerprint should be as strong as the algorithm in a normal certificate signature*  (e.g. SHA256 or strong)**  (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,*  or add for example the -sha256 , -sha384 or -sha512 parameter)**  If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to*  let the toolkit know which algorithm was used.Possible values: sha1, sha256, sha384 or sha512*  'sha1' is the default value.**  Notice that if you want to validate any SAML Message sent by the HTTP-Redirect binding, you*  will need to provide the whole X.509cert.*/// "certFingerprint": "",// "certFingerprintAlgorithm": "sha1",/* In some scenarios the IdP uses different certificates for* signing/encryption, or is under key rollover phase and* more than one certificate is published on IdP metadata.* In order to handle that the toolkit offers that parameter.* (when used, 'X.509cert' and 'certFingerprint' values are* ignored).*/// 'x509certMulti': {//      'signing': [//          '<cert1-string>'//      ],//      'encryption': [//          '<cert2-string>'//      ]// }}}In addition to the required settings data (idp, sp), extra settings can be defined inadvanced_settings.json:{// Security settings"security":{/** signatures and encryptions offered **/// Indicates that the nameID of the <samlp:logoutRequest> sent by this SP// will be encrypted."nameIdEncrypted":false,// Indicates whether the <samlp:AuthnRequest> messages sent by this SP// will be signed.  [Metadata of the SP will offer this info]"authnRequestsSigned":false,// Indicates whether the <samlp:logoutRequest> messages sent by this SP// will be signed."logoutRequestSigned":false,// Indicates whether the <samlp:logoutResponse> messages sent by this SP// will be signed."logoutResponseSigned":false,/* Sign the Metadatafalse || true (use sp certs) || {"keyFileName": "metadata.key","certFileName": "metadata.crt"}*/"signMetadata":false,/** signatures and encryptions required **/// Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest>// and <samlp:LogoutResponse> elements received by this SP to be signed."wantMessagesSigned":false,// Indicates a requirement for the <saml:Assertion> elements received by// this SP to be signed. [Metadata of the SP will offer this info]"wantAssertionsSigned":false,// Indicates a requirement for the <saml:Assertion>// elements received by this SP to be encrypted."wantAssertionsEncrypted":false,// Indicates a requirement for the NameID element on the SAMLResponse// received by this SP to be present."wantNameId":true,// Indicates a requirement for the NameID received by// this SP to be encrypted."wantNameIdEncrypted":false,// Indicates a requirement for the AttributeStatement element"wantAttributeStatement":true,// Authentication context.// Set to false and no AuthContext will be sent in the AuthNRequest,// 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'// 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'),"requestedAuthnContext":true,// Allows the authn comparison parameter to be set, defaults to 'exact' if the setting is not present."requestedAuthnContextComparison":"exact",// Set to true to check that the AuthnContext(s) received match(es) the requested."failOnAuthnContextMismatch":false,// In some environment you will need to set how long the published metadata of the Service Provider gonna be valid.// is possible to not set the 2 following parameters (or set to null) and default values will be set (2 days, 1 week)// Provide the desire TimeStamp, for example 2015-06-26T20:00:00Z"metadataValidUntil":null,// Provide the desire Duration, for example PT518400S (6 days)"metadataCacheDuration":null,// If enabled, URLs with single-label-domains will// be allowed and not rejected by the settings validator (Enable it under Docker/Kubernetes/testing env, not recommended on production)"allowSingleLabelDomains":false,// Algorithm that the toolkit will use on signing process. Options://    'http://www.w3.org/2000/09/xmldsig#rsa-sha1'//    'http://www.w3.org/2000/09/xmldsig#dsa-sha1'//    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'//    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'//    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'"signatureAlgorithm":"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",// Algorithm that the toolkit will use on digest process. Options://    'http://www.w3.org/2000/09/xmldsig#sha1'//    'http://www.w3.org/2001/04/xmlenc#sha256'//    'http://www.w3.org/2001/04/xmldsig-more#sha384'//    'http://www.w3.org/2001/04/xmlenc#sha512''digestAlgorithm':"http://www.w3.org/2001/04/xmlenc#sha256",// Specify if you want the SP to view assertions with duplicated Name or FriendlyName attributes to be valid// Defaults to false if not specified'allowRepeatAttributeName':false,// If the toolkit receive a message signed with a// deprecated algorithm (defined at the constant class)// will raise an error and reject the message"rejectDeprecatedAlgorithm":true},// Contact information template, it is recommended to suply a// technical and support contacts."contactPerson":{"technical":{"givenName":"technical_name","emailAddress":"technical@example.com"},"support":{"givenName":"support_name","emailAddress":"support@example.com"}},// Organization information template, the info in en_US lang is// recommended, add more if required."organization":{"en-US":{"name":"sp_test","displayname":"SP test","url":"http://sp.example.com"}}}In thesecuritysection, 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.Once we know what kind of data could be configured, let's talk about the way settings are handled within the toolkit.The settings files described (settings.jsonandadvanced_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.# Initializes toolkit with settings.json & advanced_settings.json files.auth=OneLogin_Saml2_Auth(req)# orsettings=OneLogin_Saml2_Settings()# Initializes toolkit with settings.json & advanced_settings.json files from a custom base path.custom_folder='/var/www/django-project'auth=OneLogin_Saml2_Auth(req,custom_base_path=custom_folder)# orsettings=OneLogin_Saml2_Settings(custom_base_path=custom_folder)# Initializes toolkit with the dict provided.auth=OneLogin_Saml2_Auth(req,settings_data)# orsettings=OneLogin_Saml2_Settings(settings_data)You can declare thesettings_datain 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:filename="/var/www/django-project/custom_settings.json"# The custom_settings.json contains ajson_data_file=open(filename,'r')# settings_data dict.settings_data=json.load(json_data_file)json_data_file.close()auth=OneLogin_Saml2_Auth(req,settings_data)Metadata Based ConfigurationThe method above requires a little extra work to manually specify attributes about the IdP. (And your SP application)There'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.Usingparse_remoteIdP metadata can be obtained and added to the settings without further ado.Take in mind that the OneLogin_Saml2_IdPMetadataParser class does not validate in any way the URL that is introduced in order to be parsed.Usually the same administrator that handles the Service Provider also sets the URL to the IdP, which should be a trusted resource.But 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.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata')You can specify a timeout in seconds for metadata retrieval, without it is not guaranteed that the request will completeidp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata', timeout=5)If the Metadata contains several entities, the relevantEntityDescriptorcan be specified when retrieving the settings from theIdpMetadataParserby itsentityIdvalue:idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(https://example.com/metadatas, entity_id='idp_entity_id')How load the libraryIn order to use the toolkit library you need to import the file that contains the class that you will need
on the top of your python file.fromonelogin.saml2.authimportOneLogin_Saml2_Authfromonelogin.saml2.settingsimportOneLogin_Saml2_Settingsfromonelogin.saml2.utilsimportOneLogin_Saml2_UtilsThe RequestBuilding aOneLogin\_Saml2\_Authobject requires arequestparameter:auth=OneLogin_Saml2_Auth(req)This parameter has the following scheme:req={"http_host":"","script_name":"","get_data":"","post_data":"",# Advanced request options"https":"","request_uri":"","query_string":"","validate_signature_from_qs":False,"lowercase_urlencoding":False}Each Python framework builds its ownrequestobject, you may map its data to match what the SAML toolkit expects.
Let`s see some examples:defprepare_from_django_request(request):return{'http_host':request.META['HTTP_HOST'],'script_name':request.META['PATH_INFO'],'get_data':request.GET.copy(),'post_data':request.POST.copy()}defprepare_from_flask_request(request):url_data=urlparse(request.url)return{'http_host':request.host,'script_name':request.path,'get_data':request.args.copy(),'post_data':request.form.copy()}An explanation of some advanced request parameters:https- Defaults tooff. Set this toonif you receive responses over HTTPS.request_uri- The path where your SAML server receives requests. Set this if requests are not received at the server's root.query_string- Set this with additional query parameters that should be passed to the request endpoint.validate_signature_from_qs- IfTrue, usequery_stringto validate request and response signatures. Otherwise, useget_data. Defaults toFalse. Note that when usingget_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 passquery_stringand setvalidate_signature_from_qstoTrue, which works for all IdPs, or setlowercase_urlencodingtoTrue, which only works for AD.Initiate SSOIn order to send anAuthNRequestto the IdP:fromonelogin.saml2.authimportOneLogin_Saml2_Authreq=prepare_request_for_toolkit(request)auth=OneLogin_Saml2_Auth(req)# Constructor of the SP, loads settings.json# and advanced_settings.jsonauth.login()# This method will build and return a AuthNRequest URL that can be# either redirected to, or printed out onto the screen as a hyperlinkTheAuthNRequestwill be sent signed or unsigned based on the security info of theadvanced_settings.jsonfile (i.e.authnRequestsSigned).The IdP will then return the SAML Response to the user's client. The client is then forwarded to theAssertion Consumer Service (ACS)of the SP with this information.We can set areturn_tourl parameter to the login function and that will be converted as aRelayStateparameter:target_url='https://example.com'auth.login(return_to=target_url)The login method can receive 3 more optional parameters:force_authnWhentrue, theAuthNReuqestwill set theForceAuthn='true'is_passiveWhen true, theAuthNReuqestwill set theIspassive='true'set_nameid_policyWhen true, theAuthNReuqestwill set anameIdPolicyelement.If a match on the futureSAMLResponseID and theAuthNRequestID to be sent is required, thatAuthNRequestID must to be extracted and stored for future validation, we can get that ID byauth.get_last_request_id()The SP EndpointsRelated to the SP there are 3 important endpoints: The metadata view, the ACS view and the SLS view.
The toolkit provides examples of those views in the demos, but let's see an example.SP MetadataThis code will provide the XML metadata file of our SP, based on the info that we provided in the settings files.req=prepare_request_for_toolkit(request)auth=OneLogin_Saml2_Auth(req)saml_settings=auth.get_settings()metadata=saml_settings.get_sp_metadata()errors=saml_settings.validate_metadata(metadata)iflen(errors)==0:print(metadata)else:print("Error found on Metadata:%s"%(', '.join(errors)))Theget_sp_metadatawill return the metadata signed or not based on the security info of theadvanced_settings.json(signMetadata).Before the XML metadata is exposed, a check takes place to ensure that the info to be provided is valid.Instead of using the Auth object, you can directly usesaml_settings = OneLogin_Saml2_Settings(settings=None, custom_base_path=None, sp_validation_only=True)to get the settings object and with thesp_validation_only=Trueparameter we will avoid the IdP settings validation.Assertion Consumer Service (ACS)This code handles the SAML response that the IdP forwards to the SP through the user's client.req=prepare_request_for_toolkit(request)auth=OneLogin_Saml2_Auth(req)auth.process_response()errors=auth.get_errors()ifnoterrors:ifauth.is_authenticated():request.session['samlUserdata']=auth.get_attributes()if'RelayState'inreq['post_data']andOneLogin_Saml2_Utils.get_self_url(req)!=req['post_data']['RelayState']:# To avoid 'Open Redirect' attacks, before execute the redirection confirm# the value of the req['post_data']['RelayState'] is a trusted URL.auth.redirect_to(req['post_data']['RelayState'])else:forattr_nameinrequest.session['samlUserdata'].keys():print('%s==>%s'%(attr_name,'|| '.join(request.session['samlUserdata'][attr_name])))else:print('Not authenticated')else:print("Error when processing SAML Response:%s%s"%(', '.join(errors),auth.get_last_error_reason()))The 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.At that point there are 2 possible alternatives:If noRelayStateis provided, we could show the user data in this view or however we wanted.IfRelayStateis provided, a redirection takes place.Notice that we saved the user data in the session before the redirection to have the user data available at theRelayStateview.In order to retrieve attributes we use:attributes=auth.get_attributes();With this method we get a dict with all the user data provided by the IdP in the assertion of the SAML response.If we execute print attributes we could get:{"cn":["Jhon"],"sn":["Doe"],"mail":["Doe"],"groups":["users","members"]}Each 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 list of a single element.The following code is equivalent:attributes=auth.get_attributes();print(attributes['cn'])print(auth.get_attribute('cn'))Before 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 toget_attributesbefore aauth.process_response, theget_attributes()will return an empty dict.Single Logout Service (SLS)This code handles the Logout Request and the Logout Responses.delete_session_callback=lambda:request.session.flush()url=auth.process_slo(delete_session_cb=delete_session_callback)errors=auth.get_errors()iflen(errors)==0:ifurlisnotNone:# To avoid 'Open Redirect' attacks, before execute the redirection confirm# the value of the url is a trusted URL.returnredirect(url)else:print("Successfully Logged out")else:print("Error when processing SLO:%s%s"%(', '.join(errors),auth.get_last_error_reason()))If the SLS endpoints receives a Logout Response, the response is validated and the session could be closed, using the callback.# Part of the process_slo methodlogout_response=OneLogin_Saml2_Logout_Response(self.__settings,self.__request_data['get_data']['SAMLResponse'])ifnotlogout_response.is_valid(self.__request_data,request_id):self.__errors.append('invalid_logout_response')eliflogout_response.get_status()!=OneLogin_Saml2_Constants.STATUS_SUCCESS:self.__errors.append('logout_not_success')elifnotkeep_local_session:OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)If 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.# Part of the process_slo methodrequest=OneLogin_Saml2_Utils.decode_base64_and_inflate(self.__request_data['get_data']['SAMLRequest'])ifnotOneLogin_Saml2_Logout_Request.is_valid(self.__settings,request,self.__request_data):self.__errors.append('invalid_logout_request')else:ifnotkeep_local_session:OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)in_response_to=request.idresponse_builder=OneLogin_Saml2_Logout_Response(self.__settings)response_builder.build(in_response_to)logout_response=response_builder.get_response()parameters={'SAMLResponse':logout_response}if'RelayState'inself.__request_data['get_data']:parameters['RelayState']=self.__request_data['get_data']['RelayState']security=self.__settings.get_security_data()if'logoutResponseSigned'insecurityandsecurity['logoutResponseSigned']:parameters['SigAlg']=OneLogin_Saml2_Constants.RSA_SHA1parameters['Signature']=self.build_response_signature(logout_response,parameters.get('RelayState',None))returnself.redirect_to(self.get_slo_url(),parameters)If we don't want thatprocess_sloto destroy the session, pass atrueparameter to theprocess_slomethod:keepLocalSession=trueauth.process_slo(keep_local_session=keepLocalSession);Initiate SLOIn order to send a Logout Request to the IdP:The Logout Request will be sent signed or unsigned based on the security info of theadvanced_settings.json(logoutRequestSigned).The IdP will return the Logout Response through the user's client to the Single Logout Service (SLS) of the SP.We can set areturn_tourl parameter to the logout function and that will be converted as aRelayStateparameter:target_url='https://example.com'auth.logout(return_to=target_url)Also there are another 5 optional parameters that can be set:name_id: That will be used to build theLogoutRequest. If noname_idparameter is set and the auth object processed a
SAML Response with aNameId, then thisNameIdwill be used.session_index:SessionIndexthat identifies the session of the user.nq: IDP Name Qualifier.name_id_format: TheNameIDFormat that will be set in theLogoutRequest.spnq: TheNameID SP NameQualifierwill be set in theLogoutRequest.If noname_idis provided, theLogoutRequestwill contain aNameIDwith the entity Format.
Ifname_idis provided and noname_id_formatis provided, theNameIDFormatof the settings will be used.If a match on theLogoutResponseID and theLogoutRequestID to be sent is required, thatLogoutRequestID must to be extracted and stored for future validation, we can get that ID by:auth.get_last_request_id()Example of a view that initiates the SSO request and handles the response (is the acs target)We can code a unique file that initiates the SSO process, handle the response, get the attributes, initiate the SLO and processes the logout response.Note: Review the demos, in a later section we explain the demo use case further in detail.req=prepare_request_for_toolkit(request)# Process the request and build the request dict that# the toolkit expectsauth=OneLogin_Saml2_Auth(req)# Initialize the SP SAML instanceif'sso'inrequest.args:# SSO action (SP-SSO initited).  Will send an AuthNRequest to the IdPreturnredirect(auth.login())elif'sso2'inrequest.args:# Another SSO init actionreturn_to='%sattrs/'%request.host_url# but set a custom RelayState URLreturnredirect(auth.login(return_to))elif'slo'inrequest.args:# SLO action. Will sent a Logout Request to IdPnameid=request.session['samlNameId']nameid_format=request.session['samlNameIdFormat']nameid_nq=request.session['samlNameIdNameQualifier']nameid_spnq=request.session['samlNameIdSPNameQualifier']session_index=request.session['samlSessionIndex']returnredirect(auth.logout(None,nameid,session_index,nameid_nq,nameid_format,nameid_spnq))elif'acs'inrequest.args:# Assertion Consumer Serviceauth.process_response()# Process the Response of the IdPerrors=auth.get_errors()# This method receives an array with the errorsiflen(errors)==0:# that could took place during the processifnotauth.is_authenticated():# This check if the response was ok and the usermsg="Not authenticated"# data retrieved or not (user authenticated)else:request.session['samlUserdata']=auth.get_attributes()# Retrieves user datarequest.session['samlNameId']=auth.get_nameid()request.session['samlNameIdFormat']=auth.get_nameid_format()request.session['samlNameIdNameQualifier']=auth.get_nameid_nq()request.session['samlNameIdSPNameQualifier']=auth.get_nameid_spnq()request.session['samlSessionIndex']=auth.get_session_index()self_url=OneLogin_Saml2_Utils.get_self_url(req)if'RelayState'inrequest.formandself_url!=request.form['RelayState']:# To avoid 'Open Redirect' attacks, before execute the redirection confirm# the value of the request.form['RelayState'] is a trusted URL.returnredirect(auth.redirect_to(request.form['RelayState']))# Redirect if there is a relayStateelse:# If there is user data we save that to print it later.msg=''forattr_nameinrequest.session['samlUserdata'].keys():msg+='%s==>%s'%(attr_name,'|| '.join(request.session['samlUserdata'][attr_name]))elif'sls'inrequest.args:# Single Logout Servicedelete_session_callback=lambda:session.clear()# Obtain session clear callbackurl=auth.process_slo(delete_session_cb=delete_session_callback)# Process the Logout Request & Logout Responseerrors=auth.get_errors()#  Retrieves possible validation errorsiflen(errors)==0:ifurlisnotNone:# To avoid 'Open Redirect' attacks, before execute the redirection confirm# the value of the url is a trusted URL.returnredirect(url)else:msg="Successfully logged out"iflen(errors)==0:print(msg)else:print(', '.join(errors))SP Key rolloverIf you plan to update the SPx509certandprivateKeyyou can define the newx509certassettings['sp']['x509certNew']and it will be
published on the SP metadata so Identity Providers can read them and get ready for rollover.IdP with multiple certificatesIn some scenarios the IdP uses different certificates for
signing/encryption, or is under key rollover phase and more than one certificate is published on IdP metadata.In order to handle that the toolkit offers thesettings['idp']['x509certMulti']parameter.When that parameter is used,x509certandcertFingerprintvalues will be ignored by the toolkit.Thex509certMultiis an array with 2 keys:signing: An array of certs that will be used to validate IdP signatureencryption: An array with one unique cert that will be used to encrypt data to be sent to the IdP.Replay attacksIn 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.Get the ID of the last processed message/assertion with theget_last_message_id/get_last_assertion_idmethod of theAuthobject.Main classes and methodsDescribed below are the main classes and methods that can be invoked from the SAML2 library.OneLogin_Saml2_Auth - auth.pyMain class of SAML Python Toolkit__init__Initializes the SP SAML instance.loginInitiates the SSO process.logoutInitiates the SLO process.process_responseProcess the SAML Response sent by the IdP.process_sloProcess the SAML Logout Response / Logout Request sent by the IdP.redirect_toRedirects the user to the url past by parameter or to the url that we defined in our SSO Request.is_authenticatedChecks if the user is authenticated or not.get_attributesReturns the set of SAML attributes.get_attributeReturns the requested SAML attribute.get_nameidReturns thenameID.get_session_indexGets theSessionIndexfrom theAuthnStatement.get_session_expirationGets theSessionNotOnOrAfterfrom theAuthnStatement.get_errorsReturns a list with code errors if something went wrong.get_last_error_reasonReturns the reason of the last errorget_sso_urlGets the SSO url.get_slo_urlGets the SLO url.get_last_request_idThe ID of the last Request SAML message generated (AuthNRequest,LogoutRequest).get_last_authn_contextsReturns the list of authentication contexts sent in the last SAML Response.build_request_signatureBuilds the Signature of the SAML Request.build_response_signatureBuilds the Signature of the SAML Response.get_settingsReturns the settings info.set_strictSet the strict mode active/disable.get_last_request_xmlReturns the most recently-constructed/processed XML SAML request (AuthNRequest,LogoutRequest)get_last_response_xmlReturns the most recently-constructed/processed XML SAML response (SAMLResponse,LogoutResponse). If the SAMLResponse had an encrypted assertion, decrypts it.get_last_response_in_response_toTheInResponseToID of the most recently processed SAML Response.get_last_message_idThe ID of the last Response SAML message processed.get_last_assertion_idThe ID of the last assertion processed.get_last_assertion_not_on_or_afterTheNotOnOrAftervalue of the validSubjectConfirmationDatanode (if any) of the last assertion processed (is only calculated with strict = true)get_last_assertion_issue_instantTheIssueInstantvalue of the last assertion processed.OneLogin_Saml2_Auth - authn_request.pySAML 2 Authentication Request class__init__This class handles anAuthNRequest. It builds anAuthNRequestobject.get_requestReturns unsignedAuthnRequest.get_idReturns theAuthNRequestID.get_xmlReturns the XML that will be sent as part of the request.OneLogin_Saml2_Response - response.pySAML 2 Authentication Response class__init__Constructs the SAML Response object.is_validDetermines if the SAML Response is valid. Includes checking of the signature by a certificate.check_statusCheck if the status of the response is success or notget_audiencesGets the audiencesget_issuersGets the issuers (from message and from assertion)get_nameid_dataGets the NameID Data provided by the SAML Response from the IdP (returns a dict)get_nameidGets the NameID provided by the SAML Response from the IdP (returns a string)get_session_not_on_or_afterGets theSessionNotOnOrAfterfrom theAuthnStatementget_session_indexGets theSessionIndexfrom theAuthnStatementget_attributesGets the Attributes from theAttributeStatementelement.validate_num_assertionsVerifies that the document only contains a single Assertion (encrypted or not)validate_timestampsVerifies that the document is valid according to Conditions Elementget_errorAfter execute a validation process, if fails this method returns the causeget_xml_documentReturns the SAML Response document (If contains an encrypted assertion, decrypts it).get_idthe ID of the responseget_assertion_idthe ID of the assertion in the responseget_assertion_not_on_or_aftertheNotOnOrAftervalue of the validSubjectConfirmationDataif anyOneLogin_Saml2_LogoutRequest - logout_request.pySAML 2 Logout Request class__init__Constructs the Logout Request object.get_requestReturns the Logout Request deflated, base64-encoded.get_idReturns the ID of the Logout Request. (If you have the object you can access to the id attribute)get_nameid_dataGets the NameID Data of the the Logout Request (returns a dict).get_nameidGets the NameID of the Logout Request Message (returns a string).get_issuerGets the Issuer of the Logout Request Message.get_session_indexesGets theSessionIndexesfrom the Logout Request.is_validChecks if the Logout Request received is valid.get_errorAfter execute a validation process, if fails this method returns the cause.get_xmlReturns the XML that will be sent as part of the request or that was received at the SPOneLogin_Saml2_LogoutResponse - logout_response.pySAML 2 Logout Response class__init__Constructs a Logout Response object.get_issuerGets the Issuer of the Logout Response Messageget_statusGets the Status of the Logout Response.is_validDetermines if the SAMLLogoutResponseis validbuildCreates a Logout Response object.get_responseReturns a Logout Response object.get_errorAfter execute a validation process, if fails this method returns the cause.get_xmlReturns the XML that will be sent as part of the response or that was received at the SPOneLogin_Saml2_Settings - settings.pyConfiguration of the SAML Python Toolkit__init__Initializes the settings: Sets the paths of the different folders and Loads settings info from settings file or array/object provided.check_settingsChecks the settings info.check_idp_settingsChecks the IdP settings info.check_sp_settingsChecks the SP settings info.get_errorsReturns an array with the errors, the array is empty when the settings is ok.get_sp_metadataGets the SP metadata. The XML representation.validate_metadataValidates an XML SP Metadata.get_base_pathReturns base path.get_cert_pathReturns cert path.get_lib_pathReturns lib path.get_ext_lib_pathReturns external lib path.get_schemas_pathReturns schema path.check_sp_certsChecks if the X.509 certs of the SP exists and are valid.get_sp_keyReturns the X.509 private key of the SP.get_sp_certReturns the X.509 public cert of the SP.get_sp_cert_newReturns the future X.509 public cert of the SP.get_idp_certReturns the X.509 public cert of the IdP.get_sp_dataGets the SP data.get_idp_dataGets the IdP data.get_security_dataGets security data.get_contactsGets contacts data.get_organizationGets organization data.format_idp_certFormats the IdP cert.format_idp_cert_multiFormats all registered IdP certs.format_sp_certFormats the SP cert.format_sp_cert_newFormats the SP cert new.format_sp_keyFormats the private key.set_strictActivates or deactivates the strict mode.is_strictReturns if thestrictmode is active.is_debug_activeReturns if the debug is active.OneLogin_Saml2_Metadata - metadata.pyA class that contains functionality related to the metadata of the SPbuilderGenerates the metadata of the SP based on the settings.sign_metadataSigns the metadata with the key/cert provided.add_x509_key_descriptorsAdds the X.509 descriptors (sign/encryption) to the metadataOneLogin_Saml2_Utils - utils.pyAuxiliary class that contains several methodsdecode_base64_and_inflateBase64 decodes and then inflates according to RFC1951.deflate_and_base64_encodeDeflates and the base64 encodes a string.format_certReturns a X.509 cert (adding header & footer if required).format_private_keyReturns a private key (adding header & footer if required).redirectExecutes a redirection to the provided url (or return the target url).get_self_url_hostReturns the protocol + the current host + the port (if different than common ports).get_self_hostReturns the current host.is_httpsChecks if https or http.get_self_url_no_queryReturns the URL of the current host + current view.get_self_routed_url_no_queryReturns the routed URL of the current host + current view.get_self_urlReturns the URL of the current host + current view + query.generate_unique_idGenerates an unique string (used for example as ID for assertions).parse_time_to_SAMLConverts a UNIX timestamp to SAML2 timestamp on the form yyyy-mm-ddThh:mm:ss(.s+)?Z.parse_SAML_to_timeConverts a SAML2 timestamp on the form yyyy-mm-ddThh:mm:ss(.s+)?Z to a UNIX timestamp.nowReturns unix timestamp of actual time.parse_durationInterprets a ISO8601 duration value relative to a given timestamp.get_expire_timeCompares 2 dates and returns the earliest.delete_local_sessionDeletes the local session.calculate_X.509_fingerprintCalculates the fingerprint of a X.509 cert.format_finger_printFormats a fingerprint.generate_name_idGenerates a nameID.get_statusGets Status from a Response.decrypt_elementDecrypts an encrypted element.write_temp_fileWrites some content into a temporary file and returns it.add_signAdds signature key and senders certificate to an element (Message or Assertion).validate_signValidates a signature (Message or Assertion).validate_binary_signValidates signed bynary data (Used to validate GET Signature).OneLogin_Saml2_XML- xml_utils.pyA class that contains methods to handle XMLsto_stringSerialize an element to an encoded string representation of its XML tree.to_etreeParses an XML document or fragment from a string.validate_xmlValidates a xml against a schemaqueryExtracts nodes that match the query from the Elementextract_tag_textOneLogin_Saml2_IdPMetadataParser - idp_metadata_parser.pyA class that contains methods to obtain and parse metadata from IdPget_metadataGet the metadata XML from the provided URLparse_remoteGet the metadata XML from the provided URL and parse it, returning a dict with extracted dataparseParse the Identity Provider metadata and returns a dict with extracted datamerge_settingsWill update the settings with the provided new settings data extracted from the IdP metadataFor 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.Demos included in the toolkitThe toolkit includes 3 demos to teach how use the toolkit (A Django, Flask and a Tornado project), take a look on it.
Demos require that SP and IdP are well configured before test it, so edit the settings files.Notice that each python framework has it own way to handle routes/urls and process request, so focus on
how it deployed. New demos using other python frameworks are welcome as a contribution.Getting StartedWe said that this toolkit includes a Django application demo and a Flask application demo,
let's see how fast is it to deploy them.VirtualenvThe use of avirtualenvis
highly recommended.Virtualenv helps isolating the python environment used to run the toolkit. You
can find more details and an installation guide in theofficial documentation.Once you have your virtualenv ready and loaded, then you can install the
toolkit on it in development mode executing this:python setup.py developUsing this method of deployment the toolkit files will be linked instead of
copied, so if you make changes on them you won't need to reinstall the toolkit.If you want install it in a normal mode, execute:python setup.py installDemo FlaskYou'll need a virtualenv with the toolkit installed on it.To run the demo you need to install the requirements first. Load your
virtualenv and execute:pip install -r demo-flask/requirements.txtThis will install flask and its dependencies. Once it has finished, you have to complete the configuration
of the toolkit. You'll find it atdemo-flask/settings.jsonNow, with the virtualenv loaded, you can run the demo like this:cd demo-flask
 python index.pyYou'll have the demo running athttp://localhost:8000ContentThe flask project contains:index.pyIs the main flask file, where or the SAML handle take place.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.samlIs 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).SP setupThe SAML Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In thedemo-flask, it uses the first method.In theindex.pyfile we define theapp.config['SAML_PATH'], that will target to thesamlfolder. We require it in order to load the settings files.First we need to edit thesaml/settings.jsonfile, configure the SP part and review the metadata of the IdP and complete the IdP info.  Later edit thesaml/advanced_settings.jsonfiles and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.IdP setupOnce the SP is configured, the metadata of the SP is published at the/metadataurl. Based on that info, configure the IdP.How it worksFirst 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).When you click:2.1 in the first link, we access to/?sso(index view). AnAuthNRequestis 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 aRelayStateparameter is set to the url that initiated the process, the index view.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 asRelayStateis set theattrsurl.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 theRelayStateview. a) / or b)/?attrsWe are logged in the app and the user attributes are showed. At this point, we can test the single log out functionality.The single log out functionality could be tested by 2 ways.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.

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.Notice 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.Demo TornadoYou'll need a virtualenv with the toolkit installed on it.First of all you need some packages, execute:apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-opensslTo run the demo you need to install the requirements first. Load your
virtualenv and execute:pip install -r demo-tornado/requirements.txtThis will install tornado and its dependencies. Once it has finished, you have to complete the configuration
of the toolkit. You'll find it atdemo-tornado/saml/settings.jsonNow, with the virtualenv loaded, you can run the demo like this:cd demo-tornado
 python views.pyYou'll have the demo running athttp://localhost:8000ContentThe tornado project contains:views.pyIs the main flask file, where or the SAML handle take place.settings.pyContains the base path and the path where is located thesamlfolder and thetemplatefoldertemplates. 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.samlIs 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).SP setupThe SAML Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In thedemo-tornado, it uses the first method.In thesettings.pyfile we define theSAML_PATH, that will target to thesamlfolder. We require it in order to load the settings files.First we need to edit thesaml/settings.jsonfile, configure the SP part and review the metadata of the IdP and complete the IdP info.  Later edit thesaml/advanced_settings.jsonfiles and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.IdP setupOnce the SP is configured, the metadata of the SP is published at the/metadataurl. Based on that info, configure the IdP.How it worksFirst 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).When you click:2.1 in the first link, we access to/?sso(index view). AnAuthNRequestis 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 aRelayStateparameter is set to the url that initiated the process, the index view.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 asRelayStateis set theattrsurl.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 theRelayStateview. a) / or b)/?attrsWe are logged in the app and the user attributes are showed. At this point, we can test the single log out functionality.The single log out functionality could be tested by 2 ways.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.

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.Notice 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.Demo DjangoYou'll need a virtualenv with the toolkit installed on it.To run the demo you need to install the requirements first. Load your
virtualenv and execute:pip install -r demo-django/requirements.txtThis will install django and its dependencies. Once it has finished, you have to complete the configuration of the toolkit.Later, with the virtualenv loaded, you can run the demo like this:cd demo-django
 python manage.py runserver 0.0.0.0:8000You'll have the demo running athttp://localhost:8000.Note 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.If 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-providerContentThe django project contains: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 onsys.pathand sets theDJANGO_SETTINGS_MODULEenvironment variable.samlIs 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.jsonandadvanced_settings.json).demoIs the main folder of the django project, that contains the typical files:settings.pyContains the default parameters of a django project except theSAML_FOLDERparameter, that may contain the path where is located thesamlfolder.urls.pyA 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.views.pyThis file contains the views of the django project and some aux methods.wsgi.pyA file that let as deploy django using WSGI, the Python standard for web servers and applications.templates. Is the folder where django stores the templates of the project. It was implemented abase.htmltemplate that is extended byindex.htmlandattrs.html, the templates of our simple demo that shows messages, user attributes when available and login and logout links.SP setupThe SAML 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.After set theSAML_FOLDERin thedemo/settings.py, the settings of the Python toolkit will be loaded on the Django web.First we need to edit thesaml/settings.json, configure the SP part and review the metadata of the IdP and complete the IdP info.  Later edit thesaml/advanced_settings.jsonfiles and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.IdP setupOnce the SP is configured, the metadata of the SP is published at the/metadataurl. Based on that info, configure the IdP.How it worksThis demo works very similar to theflask-demo(We did it intentionally).Getting up and running on HerokuGettingpython3-samlup and running on Heroku will require some extra legwork:python3-samldepends onpython-xmlsecwhich depends on headers from thexmlsec1-devLinux package to install correctly.First you will need to add theaptbuildpack to your build server:heroku buildpacks:add --index=1 -a your-app heroku-community/apt
heroku buildpacks:add --index=2 -a your-app heroku/pythonYou can confirm the buildpacks have been added in the correct order withheroku buildpacks -a your-app, you should see the apt buildpack first followed by the Python buildpack.Then add anAptfileinto the root of your repository containing thelibxmlsec1-devpackage, the file should look like:libxmlsec1-devFinally, addpython3-samlto yourrequirements.txtandgit pushto trigger a build.Demo PyramidUnlike the other two projects, you don't need a pre-existing virtualenv to get
up and running here, since Pyramid comes from thebuildoutschool of thought.To run the demo you need to install Pyramid, the requirements, etc.:cd demo_pyramid
 python3 -m venv env
 env/bin/pip install --upgrade pip setuptools
 env/bin/pip install -e ".[testing]"If you want to make sure the tests pass, run:env/bin/pytestNext, edit the settings indemo_pyramid/saml/settings.json. (Pyramid runs on
port 6543 by default.)Now you can run the demo like this:env/bin/pserve development.iniIf that worked, the demo is now running athttp://localhost:6543.ContentThe Pyramid project contains:__init__.pyis the main Pyramid file that configures the app and its routes.views.pyis where all the SAML handling takes place.templatesis the folder where Pyramid stores the templates of the project. It was implemented alayout.jinja2template that is extended byindex.jinja2andattrs.jinja2, the templates of our simple demo that shows messages, user attributes when available and login and logout links.samlis 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.jsonandadvanced_settings.json).SP setupThe SAML Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. Indemo_pyramidthe first method is used.In theviews.pyfile we define theSAML_PATH, which will target thesamlfolder. We require it in order to load the settings files.First we need to edit thesaml/settings.json, configure the SP part and review the metadata of the IdP and complete the IdP info.  Later edit thesaml/advanced_settings.jsonfiles and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.IdP setupOnce the SP is configured, the metadata of the SP is published at the/metadataurl. Based on that info, configure the IdP.How it worksFirst 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).When you click:2.1 in the first link, we access to/?sso(index view). AnAuthNRequestis 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 aRelayStateparameter is set to the url that initiated the process, the index view.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 asRelayStateis set theattrsurl.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 theRelayStateview. a)/or b)/?attrsWe are logged in the app and the user attributes are showed. At this point, we can test the single log out functionality.The single log out functionality could be tested by 2 ways.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.

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.Notice 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.