예제 #1
0
    def get_issuers(self):
        """
        Gets the issuers (from message and from assertion)

        :returns: The issuers
        :rtype: list
        """
        issuers = []

        message_issuer_nodes = OneLogin_Saml2_Utils.query(
            self.document, '/samlp:Response/saml:Issuer')
        if len(message_issuer_nodes) > 0:
            if len(message_issuer_nodes) == 1:
                issuers.append(
                    OneLogin_Saml2_Utils.element_text(message_issuer_nodes[0]))
            else:
                raise OneLogin_Saml2_ValidationError(
                    'Issuer of the Response is multiple.',
                    OneLogin_Saml2_ValidationError.ISSUER_MULTIPLE_IN_RESPONSE)

        assertion_issuer_nodes = self.__query_assertion('/saml:Issuer')
        if len(assertion_issuer_nodes) == 1:
            issuers.append(
                OneLogin_Saml2_Utils.element_text(assertion_issuer_nodes[0]))
        else:
            raise OneLogin_Saml2_ValidationError(
                'Issuer of the Assertion not found or multiple.',
                OneLogin_Saml2_ValidationError.ISSUER_NOT_FOUND_IN_ASSERTION)

        return list(set(issuers))
예제 #2
0
    def get_issuers(self):
        """
        Gets the issuers (from message and from assertion)

        :returns: The issuers
        :rtype: list
        """
        issuers = []

        message_issuer_nodes = OneLogin_Saml2_Utils.query(self.document, '/samlp:Response/saml:Issuer')
        if len(message_issuer_nodes) > 0:
            if len(message_issuer_nodes) == 1:
                issuers.append(OneLogin_Saml2_Utils.element_text(message_issuer_nodes[0]))
            else:
                raise OneLogin_Saml2_ValidationError(
                    'Issuer of the Response is multiple.',
                    OneLogin_Saml2_ValidationError.ISSUER_MULTIPLE_IN_RESPONSE
                )

        assertion_issuer_nodes = self.__query_assertion('/saml:Issuer')
        if len(assertion_issuer_nodes) == 1:
            issuers.append(OneLogin_Saml2_Utils.element_text(assertion_issuer_nodes[0]))
        else:
            raise OneLogin_Saml2_ValidationError(
                'Issuer of the Assertion not found or multiple.',
                OneLogin_Saml2_ValidationError.ISSUER_NOT_FOUND_IN_ASSERTION
            )

        return list(set(issuers))
예제 #3
0
    def get_audiences(self):
        """
        Gets the audiences

        :returns: The valid audiences for the SAML Response
        :rtype: list
        """
        audience_nodes = self.__query_assertion(
            '/saml:Conditions/saml:AudienceRestriction/saml:Audience')
        return [
            OneLogin_Saml2_Utils.element_text(node) for node in audience_nodes
            if OneLogin_Saml2_Utils.element_text(node) is not None
        ]
예제 #4
0
    def get_nameid_data(self):
        """
        Gets the NameID Data provided by the SAML Response from the IdP

        :returns: Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
        :rtype: dict
        """
        nameid = None
        nameid_data = {}

        encrypted_id_data_nodes = self.__query_assertion(
            '/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
        if encrypted_id_data_nodes:
            encrypted_data = encrypted_id_data_nodes[0]
            key = self.__settings.get_sp_key()
            nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key)
        else:
            nameid_nodes = self.__query_assertion('/saml:Subject/saml:NameID')
            if nameid_nodes:
                nameid = nameid_nodes[0]

        is_strict = self.__settings.is_strict()
        want_nameid = self.__settings.get_security_data().get(
            'wantNameId', True)
        if nameid is None:
            if is_strict and want_nameid:
                raise OneLogin_Saml2_ValidationError(
                    'NameID not found in the assertion of the Response',
                    OneLogin_Saml2_ValidationError.NO_NAMEID)
        else:
            if is_strict and want_nameid and not OneLogin_Saml2_Utils.element_text(
                    nameid):
                raise OneLogin_Saml2_ValidationError(
                    'An empty NameID value found',
                    OneLogin_Saml2_ValidationError.EMPTY_NAMEID)

            nameid_data = {'Value': OneLogin_Saml2_Utils.element_text(nameid)}
            for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
                value = nameid.get(attr, None)
                if value:
                    if is_strict and attr == 'SPNameQualifier':
                        sp_data = self.__settings.get_sp_data()
                        sp_entity_id = sp_data.get('entityId', '')
                        if sp_entity_id != value:
                            raise OneLogin_Saml2_ValidationError(
                                'The SPNameQualifier value mistmatch the SP entityID value.',
                                OneLogin_Saml2_ValidationError.
                                SP_NAME_QUALIFIER_NAME_MISMATCH)

                    nameid_data[attr] = value
        return nameid_data
예제 #5
0
    def get_nameid_data(self):
        """
        Gets the NameID Data provided by the SAML Response from the IdP

        :returns: Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
        :rtype: dict
        """
        nameid = None
        nameid_data = {}

        encrypted_id_data_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
        if encrypted_id_data_nodes:
            encrypted_data = encrypted_id_data_nodes[0]
            key = self.__settings.get_sp_key()
            nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key)
        else:
            nameid_nodes = self.__query_assertion('/saml:Subject/saml:NameID')
            if nameid_nodes:
                nameid = nameid_nodes[0]

        is_strict = self.__settings.is_strict()
        want_nameid = self.__settings.get_security_data().get('wantNameId', True)
        if nameid is None:
            if is_strict and want_nameid:
                raise OneLogin_Saml2_ValidationError(
                    'NameID not found in the assertion of the Response',
                    OneLogin_Saml2_ValidationError.NO_NAMEID
                )
        else:
            if is_strict and want_nameid and not OneLogin_Saml2_Utils.element_text(nameid):
                raise OneLogin_Saml2_ValidationError(
                    'An empty NameID value found',
                    OneLogin_Saml2_ValidationError.EMPTY_NAMEID
                )

            nameid_data = {'Value': OneLogin_Saml2_Utils.element_text(nameid)}
            for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
                value = nameid.get(attr, None)
                if value:
                    if is_strict and attr == 'SPNameQualifier':
                        sp_data = self.__settings.get_sp_data()
                        sp_entity_id = sp_data.get('entityId', '')
                        if sp_entity_id != value:
                            raise OneLogin_Saml2_ValidationError(
                                'The SPNameQualifier value mistmatch the SP entityID value.',
                                OneLogin_Saml2_ValidationError.SP_NAME_QUALIFIER_NAME_MISMATCH
                            )

                    nameid_data[attr] = value
        return nameid_data
예제 #6
0
 def get_authn_contexts(self):
     """
     Gets the authentication contexts
      :returns: The authentication classes for the SAML Response
     :rtype: list
     """
     authn_context_nodes = self.__query_assertion('/saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef')
     return [OneLogin_Saml2_Utils.element_text(node) for node in authn_context_nodes]
예제 #7
0
    def get_audiences(self):
        """
        Gets the audiences

        :returns: The valid audiences for the SAML Response
        :rtype: list
        """
        audience_nodes = self.__query_assertion('/saml:Conditions/saml:AudienceRestriction/saml:Audience')
        return [OneLogin_Saml2_Utils.element_text(node) for node in audience_nodes if OneLogin_Saml2_Utils.element_text(node) is not None]
예제 #8
0
    def get_friendlyname_attributes(self):
        """
        Gets the Attributes from the AttributeStatement element indexed by FiendlyName.
        EncryptedAttributes are not supported
        """
        attributes = {}
        attribute_nodes = self.__query_assertion(
            '/saml:AttributeStatement/saml:Attribute')
        for attribute_node in attribute_nodes:
            attr_friendlyname = attribute_node.get('FriendlyName')
            if attr_friendlyname:
                if attr_friendlyname in attributes.keys():
                    raise OneLogin_Saml2_ValidationError(
                        'Found an Attribute element with duplicated FriendlyName',
                        OneLogin_Saml2_ValidationError.
                        DUPLICATED_ATTRIBUTE_NAME_FOUND)

                values = []
                for attr in attribute_node.iterchildren(
                        '{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP[
                            OneLogin_Saml2_Constants.NS_PREFIX_SAML]):
                    # Remove any whitespace (which may be present where attributes are
                    # nested inside NameID children).
                    attr_text = OneLogin_Saml2_Utils.element_text(attr)
                    if attr_text:
                        attr_text = attr_text.strip()
                        if attr_text:
                            values.append(attr_text)

                    # Parse any nested NameID children
                    for nameid in attr.iterchildren(
                            '{%s}NameID' % OneLogin_Saml2_Constants.NSMAP[
                                OneLogin_Saml2_Constants.NS_PREFIX_SAML]):
                        values.append({
                            'NameID': {
                                'Format': nameid.get('Format'),
                                'NameQualifier': nameid.get('NameQualifier'),
                                'value':
                                OneLogin_Saml2_Utils.element_text(nameid)
                            }
                        })

                attributes[attr_friendlyname] = values
        return attributes
예제 #9
0
 def get_issuer(self):
     """
     Gets the Issuer of the Logout Response Message
     :return: The Issuer
     :rtype: string
     """
     issuer = None
     issuer_nodes = self.__query('/samlp:LogoutResponse/saml:Issuer')
     if len(issuer_nodes) == 1:
         issuer = OneLogin_Saml2_Utils.element_text(issuer_nodes[0])
     return issuer
예제 #10
0
 def get_authn_contexts(self):
     """
     Gets the authentication contexts
      :returns: The authentication classes for the SAML Response
     :rtype: list
     """
     authn_context_nodes = self.__query_assertion(
         '/saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef')
     return [
         OneLogin_Saml2_Utils.element_text(node)
         for node in authn_context_nodes
     ]
예제 #11
0
    def get_nameid_data(request, key=None):
        """
        Gets the NameID Data of the the Logout Request
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :param key: The SP key
        :type key: string
        :return: Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
        :rtype: dict
        """
        if isinstance(request, etree._Element):
            elem = request
        else:
            if isinstance(request, Document):
                request = request.toxml()
            elem = fromstring(request, forbid_dtd=True)

        name_id = None
        encrypted_entries = OneLogin_Saml2_Utils.query(
            elem, '/samlp:LogoutRequest/saml:EncryptedID')

        if len(encrypted_entries) == 1:
            if key is None:
                raise OneLogin_Saml2_Error(
                    'Private Key is required in order to decrypt the NameID, check settings',
                    OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND)

            encrypted_data_nodes = OneLogin_Saml2_Utils.query(
                elem,
                '/samlp:LogoutRequest/saml:EncryptedID/xenc:EncryptedData')
            if len(encrypted_data_nodes) == 1:
                encrypted_data = encrypted_data_nodes[0]
                name_id = OneLogin_Saml2_Utils.decrypt_element(
                    encrypted_data, key)
        else:
            entries = OneLogin_Saml2_Utils.query(
                elem, '/samlp:LogoutRequest/saml:NameID')
            if len(entries) == 1:
                name_id = entries[0]

        if name_id is None:
            raise OneLogin_Saml2_ValidationError(
                'NameID not found in the Logout Request',
                OneLogin_Saml2_ValidationError.NO_NAMEID)

        name_id_data = {'Value': OneLogin_Saml2_Utils.element_text(name_id)}
        for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
            if attr in name_id.attrib.keys():
                name_id_data[attr] = name_id.attrib[attr]

        return name_id_data
예제 #12
0
    def get_nameid_data(request, key=None):
        """
        Gets the NameID Data of the the Logout Request
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :param key: The SP key
        :type key: string
        :return: Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
        :rtype: dict
        """
        if isinstance(request, etree._Element):
            elem = request
        else:
            if isinstance(request, Document):
                request = request.toxml()
            elem = fromstring(request, forbid_dtd=True)

        name_id = None
        encrypted_entries = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/saml:EncryptedID')

        if len(encrypted_entries) == 1:
            if key is None:
                raise OneLogin_Saml2_Error(
                    'Private Key is required in order to decrypt the NameID, check settings',
                    OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND
                )

            encrypted_data_nodes = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/saml:EncryptedID/xenc:EncryptedData')
            if len(encrypted_data_nodes) == 1:
                encrypted_data = encrypted_data_nodes[0]
                name_id = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key)
        else:
            entries = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/saml:NameID')
            if len(entries) == 1:
                name_id = entries[0]

        if name_id is None:
            raise OneLogin_Saml2_ValidationError(
                'NameID not found in the Logout Request',
                OneLogin_Saml2_ValidationError.NO_NAMEID
            )

        name_id_data = {
            'Value': OneLogin_Saml2_Utils.element_text(name_id)
        }
        for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
            if attr in name_id.attrib.keys():
                name_id_data[attr] = name_id.attrib[attr]

        return name_id_data
예제 #13
0
    def get_attributes(self):
        """
        Gets the Attributes from the AttributeStatement element.
        EncryptedAttributes are not supported
        """
        attributes = {}
        attribute_nodes = self.__query_assertion('/saml:AttributeStatement/saml:Attribute')
        for attribute_node in attribute_nodes:
            attr_name = attribute_node.get('Name')
            if attr_name in attributes.keys():
                raise OneLogin_Saml2_ValidationError(
                    'Found an Attribute element with duplicated Name',
                    OneLogin_Saml2_ValidationError.DUPLICATED_ATTRIBUTE_NAME_FOUND
                )

            values = []
            for attr in attribute_node.iterchildren('{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP[OneLogin_Saml2_Constants.NS_PREFIX_SAML]):
                # Remove any whitespace (which may be present where attributes are
                # nested inside NameID children).
                attr_text = OneLogin_Saml2_Utils.element_text(attr)
                if attr_text:
                    attr_text = attr_text.strip()
                    if attr_text:
                        values.append(attr_text)

                # Parse any nested NameID children
                for nameid in attr.iterchildren('{%s}NameID' % OneLogin_Saml2_Constants.NSMAP[OneLogin_Saml2_Constants.NS_PREFIX_SAML]):
                    values.append({
                        'NameID': {
                            'Format': nameid.get('Format'),
                            'NameQualifier': nameid.get('NameQualifier'),
                            'value': OneLogin_Saml2_Utils.element_text(nameid)
                        }
                    })

            attributes[attr_name] = values
        return attributes
예제 #14
0
    def _parse_name_id_format(self, provider_node):
        """Parses a name ID format

        NOTE: OneLogin's python-saml library used for implementing SAML authentication support only one name ID format.
        If there are multiple name ID formats specified in the XML metadata, we select the first one.

        :param provider_node: Parent IDPSSODescriptor/SPSSODescriptor node
        :type provider_node: defusedxml.lxml.RestrictedElement

        :return: Name ID format
        :rtype: string
        """
        name_id_format = NameIDFormat.UNSPECIFIED.value
        name_id_format_nodes = OneLogin_Saml2_Utils.query(provider_node, './ md:NameIDFormat')
        if len(name_id_format_nodes) > 0:
            # OneLogin's python-saml supports only one name ID format so we select the first one
            name_id_format = OneLogin_Saml2_Utils.element_text(name_id_format_nodes[0])

        return name_id_format
예제 #15
0
    def get_session_indexes(request):
        """
        Gets the SessionIndexes from the Logout Request
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :return: The SessionIndex value
        :rtype: list
        """
        if isinstance(request, etree._Element):
            elem = request
        else:
            if isinstance(request, Document):
                request = request.toxml()
            elem = fromstring(request, forbid_dtd=True)

        session_indexes = []
        session_index_nodes = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/samlp:SessionIndex')
        for session_index_node in session_index_nodes:
            session_indexes.append(OneLogin_Saml2_Utils.element_text(session_index_node))
        return session_indexes
예제 #16
0
    def get_session_indexes(request):
        """
        Gets the SessionIndexes from the Logout Request
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :return: The SessionIndex value
        :rtype: list
        """
        if isinstance(request, etree._Element):
            elem = request
        else:
            if isinstance(request, Document):
                request = request.toxml()
            elem = fromstring(request)

        session_indexes = []
        session_index_nodes = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/samlp:SessionIndex')
        for session_index_node in session_index_nodes:
            session_indexes.append(OneLogin_Saml2_Utils.element_text(session_index_node))
        return session_indexes
예제 #17
0
    def _parse_certificates(self, certificate_nodes):
        """Parses XML nodes containing X.509 certificates into a list of strings

        :param certificate_nodes: List of XML nodes containing X.509 certificates
        :type certificate_nodes: List[defusedxml.lxml.RestrictedElement]

        :return: List of string containing X.509 certificates
        :rtype: List[string]

        :raise: MetadataParsingError
        """
        certificates = []

        try:
            for certificate_node in certificate_nodes:
                certificates.append(''.join(OneLogin_Saml2_Utils.element_text(certificate_node).split()))
        except XMLSyntaxError as exception:
            raise SAMLMetadataParsingError(inner_exception=exception)

        return certificates
예제 #18
0
    def get_issuer(request):
        """
        Gets the Issuer of the Logout Request Message
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :return: The Issuer
        :rtype: string
        """
        if isinstance(request, etree._Element):
            elem = request
        else:
            if isinstance(request, Document):
                request = request.toxml()
            elem = fromstring(request, forbid_dtd=True)

        issuer = None
        issuer_nodes = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/saml:Issuer')
        if len(issuer_nodes) == 1:
            issuer = OneLogin_Saml2_Utils.element_text(issuer_nodes[0])
        return issuer
예제 #19
0
    def get_issuer(request):
        """
        Gets the Issuer of the Logout Request Message
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :return: The Issuer
        :rtype: string
        """
        if isinstance(request, etree._Element):
            elem = request
        else:
            if isinstance(request, Document):
                request = request.toxml()
            elem = fromstring(request)

        issuer = None
        issuer_nodes = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/saml:Issuer')
        if len(issuer_nodes) == 1:
            issuer = OneLogin_Saml2_Utils.element_text(issuer_nodes[0])
        return issuer
예제 #20
0
    def _parse(self,
               dom,
               required_sso_binding=OneLogin_Saml2_Constants.
               BINDING_HTTP_REDIRECT,
               required_slo_binding=OneLogin_Saml2_Constants.
               BINDING_HTTP_REDIRECT,
               entity_id=None):
        data = {}
        idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None
        entity_descriptor_node = dom

        idp_descriptor_nodes = OneLogin_Saml2_Utils.query(
            entity_descriptor_node, './md:IDPSSODescriptor')
        if len(idp_descriptor_nodes) > 0:
            idp_descriptor_node = idp_descriptor_nodes[0]

            idp_entity_id = entity_descriptor_node.get('entityID', None)

            want_authn_requests_signed = entity_descriptor_node.get(
                'WantAuthnRequestsSigned', None)

            name_id_format_nodes = OneLogin_Saml2_Utils.query(
                idp_descriptor_node, './md:NameIDFormat')
            if len(name_id_format_nodes) > 0:
                idp_name_id_format = OneLogin_Saml2_Utils.element_text(
                    name_id_format_nodes[0])

            sso_nodes = OneLogin_Saml2_Utils.query(
                idp_descriptor_node,
                "./md:SingleSignOnService[@Binding='%s']" %
                required_sso_binding)

            if len(sso_nodes) > 0:
                idp_sso_url = sso_nodes[0].get('Location', None)

            slo_nodes = OneLogin_Saml2_Utils.query(
                idp_descriptor_node,
                "./md:SingleLogoutService[@Binding='%s']" %
                required_slo_binding)
            if len(slo_nodes) > 0:
                idp_slo_url = slo_nodes[0].get('Location', None)

            signing_nodes = OneLogin_Saml2_Utils.query(
                idp_descriptor_node,
                "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
            )
            encryption_nodes = OneLogin_Saml2_Utils.query(
                idp_descriptor_node,
                "./md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
            )

            if len(signing_nodes) > 0 or len(encryption_nodes) > 0:
                certs = {}
                if len(signing_nodes) > 0:
                    certs['signing'] = []
                    for cert_node in signing_nodes:
                        certs['signing'].append(''.join(
                            OneLogin_Saml2_Utils.element_text(
                                cert_node).split()))
                if len(encryption_nodes) > 0:
                    certs['encryption'] = []
                    for cert_node in encryption_nodes:
                        certs['encryption'].append(''.join(
                            OneLogin_Saml2_Utils.element_text(
                                cert_node).split()))

            data['idp'] = {}

            if idp_entity_id is not None:
                data['idp']['entityId'] = idp_entity_id

            if idp_sso_url is not None:
                data['idp']['singleSignOnService'] = {}
                data['idp']['singleSignOnService']['url'] = idp_sso_url
                data['idp']['singleSignOnService'][
                    'binding'] = required_sso_binding

            if idp_slo_url is not None:
                data['idp']['singleLogoutService'] = {}
                data['idp']['singleLogoutService']['url'] = idp_slo_url
                data['idp']['singleLogoutService'][
                    'binding'] = required_slo_binding

            if certs is not None:
                if (len(certs) == 1 and
                    (('signing' in certs and len(certs['signing']) == 1) or
                     ('encryption' in certs and len(certs['encryption']) == 1))) or \
                        (('signing' in certs and len(certs['signing']) == 1) and
                         ('encryption' in certs and len(certs['encryption']) == 1 and
                          certs['signing'][0] == certs['encryption'][0])):
                    if 'signing' in certs:
                        data['idp']['x509cert'] = certs['signing'][0]
                    else:
                        data['idp']['x509cert'] = certs['encryption'][0]
                else:
                    data['idp']['x509certMulti'] = certs

            if want_authn_requests_signed is not None:
                data['security'] = {}
                data['security'][
                    'authnRequestsSigned'] = want_authn_requests_signed

            if idp_name_id_format:
                data['sp'] = {}
                data['sp']['NameIDFormat'] = idp_name_id_format
        return data
예제 #21
0
    def parse(
            idp_metadata,
            required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            entity_id=None):
        """
        Parse the Identity Provider metadata and return a dict with extracted data.

        If there are multiple <IDPSSODescriptor> tags, parse only the first.

        Parse only those SSO endpoints with the same binding as given by
        the `required_sso_binding` parameter.

        Parse only those SLO endpoints with the same binding as given by
        the `required_slo_binding` parameter.

        If the metadata specifies multiple SSO endpoints with the required
        binding, extract only the first (the same holds true for SLO
        endpoints).

        :param idp_metadata: XML of the Identity Provider Metadata.
        :type idp_metadata: string

        :param required_sso_binding: Parse only POST or REDIRECT SSO endpoints.
        :type required_sso_binding: one of OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
            or OneLogin_Saml2_Constants.BINDING_HTTP_POST

        :param required_slo_binding: Parse only POST or REDIRECT SLO endpoints.
        :type required_slo_binding: one of OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
            or OneLogin_Saml2_Constants.BINDING_HTTP_POST

        :param entity_id: Specify the entity_id of the EntityDescriptor that you want to parse a XML
                          that contains multiple EntityDescriptor.
        :type entity_id: string

        :returns: settings dict with extracted data
        :rtype: dict
        """
        data = {}

        dom = fromstring(idp_metadata)

        entity_desc_path = '//md:EntityDescriptor'
        if entity_id:
            entity_desc_path += "[@entityID='%s']" % entity_id

        entity_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, entity_desc_path)

        idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None

        if len(entity_descriptor_nodes) > 0:
            entity_descriptor_node = entity_descriptor_nodes[0]
            idp_descriptor_nodes = OneLogin_Saml2_Utils.query(entity_descriptor_node, './md:IDPSSODescriptor')
            if len(idp_descriptor_nodes) > 0:
                idp_descriptor_node = idp_descriptor_nodes[0]

                idp_entity_id = entity_descriptor_node.get('entityID', None)

                want_authn_requests_signed = entity_descriptor_node.get('WantAuthnRequestsSigned', None)

                name_id_format_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, './md:NameIDFormat')
                if len(name_id_format_nodes) > 0:
                    idp_name_id_format = OneLogin_Saml2_Utils.element_text(name_id_format_nodes[0])

                sso_nodes = OneLogin_Saml2_Utils.query(
                    idp_descriptor_node,
                    "./md:SingleSignOnService[@Binding='%s']" % required_sso_binding
                )

                if len(sso_nodes) > 0:
                    idp_sso_url = sso_nodes[0].get('Location', None)

                slo_nodes = OneLogin_Saml2_Utils.query(
                    idp_descriptor_node,
                    "./md:SingleLogoutService[@Binding='%s']" % required_slo_binding
                )
                if len(slo_nodes) > 0:
                    idp_slo_url = slo_nodes[0].get('Location', None)

                signing_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
                encryption_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate")

                if len(signing_nodes) > 0 or len(encryption_nodes) > 0:
                    certs = {}
                    if len(signing_nodes) > 0:
                        certs['signing'] = []
                        for cert_node in signing_nodes:
                            certs['signing'].append(''.join(OneLogin_Saml2_Utils.element_text(cert_node).split()))
                    if len(encryption_nodes) > 0:
                        certs['encryption'] = []
                        for cert_node in encryption_nodes:
                            certs['encryption'].append(''.join(OneLogin_Saml2_Utils.element_text(cert_node).split()))

                data['idp'] = {}

                if idp_entity_id is not None:
                    data['idp']['entityId'] = idp_entity_id

                if idp_sso_url is not None:
                    data['idp']['singleSignOnService'] = {}
                    data['idp']['singleSignOnService']['url'] = idp_sso_url
                    data['idp']['singleSignOnService']['binding'] = required_sso_binding

                if idp_slo_url is not None:
                    data['idp']['singleLogoutService'] = {}
                    data['idp']['singleLogoutService']['url'] = idp_slo_url
                    data['idp']['singleLogoutService']['binding'] = required_slo_binding

                if certs is not None:
                    if (len(certs) == 1 and
                        (('signing' in certs and len(certs['signing']) == 1) or
                         ('encryption' in certs and len(certs['encryption']) == 1))) or \
                        (('signing' in certs and len(certs['signing']) == 1) and
                         ('encryption' in certs and len(certs['encryption']) == 1 and
                         certs['signing'][0] == certs['encryption'][0])):
                        if 'signing' in certs:
                            data['idp']['x509cert'] = certs['signing'][0]
                        else:
                            data['idp']['x509cert'] = certs['encryption'][0]
                    else:
                        data['idp']['x509certMulti'] = certs

                if want_authn_requests_signed is not None:
                    data['security'] = {}
                    data['security']['authnRequestsSigned'] = want_authn_requests_signed

                if idp_name_id_format:
                    data['sp'] = {}
                    data['sp']['NameIDFormat'] = idp_name_id_format
        return data
예제 #22
0
    def parse(
            idp_metadata,
            required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            entity_id=None):
        """
        Parse the Identity Provider metadata and return a dict with extracted data.

        If there are multiple <IDPSSODescriptor> tags, parse only the first.

        Parse only those SSO endpoints with the same binding as given by
        the `required_sso_binding` parameter.

        Parse only those SLO endpoints with the same binding as given by
        the `required_slo_binding` parameter.

        If the metadata specifies multiple SSO endpoints with the required
        binding, extract only the first (the same holds true for SLO
        endpoints).

        :param idp_metadata: XML of the Identity Provider Metadata.
        :type idp_metadata: string

        :param required_sso_binding: Parse only POST or REDIRECT SSO endpoints.
        :type required_sso_binding: one of OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
            or OneLogin_Saml2_Constants.BINDING_HTTP_POST

        :param required_slo_binding: Parse only POST or REDIRECT SLO endpoints.
        :type required_slo_binding: one of OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
            or OneLogin_Saml2_Constants.BINDING_HTTP_POST

        :param entity_id: Specify the entity_id of the EntityDescriptor that you want to parse a XML
                          that contains multiple EntityDescriptor.
        :type entity_id: string

        :returns: settings dict with extracted data
        :rtype: dict
        """
        data = {}

        dom = fromstring(idp_metadata, forbid_dtd=True)

        entity_desc_path = '//md:EntityDescriptor'
        if entity_id:
            entity_desc_path += "[@entityID='%s']" % entity_id

        entity_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, entity_desc_path)

        idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None

        if len(entity_descriptor_nodes) > 0:
            entity_descriptor_node = entity_descriptor_nodes[0]
            idp_descriptor_nodes = OneLogin_Saml2_Utils.query(entity_descriptor_node, './md:IDPSSODescriptor')
            if len(idp_descriptor_nodes) > 0:
                idp_descriptor_node = idp_descriptor_nodes[0]

                idp_entity_id = entity_descriptor_node.get('entityID', None)

                want_authn_requests_signed = entity_descriptor_node.get('WantAuthnRequestsSigned', None)

                name_id_format_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, './md:NameIDFormat')
                if len(name_id_format_nodes) > 0:
                    idp_name_id_format = OneLogin_Saml2_Utils.element_text(name_id_format_nodes[0])

                sso_nodes = OneLogin_Saml2_Utils.query(
                    idp_descriptor_node,
                    "./md:SingleSignOnService[@Binding='%s']" % required_sso_binding
                )

                if len(sso_nodes) > 0:
                    idp_sso_url = sso_nodes[0].get('Location', None)

                slo_nodes = OneLogin_Saml2_Utils.query(
                    idp_descriptor_node,
                    "./md:SingleLogoutService[@Binding='%s']" % required_slo_binding
                )
                if len(slo_nodes) > 0:
                    idp_slo_url = slo_nodes[0].get('Location', None)

                signing_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
                encryption_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate")

                if len(signing_nodes) > 0 or len(encryption_nodes) > 0:
                    certs = {}
                    if len(signing_nodes) > 0:
                        certs['signing'] = []
                        for cert_node in signing_nodes:
                            certs['signing'].append(''.join(OneLogin_Saml2_Utils.element_text(cert_node).split()))
                    if len(encryption_nodes) > 0:
                        certs['encryption'] = []
                        for cert_node in encryption_nodes:
                            certs['encryption'].append(''.join(OneLogin_Saml2_Utils.element_text(cert_node).split()))

                data['idp'] = {}

                if idp_entity_id is not None:
                    data['idp']['entityId'] = idp_entity_id

                if idp_sso_url is not None:
                    data['idp']['singleSignOnService'] = {}
                    data['idp']['singleSignOnService']['url'] = idp_sso_url
                    data['idp']['singleSignOnService']['binding'] = required_sso_binding

                if idp_slo_url is not None:
                    data['idp']['singleLogoutService'] = {}
                    data['idp']['singleLogoutService']['url'] = idp_slo_url
                    data['idp']['singleLogoutService']['binding'] = required_slo_binding

                if certs is not None:
                    if (len(certs) == 1 and
                        (('signing' in certs and len(certs['signing']) == 1) or
                         ('encryption' in certs and len(certs['encryption']) == 1))) or \
                        (('signing' in certs and len(certs['signing']) == 1) and
                         ('encryption' in certs and len(certs['encryption']) == 1 and
                         certs['signing'][0] == certs['encryption'][0])):
                        if 'signing' in certs:
                            data['idp']['x509cert'] = certs['signing'][0]
                        else:
                            data['idp']['x509cert'] = certs['encryption'][0]
                    else:
                        data['idp']['x509certMulti'] = certs

                if want_authn_requests_signed is not None:
                    data['security'] = {}
                    data['security']['authnRequestsSigned'] = want_authn_requests_signed

                if idp_name_id_format:
                    data['sp'] = {}
                    data['sp']['NameIDFormat'] = idp_name_id_format
        return data