Source code for onelogin.saml2.metadata

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

""" OneLogin_Saml2_Metadata class

MIT License

Metadata class of Python Toolkit.

"""

from time import gmtime, strftime, time
from datetime import datetime
from defusedxml.minidom import parseString

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


[docs]class OneLogin_Saml2_Metadata(object): """ A class that contains methods related to the metadata of the SP """ TIME_VALID = 172800 # 2 days TIME_CACHED = 604800 # 1 week
[docs] @staticmethod def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=None, contacts=None, organization=None): """ Builds 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 expiry date :type valid_until: string|DateTime|Timestamp :param cache_duration: Duration of the cache in seconds :type cache_duration: int|string :param contacts: Contacts info :type contacts: dict :param organization: Organization info :type organization: dict """ if valid_until is None: valid_until = int(time()) + OneLogin_Saml2_Metadata.TIME_VALID if not isinstance(valid_until, basestring): if isinstance(valid_until, datetime): valid_until_time = valid_until.timetuple() else: valid_until_time = gmtime(valid_until) valid_until_str = strftime(r'%Y-%m-%dT%H:%M:%SZ', valid_until_time) else: valid_until_str = valid_until if cache_duration is None: cache_duration = OneLogin_Saml2_Metadata.TIME_CACHED if not isinstance(cache_duration, basestring): cache_duration_str = 'PT%sS' % cache_duration # 'P'eriod of 'T'ime x 'S'econds else: cache_duration_str = cache_duration if contacts is None: contacts = {} if organization is None: organization = {} str_attribute_consuming_service = '' if 'attributeConsumingService' in sp and len(sp['attributeConsumingService']): attr_cs_desc_str = '' if "serviceDescription" in sp['attributeConsumingService']: attr_cs_desc_str = """ <md:ServiceDescription xml:lang="en">%s</md:ServiceDescription> """ % sp['attributeConsumingService']['serviceDescription'] requested_attribute_data = [] for req_attribs in sp['attributeConsumingService']['requestedAttributes']: req_attr_nameformat_str = req_attr_friendlyname_str = req_attr_isrequired_str = '' req_attr_aux_str = ' />' if 'nameFormat' in req_attribs.keys() and req_attribs['nameFormat']: req_attr_nameformat_str = " NameFormat=\"%s\"" % req_attribs['nameFormat'] if 'friendlyName' in req_attribs.keys() and req_attribs['friendlyName']: req_attr_friendlyname_str = " FriendlyName=\"%s\"" % req_attribs['friendlyName'] if 'isRequired' in req_attribs.keys() and req_attribs['isRequired']: req_attr_isrequired_str = " isRequired=\"%s\"" % 'true' if req_attribs['isRequired'] else 'false' if 'attributeValue' in req_attribs.keys() and req_attribs['attributeValue']: if isinstance(req_attribs['attributeValue'], basestring): req_attribs['attributeValue'] = [req_attribs['attributeValue']] req_attr_aux_str = ">" for attrValue in req_attribs['attributeValue']: req_attr_aux_str += """ <saml:AttributeValue xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">%(attributeValue)s</saml:AttributeValue>""" % \ { 'attributeValue': attrValue } req_attr_aux_str += """ </md:RequestedAttribute>""" requested_attribute = """ <md:RequestedAttribute Name="%(req_attr_name)s"%(req_attr_nameformat_str)s%(req_attr_friendlyname_str)s%(req_attr_isrequired_str)s%(req_attr_aux_str)s""" % \ { 'req_attr_name': req_attribs['name'], 'req_attr_nameformat_str': req_attr_nameformat_str, 'req_attr_friendlyname_str': req_attr_friendlyname_str, 'req_attr_isrequired_str': req_attr_isrequired_str, 'req_attr_aux_str': req_attr_aux_str } requested_attribute_data.append(requested_attribute) str_attribute_consuming_service = """ <md:AttributeConsumingService index="1"> <md:ServiceName xml:lang="en">%(service_name)s</md:ServiceName> %(attr_cs_desc)s%(requested_attribute_str)s </md:AttributeConsumingService> """ % \ { 'service_name': sp['attributeConsumingService']['serviceName'], 'attr_cs_desc': attr_cs_desc_str, 'requested_attribute_str': '\n'.join(requested_attribute_data) } sls = '' if 'singleLogoutService' in sp and 'url' in sp['singleLogoutService']: sls = """ <md:SingleLogoutService Binding="%(binding)s" Location="%(location)s" />\n""" % \ { '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_names = [] organization_displaynames = [] organization_urls = [] for (lang, info) in organization.items(): organization_names.append(""" <md:OrganizationName xml:lang="%s">%s</md:OrganizationName>""" % (lang, info['name'])) organization_displaynames.append(""" <md:OrganizationDisplayName xml:lang="%s">%s</md:OrganizationDisplayName>""" % (lang, info['displayname'])) organization_urls.append(""" <md:OrganizationURL xml:lang="%s">%s</md:OrganizationURL>""" % (lang, info['url'])) org_data = '\n'.join(organization_names) + '\n' + '\n'.join(organization_displaynames) + '\n' + '\n'.join(organization_urls) str_organization = """ <md:Organization> %(org)s </md:Organization>\n""" % {'org': org_data} str_contacts = '' if len(contacts) > 0: contacts_info = [] for (ctype, info) in contacts.items(): contact = """ <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'], } contacts_info.append(contact) str_contacts = '\n'.join(contacts_info) + '\n' metadata = u"""<?xml version="1.0"?> <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" %(valid)s %(cache)s entityID="%(entity_id)s"> <md:SPSSODescriptor AuthnRequestsSigned="%(authnsign)s" WantAssertionsSigned="%(wsign)s" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> %(sls)s <md:NameIDFormat>%(name_id_format)s</md:NameIDFormat> <md:AssertionConsumerService Binding="%(binding)s" Location="%(location)s" index="1" /> %(attribute_consuming_service)s </md:SPSSODescriptor> %(organization)s%(contacts)s</md:EntityDescriptor>""" % \ { 'valid': ('validUntil="%s"' % valid_until_str) if valid_until_str else '', 'cache': ('cacheDuration="%s"' % cache_duration_str) if cache_duration_str else '', '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, 'attribute_consuming_service': str_attribute_consuming_service } return metadata
[docs] @staticmethod def sign_metadata(metadata, key, cert, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA256, digest_algorithm=OneLogin_Saml2_Constants.SHA256): """ Signs 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 :param sign_algorithm: Signature algorithm method :type sign_algorithm: string :param digest_algorithm: Digest algorithm method :type digest_algorithm: string :returns: Signed Metadata :rtype: string """ return OneLogin_Saml2_Utils.add_sign(metadata, key, cert, False, sign_algorithm, digest_algorithm)
[docs] @staticmethod def add_x509_key_descriptors(metadata, cert=None, add_encryption=True): """ Adds the x509 descriptors (sign/encryption) 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 :param add_encryption: Determines if the KeyDescriptor[use="encryption"] should be added. :type add_encryption: boolean :returns: Metadata with KeyDescriptors :rtype: string """ if cert is None or cert == '': return metadata try: xml = parseString(metadata.encode('utf-8'), forbid_dtd=True, forbid_entities=True, forbid_external=True) 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 = xml.getElementsByTagName('md:EntityDescriptor')[0] sp_sso_descriptor = entity_descriptor.getElementsByTagName('md:SPSSODescriptor')[0] sp_sso_descriptor.insertBefore(key_descriptor.cloneNode(True), sp_sso_descriptor.firstChild) if add_encryption: sp_sso_descriptor.insertBefore(key_descriptor.cloneNode(True), sp_sso_descriptor.firstChild) signing = xml.getElementsByTagName('md:KeyDescriptor')[0] signing.setAttribute('use', 'signing') signing.appendChild(key_info) signing.setAttribute('xmlns:ds', OneLogin_Saml2_Constants.NS_DS) if add_encryption: encryption = xml.getElementsByTagName('md:KeyDescriptor')[1] encryption.setAttribute('use', 'encryption') encryption.appendChild(key_info.cloneNode(True)) encryption.setAttribute('xmlns:ds', OneLogin_Saml2_Constants.NS_DS) return xml.toxml()