Source code for onelogin.saml2.metadata

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

""" OneLoginSaml2Metadata class


Metadata class of SAML Python Toolkit.

"""

from time import gmtime, strftime, time
from datetime import datetime

from onelogin.saml2 import compat
from onelogin.saml2.constants import OneLogin_Saml2_Constants
from onelogin.saml2.utils import OneLogin_Saml2_Utils
from onelogin.saml2.xml_templates import OneLogin_Saml2_Templates
from onelogin.saml2.xml_utils import OneLogin_Saml2_XML

try:
    basestring
except NameError:
    basestring = str


[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] @classmethod def builder(cls, 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()) + cls.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 = cls.TIME_CACHED if not isinstance(cache_duration, compat.str_type): cache_duration_str = 'PT%sS' % cache_duration # Period of Time x Seconds else: cache_duration_str = cache_duration if contacts is None: contacts = {} if organization is None: organization = {} sls = '' if 'singleLogoutService' in sp and 'url' in sp['singleLogoutService']: sls = OneLogin_Saml2_Templates.MD_SLS % \ { '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>\n%(org)s\n </md:Organization>""" % {'org': org_data} str_contacts = '' if len(contacts) > 0: contacts_info = [] for (ctype, info) in contacts.items(): contact = OneLogin_Saml2_Templates.MD_CONTACT_PERSON % \ { 'type': ctype, 'name': info['givenName'], 'email': info['emailAddress'], } contacts_info.append(contact) str_contacts = '\n'.join(contacts_info) 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="%(attribute_consuming_service_index)s"> <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, 'attribute_consuming_service_index': sp['attributeConsumingService'].get('index', '1'), 'requested_attribute_str': '\n'.join(requested_attribute_data) } metadata = OneLogin_Saml2_Templates.MD_ENTITY_DESCRIPTOR % \ { '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 :returns: Signed Metadata :rtype: string :param sign_algorithm: Signature algorithm method :type sign_algorithm: string :param digest_algorithm: Digest algorithm method :type digest_algorithm: string """ return OneLogin_Saml2_Utils.add_sign(metadata, key, cert, False, sign_algorithm, digest_algorithm)
@staticmethod def _add_x509_key_descriptors(root, cert, signing): key_descriptor = OneLogin_Saml2_XML.make_child(root, '{%s}KeyDescriptor' % OneLogin_Saml2_Constants.NS_MD) root.remove(key_descriptor) root.insert(0, key_descriptor) key_info = OneLogin_Saml2_XML.make_child(key_descriptor, '{%s}KeyInfo' % OneLogin_Saml2_Constants.NS_DS) key_data = OneLogin_Saml2_XML.make_child(key_info, '{%s}X509Data' % OneLogin_Saml2_Constants.NS_DS) x509_certificate = OneLogin_Saml2_XML.make_child(key_data, '{%s}X509Certificate' % OneLogin_Saml2_Constants.NS_DS) x509_certificate.text = OneLogin_Saml2_Utils.format_cert(cert, False) key_descriptor.set('use', ('encryption', 'signing')[signing])
[docs] @classmethod def add_x509_key_descriptors(cls, 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: root = OneLogin_Saml2_XML.to_etree(metadata) except Exception as e: raise Exception('Error parsing metadata. ' + str(e)) assert root.tag == '{%s}EntityDescriptor' % OneLogin_Saml2_Constants.NS_MD try: sp_sso_descriptor = next(root.iterfind('.//md:SPSSODescriptor', namespaces=OneLogin_Saml2_Constants.NSMAP)) except StopIteration: raise Exception('Malformed metadata.') if add_encryption: cls._add_x509_key_descriptors(sp_sso_descriptor, cert, False) cls._add_x509_key_descriptors(sp_sso_descriptor, cert, True) return OneLogin_Saml2_XML.to_string(root)