saml2.metadata — OneLogin SAML Python library classes and methods

### Navigation

- index
- modules |
- OneLogin SAML Python library classes and methods »
- Class code »

# Source code for saml2.metadata

    # -*- coding: utf-8 -*-

    # Copyright (c) 2010-2018 OneLogin, Inc.
    # MIT License

    from time import gmtime, strftime
    from datetime import datetime
    from xml.dom.minidom import parseString

    from saml2.constants import OneLogin_Saml2_Constants
    from saml2.utils import OneLogin_Saml2_Utils

    class OneLogin_Saml2_Metadata:

        TIME_VALID = 172800   # 2 days
        TIME_CACHED = 604800  # 1 week

        @staticmethod
        def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=None, contacts=None, organization=None):
            """
            Build the metadata of the SP

            :param sp: The SP data
            :type sp: string

            :param authnsign: authnRequestsSigned attribute
            :type authnsign: string

            :param wsign: wantAssertionsSigned attribute
            :type wsign: string

            :param valid_until: Metadata's valid time
            :type valid_until: DateTime

            :param cache_duration: Duration of the cache in seconds
            :type cache_duration: Timestamp

            :param contacts: Contacts info
            :type contacts: dict

            :param organization: Organization ingo
            :type organization: dict
            """
            if valid_until is None:
                valid_until = int(datetime.now().strftime("%s")) + OneLogin_Saml2_Metadata.TIME_VALID
            valid_until_time = gmtime(valid_until)
            valid_until_time = strftime(r'%Y-%m-%dT%H:%M:%SZ', valid_until_time)
            if cache_duration is None:
                cache_duration = int(datetime.now().strftime("%s")) + OneLogin_Saml2_Metadata.TIME_CACHED
            if contacts is None:
                contacts = {}
            if organization is None:
                organization = {}

            sls = ''
            if 'singleLogoutService' in sp:
                sls = """<md:SingleLogoutService Binding="%(binding)s"
                                    Location="%(location)s" />""" % {
                    'binding': sp['singleLogoutService']['binding'],
                    'location': sp['singleLogoutService']['url'],
                }

            str_authnsign = 'true' if authnsign else 'false'
            str_wsign = 'true' if wsign else 'false'

            str_organization = ''
            if len(organization) > 0:
                organization_info = []
                for (lang, info) in organization.items():
                    organization_info.append("""    <md:Organization>
            <md:OrganizationName xml:lang="%(lang)s">%(name)s</md:OrganizationName>
            <md:OrganizationDisplayName xml:lang="%(lang)s">%(display_name)s</md:OrganizationDisplayName>
            <md:OrganizationURL xml:lang="%(lang)s">%(url)s</md:OrganizationURL>
        </md:Organization>""" % {
                        'lang': lang,
                        'name': info['name'],
                        'display_name': info['displayname'],
                        'url': info['url'],
                    })
                str_organization = '\n'.join(organization_info)

            str_contacts = ''
            if len(contacts) > 0:
                contacts_info = []
                for (ctype, info) in contacts.items():
                    contacts_info.append("""    <md:ContactPerson contactType="%(type)s">
            <md:GivenName>%(name)s</md:GivenName>
            <md:EmailAddress>%(email)s</md:EmailAddress>
        </md:ContactPerson>""" % {
                        'type': ctype,
                        'name': info['givenName'],
                        'email': info['emailAddress'],
                    })
                str_contacts = '\n'.join(contacts_info)

            metadata = """<?xml version="1.0"?>
    <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
                         validUntil="%(valid)s"
                         cacheDuration="PT%(cache)sS"
                         entityID="%(entity_id)s">
        <md:SPSSODescriptor AuthnRequestsSigned="%(authnsign)s" WantAssertionsSigned="%(wsign)s" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
            <md:NameIDFormat>%(name_id_format)s</md:NameIDFormat>
            <md:AssertionConsumerService Binding="%(binding)s"
                                         Location="%(location)s"
                                         index="1" />
    %(sls)s
        </md:SPSSODescriptor>
    %(organization)s
    %(contacts)s
    </md:EntityDescriptor>""" % {
                'valid': valid_until_time,
                'cache': cache_duration,
                'entity_id': sp['entityId'],
                'authnsign': str_authnsign,
                'wsign': str_wsign,
                'name_id_format': sp['NameIDFormat'],
                'binding': sp['assertionConsumerService']['binding'],
                'location': sp['assertionConsumerService']['url'],
                'sls': sls,
                'organization': str_organization,
                'contacts': str_contacts,
            }

            return metadata

        @staticmethod
        def sign_metadata(metadata, key, cert):
            """
            Sign the metadata with the key/cert provided

            :param metadata: SAML Metadata XML
            :type metadata: string

            :param key: x509 key
            :type key: string

            :param cert: x509 cert
            :type cert: string

            :returns: Signed Metadata
            :rtype: string
            """
            return OneLogin_Saml2_Utils.add_sign(metadata, key, cert)

        @staticmethod
        def add_x509_key_descriptors(metadata, cert):
            """
            Add the x509 descriptors (sign/encriptation to the metadata
            The same cert will be used for sign/encrypt

            :param metadata: SAML Metadata XML
            :type metadata: string

            :param cert: x509 cert
            :type cert: string

            :returns: Metadata with KeyDescriptors
            :rtype: string
            """
            try:
                xml = parseString(metadata)
            except Exception as e:
                raise Exception('Error parsing metadata. ' + e.message)

            formated_cert = OneLogin_Saml2_Utils.format_cert(cert, False)
            x509_certificate = xml.createElementNS(OneLogin_Saml2_Constants.NS_DS, 'ds:X509Certificate')
            content = xml.createTextNode(formated_cert)
            x509_certificate.appendChild(content)

            key_data = xml.createElementNS(OneLogin_Saml2_Constants.NS_DS, 'ds:X509Data')
            key_data.appendChild(x509_certificate)

            key_info = xml.createElementNS(OneLogin_Saml2_Constants.NS_DS, 'ds:KeyInfo')
            key_info.appendChild(key_data)

            key_descriptor = xml.createElementNS(OneLogin_Saml2_Constants.NS_DS, 'md:KeyDescriptor')

            entity_descriptor = sp_sso_descriptor = xml.getElementsByTagName('md:EntityDescriptor')[0]
            entity_descriptor.setAttribute('xmlns:ds', OneLogin_Saml2_Constants.NS_DS)

            sp_sso_descriptor = xml.getElementsByTagName('md:SPSSODescriptor')[0]
            sp_sso_descriptor.insertBefore(key_descriptor.cloneNode(True), sp_sso_descriptor.firstChild)
            sp_sso_descriptor.insertBefore(key_descriptor.cloneNode(True), sp_sso_descriptor.firstChild)

            signing = xml.getElementsByTagName('md:KeyDescriptor')[0]
            signing.setAttribute('use', 'signing')

            encryption = xml.getElementsByTagName('md:KeyDescriptor')[1]
            encryption.setAttribute('use', 'encryption')

            signing.appendChild(key_info)
            encryption.appendChild(key_info.cloneNode(True))

            return xml.toxml()

### Navigation

- index
- modules |
- OneLogin SAML Python library classes and methods »
- Class code »