示例#1
0
def get_status(dom):
    """
    Gets Status from a Response, include RealMe inner subcode.

    :param dom: The Response as XML
    :type: Document

    :returns: The Status, an array with code, subcode and a message.
    :rtype: dict
    """
    status = {}

    status_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status')
    if len(status_entry) != 1:
        raise Exception('Missing valid Status on response')

    code_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status/samlp:StatusCode', status_entry[0])
    if len(code_entry) != 1:
        raise Exception('Missing valid Status Code on response')
    code = code_entry[0].values()[0]
    status['code'] = code

    subcode_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status/samlp:StatusCode/samlp:StatusCode', status_entry[0])
    if len(subcode_entry) == 1:
        status['subcode'] = subcode_entry[0].values()[0]

    status['msg'] = ''
    message_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status/samlp:StatusMessage', status_entry[0])
    if len(message_entry) == 1:
        status['msg'] = message_entry[0].text

    return status
示例#2
0
    def get_status(dom):
        """
        Gets Status from a Response.

        :param dom: The Response as XML
        :type: Document

        :returns: The Status, an array with the code and a message.
        :rtype: dict
        """
        status = {}

        status_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status')
        if len(status_entry) != 1:
            raise Exception('Missing valid Status on response')

        code_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status/samlp:StatusCode', status_entry[0])
        if len(code_entry) != 1:
            raise Exception('Missing valid Status Code on response')
        code = code_entry[0].values()[0]
        status['code'] = code

        status['msg'] = ''
        message_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status/samlp:StatusMessage', status_entry[0])
        if len(message_entry) == 0:
            subcode_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status/samlp:StatusCode/samlp:StatusCode', status_entry[0])
            if len(subcode_entry) == 1:
                status['msg'] = subcode_entry[0].values()[0]
        elif len(message_entry) == 1:
            status['msg'] = message_entry[0].text

        return status
示例#3
0
    def _parse_metadata_dom(self, metadata_dom):
        entity_descriptor_nodes = OneLogin_Saml2_XML.query(
            metadata_dom, self.ENTITY_DESCRIPTOR_XPATH)
        idps = []

        for entity_descriptor_node in entity_descriptor_nodes:
            idp_descriptor_nodes = OneLogin_Saml2_XML.query(
                entity_descriptor_node, self.IDP_DESCRIPTOR_XPATH)

            for idp_descriptor_node in idp_descriptor_nodes:
                idp_entity_id = entity_descriptor_node.get(
                    self.ENTITY_ID_ATTRIBUTE, None)
                display_name_node = OneLogin_Saml2_XML.query(
                    idp_descriptor_node, self.DISPLAY_NAME_XPATH)

                if not display_name_node:
                    continue

                display_name = display_name_node[0].text

                idp = IdentityProviderMetadata(idp_entity_id, display_name,
                                               entity_descriptor_node)

                idps.append(idp)

        return idps
示例#4
0
    def validate_sign(xml,
                      cert=None,
                      fingerprint=None,
                      fingerprintalg='sha1',
                      validatecert=False,
                      debug=False):
        """
        Validates a signature (Message or Assertion).

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The pubic cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool
        """
        try:
            if xml is None or xml == '':
                raise Exception('Empty string supplied as input')

            elem = OneLogin_Saml2_XML.to_etree(xml)
            xmlsec.enable_debug_trace(debug)
            xmlsec.tree.add_ids(elem, ["ID"])

            signature_nodes = OneLogin_Saml2_XML.query(
                elem, '/samlp:Response/ds:Signature')

            if not len(signature_nodes) > 0:
                signature_nodes += OneLogin_Saml2_XML.query(
                    elem,
                    '/samlp:Response/saml:EncryptedAssertion/saml:Assertion/ds:Signature'
                )
                signature_nodes += OneLogin_Saml2_XML.query(
                    elem, '/samlp:Response/saml:Assertion/ds:Signature')

            if len(signature_nodes) == 1:
                signature_node = signature_nodes[0]

                return OneLogin_Saml2_Utils.validate_node_sign(
                    signature_node, elem, cert, fingerprint, fingerprintalg,
                    validatecert, debug)
            else:
                return False
        except xmlsec.Error as e:
            if debug:
                print(e)

            return False
示例#5
0
    def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False):
        """
        Validates a signature node.

        :param signature_node: The signature node
        :type: Node

        :param xml: The element we should validate
        :type: Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool
        """
        try:
            if (cert is None or cert == '') and fingerprint:
                x509_certificate_nodes = OneLogin_Saml2_XML.query(signature_node, '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate')
                if len(x509_certificate_nodes) > 0:
                    x509_certificate_node = x509_certificate_nodes[0]
                    x509_cert_value = x509_certificate_node.text
                    x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint(x509_cert_value, fingerprintalg)
                    if fingerprint == x509_fingerprint_value:
                        cert = OneLogin_Saml2_Utils.format_cert(x509_cert_value)

            if cert is None or cert == '':
                return False

            # Check if Reference URI is empty
            reference_elem = OneLogin_Saml2_XML.query(signature_node, '//ds:Reference')
            if len(reference_elem) > 0:
                if reference_elem[0].get('URI') == '':
                    reference_elem[0].set('URI', '#%s' % signature_node.getparent().get('ID'))

            if validatecert:
                manager = xmlsec.KeysManager()
                manager.load_cert_from_memory(cert, xmlsec.KeyFormat.CERT_PEM, xmlsec.KeyDataType.TRUSTED)
                dsig_ctx = xmlsec.SignatureContext(manager)
            else:
                dsig_ctx = xmlsec.SignatureContext()
                dsig_ctx.key = xmlsec.Key.from_memory(cert, xmlsec.KeyFormat.CERT_PEM, None)

            dsig_ctx.set_enabled_key_data([xmlsec.KeyData.X509])
            dsig_ctx.verify(signature_node)
            return True
        except xmlsec.Error as e:
            if debug:
                print(e)
示例#6
0
    def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False):
        """
        Validates a signature node.

        :param signature_node: The signature node
        :type: Node

        :param xml: The element we should validate
        :type: Document

        :param cert: The pubic cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool
        """
        try:
            if (cert is None or cert == '') and fingerprint:
                x509_certificate_nodes = OneLogin_Saml2_XML.query(signature_node, '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate')
                if len(x509_certificate_nodes) > 0:
                    x509_certificate_node = x509_certificate_nodes[0]
                    x509_cert_value = x509_certificate_node.text
                    x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint(x509_cert_value, fingerprintalg)
                    if fingerprint == x509_fingerprint_value:
                        cert = OneLogin_Saml2_Utils.format_cert(x509_cert_value)

            if cert is None or cert == '':
                return False

            # Check if Reference URI is empty
            reference_elem = OneLogin_Saml2_XML.query(signature_node, '//ds:Reference')
            if len(reference_elem) > 0:
                if reference_elem[0].get('URI') == '':
                    reference_elem[0].set('URI', '#%s' % signature_node.getparent().get('ID'))

            if validatecert:
                manager = xmlsec.KeysManager()
                manager.load_cert_from_memory(cert, xmlsec.KeyFormat.CERT_PEM, xmlsec.KeyDataType.TRUSTED)
                dsig_ctx = xmlsec.SignatureContext(manager)
            else:
                dsig_ctx = xmlsec.SignatureContext()
                dsig_ctx.key = xmlsec.Key.from_memory(cert, xmlsec.KeyFormat.CERT_PEM, None)

            dsig_ctx.set_enabled_key_data([xmlsec.KeyData.X509])
            dsig_ctx.verify(signature_node)
            return True
        except xmlsec.Error as e:
            if debug:
                print(e)
示例#7
0
    def validate_metadata_sign(
        xml, cert=None, fingerprint=None, fingerprintalg="sha1", validatecert=False, debug=False
    ):
        """
        Validates a signature of a EntityDescriptor.

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The pubic cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool
        """
        try:
            if xml is None or xml == "":
                raise Exception("Empty string supplied as input")

            elem = OneLogin_Saml2_XML.to_etree(xml)
            xmlsec.enable_debug_trace(debug)
            xmlsec.tree.add_ids(elem, ["ID"])

            signature_nodes = OneLogin_Saml2_XML.query(elem, "/md:EntitiesDescriptor/ds:Signature")

            if len(signature_nodes) == 0:
                signature_nodes += OneLogin_Saml2_XML.query(elem, "/md:EntityDescriptor/ds:Signature")

                if len(signature_nodes) == 0:
                    signature_nodes += OneLogin_Saml2_XML.query(
                        elem, "/md:EntityDescriptor/md:SPSSODescriptor/ds:Signature"
                    )
                    signature_nodes += OneLogin_Saml2_XML.query(
                        elem, "/md:EntityDescriptor/md:IDPSSODescriptor/ds:Signature"
                    )

            if len(signature_nodes) > 0:
                for signature_node in signature_nodes:
                    if not OneLogin_Saml2_Utils.validate_node_sign(
                        signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug
                    ):
                        return False
                return True
            else:
                return False
        except Exception:
            return False
示例#8
0
    def validate_num_assertions(self):
        """
        Verifies that the document only contains a single Assertion (encrypted or not)

        :returns: True if only 1 assertion encrypted or not
        :rtype: bool
        """
        encrypted_assertion_nodes = OneLogin_Saml2_XML.query(self.document, '//saml:EncryptedAssertion')
        assertion_nodes = OneLogin_Saml2_XML.query(self.document, '//saml:Assertion')
        return (len(encrypted_assertion_nodes) + len(assertion_nodes)) == 1
示例#9
0
    def __decrypt_assertion(self, xml):
        """
        Decrypts the Assertion

        :raises: Exception if no private key available
        :param xml: Encrypted Assertion
        :type xml: Element
        :returns: Decrypted Assertion
        :rtype: Element
        """
        key = self.__settings.get_sp_key()
        debug = self.__settings.is_debug_active()

        if not key:
            raise OneLogin_Saml2_Error(
                'No private key available to decrypt the assertion, check settings',
                OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND
            )

        encrypted_assertion_nodes = OneLogin_Saml2_XML.query(xml, '/samlp:Response/saml:EncryptedAssertion')
        if encrypted_assertion_nodes:
            encrypted_data_nodes = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData')
            if encrypted_data_nodes:
                keyinfo = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo')
                if not keyinfo:
                    raise OneLogin_Saml2_ValidationError(
                        'No KeyInfo present, invalid Assertion',
                        OneLogin_Saml2_ValidationError.KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA
                    )
                keyinfo = keyinfo[0]
                children = keyinfo.getchildren()
                if not children:
                    raise OneLogin_Saml2_ValidationError(
                        'KeyInfo has no children nodes, invalid Assertion',
                        OneLogin_Saml2_ValidationError.CHILDREN_NODE_NOT_FOUND_IN_KEYINFO
                    )
                for child in children:
                    if 'RetrievalMethod' in child.tag:
                        if child.attrib['Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey':
                            raise OneLogin_Saml2_ValidationError(
                                'Unsupported Retrieval Method found',
                                OneLogin_Saml2_ValidationError.UNSUPPORTED_RETRIEVAL_METHOD
                            )
                        uri = child.attrib['URI']
                        if not uri.startswith('#'):
                            break
                        uri = uri.split('#')[1]
                        encrypted_key = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id="' + uri + '"]')
                        if encrypted_key:
                            keyinfo.append(encrypted_key[0])

                encrypted_data = encrypted_data_nodes[0]
                decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug)
                xml.replace(encrypted_assertion_nodes[0], decrypted)
        return xml
示例#10
0
    def __decrypt_assertion(self, xml):
        """
        Decrypts the Assertion

        :raises: Exception if no private key available
        :param xml: Encrypted Assertion
        :type xml: Element
        :returns: Decrypted Assertion
        :rtype: Element
        """
        key = self.__settings.get_sp_key()
        debug = self.__settings.is_debug_active()

        if not key:
            raise Exception('No private key available, check settings')

        encrypted_assertion_nodes = OneLogin_Saml2_XML.query(
            xml, '/samlp:Response/saml:EncryptedAssertion')
        if encrypted_assertion_nodes:
            encrypted_data_nodes = OneLogin_Saml2_XML.query(
                encrypted_assertion_nodes[0],
                '//saml:EncryptedAssertion/xenc:EncryptedData')
            if encrypted_data_nodes:
                keyinfo = OneLogin_Saml2_XML.query(
                    encrypted_assertion_nodes[0],
                    '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo')
                if not keyinfo:
                    raise Exception('No KeyInfo present, invalid Assertion')
                keyinfo = keyinfo[0]
                children = keyinfo.getchildren()
                if not children:
                    raise Exception('No child to KeyInfo, invalid Assertion')
                for child in children:
                    if 'RetrievalMethod' in child.tag:
                        if child.attrib[
                                'Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey':
                            raise Exception(
                                'Unsupported Retrieval Method found')
                        uri = child.attrib['URI']
                        if not uri.startswith('#'):
                            break
                        uri = uri.split('#')[1]
                        encrypted_key = OneLogin_Saml2_XML.query(
                            encrypted_assertion_nodes[0],
                            './xenc:EncryptedKey[@Id="' + uri + '"]')
                        if encrypted_key:
                            keyinfo.append(encrypted_key[0])

                encrypted_data = encrypted_data_nodes[0]
                decrypted = OneLogin_Saml2_Utils.decrypt_element(
                    encrypted_data, key, debug)
                xml.replace(encrypted_assertion_nodes[0], decrypted)
        return xml
示例#11
0
    def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False, xpath=None):
        """
        Validates a signature (Message or Assertion).

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param xpath: The xpath of the signed element
        :type: string
        """
        try:
            if xml is None or xml == '':
                raise Exception('Empty string supplied as input')

            elem = OneLogin_Saml2_XML.to_etree(xml)
            xmlsec.enable_debug_trace(debug)
            xmlsec.tree.add_ids(elem, ["ID"])

            if xpath:
                signature_nodes = OneLogin_Saml2_XML.query(elem, xpath)
            else:
                signature_nodes = OneLogin_Saml2_XML.query(elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH)

                if len(signature_nodes) == 0:
                    signature_nodes = OneLogin_Saml2_XML.query(elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)

            if len(signature_nodes) == 1:
                signature_node = signature_nodes[0]

                return OneLogin_Saml2_Utils.validate_node_sign(signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug)
            else:
                return False
        except xmlsec.Error as e:
            if debug:
                print(e)

            return False
示例#12
0
    def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg="sha1", validatecert=False, debug=False):
        """
        Validates a signature (Message or Assertion).

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The pubic cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool
        """
        try:
            if xml is None or xml == "":
                raise Exception("Empty string supplied as input")

            elem = OneLogin_Saml2_XML.to_etree(xml)
            xmlsec.enable_debug_trace(debug)
            xmlsec.tree.add_ids(elem, ["ID"])

            signature_nodes = OneLogin_Saml2_XML.query(elem, "/samlp:Response/ds:Signature")

            if not len(signature_nodes) > 0:
                signature_nodes += OneLogin_Saml2_XML.query(
                    elem, "/samlp:Response/saml:EncryptedAssertion/saml:Assertion/ds:Signature"
                )
                signature_nodes += OneLogin_Saml2_XML.query(elem, "/samlp:Response/saml:Assertion/ds:Signature")

            if len(signature_nodes) == 1:
                signature_node = signature_nodes[0]

                return OneLogin_Saml2_Utils.validate_node_sign(
                    signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug
                )
            else:
                return False
        except xmlsec.Error as e:
            if debug:
                print(e)

            return False
示例#13
0
    def get_status(dom):
        """
        Gets Status from a Response.

        :param dom: The Response as XML
        :type: Document

        :returns: The Status, an array with the code and a message.
        :rtype: dict
        """
        status = {}

        status_entry = OneLogin_Saml2_XML.query(
            dom, '/samlp:Response/samlp:Status')
        if len(status_entry) != 1:
            raise OneLogin_Saml2_ValidationError(
                'Missing Status on response',
                OneLogin_Saml2_ValidationError.MISSING_STATUS)

        code_entry = OneLogin_Saml2_XML.query(
            dom, '/samlp:Response/samlp:Status/samlp:StatusCode',
            status_entry[0])
        if len(code_entry) != 1:
            raise OneLogin_Saml2_ValidationError(
                'Missing Status Code on response',
                OneLogin_Saml2_ValidationError.MISSING_STATUS_CODE)
        code = code_entry[0].values()[0]

        if not code:
            raise OneLogin_Saml2_ValidationError(
                'Missing Status Code on response',
                OneLogin_Saml2_ValidationError.MISSING_STATUS_CODE)

        status['code'] = code

        status['msg'] = ''
        message_entry = OneLogin_Saml2_XML.query(
            dom, '/samlp:Response/samlp:Status/samlp:StatusMessage',
            status_entry[0])
        if len(message_entry) == 0:
            subcode_entry = OneLogin_Saml2_XML.query(
                dom,
                '/samlp:Response/samlp:Status/samlp:StatusCode/samlp:StatusCode',
                status_entry[0])
            if len(subcode_entry) == 1:
                status['msg'] = subcode_entry[0].values()[0]
        elif len(message_entry) == 1:
            status['msg'] = OneLogin_Saml2_XML.element_text(message_entry[0])

        return status
示例#14
0
    def validate_metadata_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False):
        """
        Validates a signature of a EntityDescriptor.

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The pubic cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool
        """
        try:
            if xml is None or xml == '':
                raise Exception('Empty string supplied as input')

            elem = OneLogin_Saml2_XML.to_etree(xml)
            xmlsec.enable_debug_trace(debug)
            xmlsec.tree.add_ids(elem, ["ID"])

            signature_nodes = OneLogin_Saml2_XML.query(elem, '/md:EntitiesDescriptor/ds:Signature')

            if len(signature_nodes) == 0:
                signature_nodes += OneLogin_Saml2_XML.query(elem, '/md:EntityDescriptor/ds:Signature')

                if len(signature_nodes) == 0:
                    signature_nodes += OneLogin_Saml2_XML.query(elem, '/md:EntityDescriptor/md:SPSSODescriptor/ds:Signature')
                    signature_nodes += OneLogin_Saml2_XML.query(elem, '/md:EntityDescriptor/md:IDPSSODescriptor/ds:Signature')

            if len(signature_nodes) > 0:
                for signature_node in signature_nodes:
                    if not OneLogin_Saml2_Utils.validate_node_sign(signature_node, elem, cert, fingerprint, fingerprintalg, validatecert, debug):
                        return False
                return True
            else:
                return False
        except Exception:
            return False
    def get_metadata(url):
        """
        Gets the metadata XML from the provided URL
        :param url: Url where the XML of the Identity Provider Metadata is published.
        :type url: string
        :returns: metadata XML
        :rtype: string
        """
        valid = False
        response = urllib2.urlopen(url)
        xml = response.read()

        if xml:
            try:
                dom = OneLogin_Saml2_XML.to_etree(xml)
                idp_descriptor_nodes = OneLogin_Saml2_XML.query(dom, '//md:IDPSSODescriptor')
                if idp_descriptor_nodes:
                    valid = True
            except:
                pass

        if not valid:
            raise Exception('Not valid IdP XML found from URL: %s' % (url))

        return xml
    def validate_signed_elements(self, signed_elements):
        """
        Verifies that the document has the expected signed nodes.

        :param signed_elements: The signed elements to be checked
        :type signed_elements: list
        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if len(signed_elements) > 2:
            return False

        response_tag = '{%s}LogoutResponse' % OneLogin_Saml2_Constants.NS_SAMLP

        if (response_tag in signed_elements and signed_elements.count(response_tag) > 1) or \
           (response_tag not in signed_elements):
            return False

        # Check that the signed elements found here, are the ones that will be verified
        # by OneLogin_Saml2_Utils.validate_sign
        if response_tag in signed_elements:
            expected_signature_nodes = OneLogin_Saml2_XML.query(
                self.document, LOGOUT_RESPONSE_SIGNATURE_XPATH)
            if len(expected_signature_nodes) != 1:
                print(
                    'Unexpected number of Response signatures found. SAML Response rejected. Nodes = %s'
                    % expected_signature_nodes)
                raise OneLogin_Saml2_ValidationError(
                    'Unexpected number of Response signatures found. SAML Response rejected.',
                    OneLogin_Saml2_ValidationError.
                    WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE)
        return True
示例#17
0
    def validate_signed_elements(self, signed_elements):
        """
        Verifies that the document has the expected signed nodes.
        """
        if len(signed_elements) > 2:
            return False

        response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP
        assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML

        if (response_tag in signed_elements and signed_elements.count(response_tag) > 1) or \
           (assertion_tag in signed_elements and signed_elements.count(assertion_tag) > 1) or \
           (response_tag not in signed_elements and assertion_tag not in signed_elements):
            return False

        # Check that the signed elements found here, are the ones that will be verified
        # by OneLogin_Saml2_Utils.validate_sign
        if response_tag in signed_elements:
            expected_signature_nodes = OneLogin_Saml2_XML.query(self.document, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH)
            if len(expected_signature_nodes) != 1:
                raise Exception('Unexpected number of Response signatures found. SAML Response rejected.')

        if assertion_tag in signed_elements:
            expected_signature_nodes = self.__query(OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)
            if len(expected_signature_nodes) != 1:
                raise Exception('Unexpected number of Assertion signatures found. SAML Response rejected.')

        return True
示例#18
0
    def get_issuers(self):
        """
        Gets the issuers (from message and from assertion)

        :returns: The issuers
        :rtype: list
        """
        issuers = set()

        message_issuer_nodes = OneLogin_Saml2_XML.query(self.document, '/samlp:Response/saml:Issuer')
        if len(message_issuer_nodes) == 1:
            issuers.add(message_issuer_nodes[0].text)
        else:
            raise OneLogin_Saml2_ValidationError(
                'Issuer of the Response not found or multiple.',
                OneLogin_Saml2_ValidationError.ISSUER_NOT_FOUND_IN_RESPONSE
            )

        assertion_issuer_nodes = self.__query_assertion('/saml:Issuer')
        if len(assertion_issuer_nodes) == 1:
            issuers.add(assertion_issuer_nodes[0].text)
        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))
示例#19
0
    def get_metadata(url):
        """
        Gets the metadata XML from the provided URL
        :param url: Url where the XML of the Identity Provider Metadata is published.
        :type url: string
        :returns: metadata XML
        :rtype: string
        """
        valid = False
        response = urllib2.urlopen(url)
        xml = response.read()

        if xml:
            try:
                dom = OneLogin_Saml2_XML.to_etree(xml)
                idp_descriptor_nodes = OneLogin_Saml2_XML.query(
                    dom, '//md:IDPSSODescriptor')
                if idp_descriptor_nodes:
                    valid = True
            except:
                pass

        if not valid:
            raise Exception('Not valid IdP XML found from URL: %s' % (url))

        return xml
示例#20
0
    def validate_signed_elements(self, signed_elements):
        """
        Verifies that the document has the expected signed nodes.
        """
        if len(signed_elements) > 2:
            return False

        response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP
        assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML

        if (response_tag in signed_elements and signed_elements.count(response_tag) > 1) or \
           (assertion_tag in signed_elements and signed_elements.count(assertion_tag) > 1) or \
           (response_tag not in signed_elements and assertion_tag not in signed_elements):
            return False

        # Check that the signed elements found here, are the ones that will be verified
        # by OneLogin_Saml2_Utils.validate_sign
        if response_tag in signed_elements:
            expected_signature_nodes = OneLogin_Saml2_XML.query(self.document, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH)
            if len(expected_signature_nodes) != 1:
                raise Exception('Unexpected number of Response signatures found. SAML Response rejected.')

        if assertion_tag in signed_elements:
            expected_signature_nodes = self.__query(OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)
            if len(expected_signature_nodes) != 1:
                raise Exception('Unexpected number of Assertion signatures found. SAML Response rejected.')

        return True
示例#21
0
    def process_signed_elements(self):
        """
        Verifies the signature nodes:
         - Checks that are Response or Assertion
         - Check that IDs and reference URI are unique and consistent.

        :returns: The signed elements tag names
        :rtype: list
        """
        sign_nodes = self.__query('//ds:Signature')

        signed_elements = []
        verified_seis = []
        verified_ids = []
        response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP
        assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML

        for sign_node in sign_nodes:
            signed_element = sign_node.getparent().tag
            if signed_element != response_tag and signed_element != assertion_tag:
                raise Exception(
                    'Invalid Signature Element %s SAML Response rejected' %
                    signed_element)

            if not sign_node.getparent().get('ID'):
                raise Exception(
                    'Signed Element must contain an ID. SAML Response rejected'
                )

            id_value = sign_node.getparent().get('ID')
            if id_value in verified_ids:
                raise Exception('Duplicated ID. SAML Response rejected')
            verified_ids.append(id_value)

            # Check that reference URI matches the parent ID and no duplicate References or IDs
            ref = OneLogin_Saml2_XML.query(sign_node, './/ds:Reference')
            if ref:
                ref = ref[0]
                if ref.get('URI'):
                    sei = ref.get('URI')[1:]

                    if sei != id_value:
                        raise Exception(
                            'Found an invalid Signed Element. SAML Response rejected'
                        )

                    if sei in verified_seis:
                        raise Exception(
                            'Duplicated Reference URI. SAML Response rejected')
                    verified_seis.append(sei)

            signed_elements.append(signed_element)

            if signed_elements:
                if not self.validate_signed_elements(signed_elements):
                    raise Exception(
                        'Found an unexpected Signature Element. SAML Response rejected'
                    )
        return signed_elements
示例#22
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
        """
        elem = OneLogin_Saml2_XML.to_etree(request)
        name_id = None
        encrypted_entries = OneLogin_Saml2_XML.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_XML.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_XML.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_XML.element_text(name_id)}
        for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
            if attr in name_id.attrib:
                name_id_data[attr] = name_id.attrib[attr]

        return name_id_data
 def __query(self, query):
     """
     Extracts a node from the Etree (Logout Response Message)
     :param query: Xpath Expression
     :type query: string
     :return: The queried node
     :rtype: Element
     """
     return OneLogin_Saml2_XML.query(self.document, query)
示例#24
0
 def __query(self, query):
     """
     Extracts a node from the Etree (Logout Response Message)
     :param query: Xpath Expression
     :type query: string
     :return: The queried node
     :rtype: Element
     """
     return OneLogin_Saml2_XML.query(self.document, query)
示例#25
0
    def __decrypt_assertion(self, xml):
        """
        Decrypts the Assertion

        :raises: Exception if no private key available
        :param xml: Encrypted Assertion
        :type xml: Element
        :returns: Decrypted Assertion
        :rtype: Element
        """
        key = self.__settings.get_sp_key()
        debug = self.__settings.is_debug_active()

        if not key:
            raise Exception('No private key available, check settings')

        encrypted_assertion_nodes = OneLogin_Saml2_XML.query(xml, '/samlp:Response/saml:EncryptedAssertion')
        if encrypted_assertion_nodes:
            encrypted_data_nodes = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData')
            if encrypted_data_nodes:
                keyinfo = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo')
                if not keyinfo:
                    raise Exception('No KeyInfo present, invalid Assertion')
                keyinfo = keyinfo[0]
                children = keyinfo.getchildren()
                if not children:
                    raise Exception('No child to KeyInfo, invalid Assertion')
                for child in children:
                    if 'RetrievalMethod' in child.tag:
                        if child.attrib['Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey':
                            raise Exception('Unsupported Retrieval Method found')
                        uri = child.attrib['URI']
                        if not uri.startswith('#'):
                            break
                        uri = uri.split('#')[1]
                        encrypted_key = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id="' + uri + '"]')
                        if encrypted_key:
                            keyinfo.append(encrypted_key[0])

                encrypted_data = encrypted_data_nodes[0]
                decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug)
                xml.replace(encrypted_assertion_nodes[0], decrypted)
        return xml
示例#26
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
        """
        elem = OneLogin_Saml2_XML.to_etree(request)
        name_id = None
        encrypted_entries = OneLogin_Saml2_XML.query(
            elem, '/samlp:LogoutRequest/saml:EncryptedID')

        if len(encrypted_entries) == 1:
            if key is None:
                raise Exception(
                    'Key is required in order to decrypt the NameID')

            encrypted_data_nodes = OneLogin_Saml2_XML.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_XML.query(
                elem, '/samlp:LogoutRequest/saml:NameID')
            if len(entries) == 1:
                name_id = entries[0]

        if name_id is None:
            raise Exception('Not NameID found in the Logout Request')

        name_id_data = {'Value': name_id.text}
        for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
            if attr in name_id.attrib:
                name_id_data[attr] = name_id.attrib[attr]

        return name_id_data
示例#27
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
        """
        elem = OneLogin_Saml2_XML.to_etree(request)
        name_id = None
        encrypted_entries = OneLogin_Saml2_XML.query(elem, '/samlp:LogoutRequest/saml:EncryptedID')

        if len(encrypted_entries) == 1:
            if key is None:
                raise Exception('Key is required in order to decrypt the NameID')

            encrypted_data_nodes = OneLogin_Saml2_XML.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_XML.query(elem, '/samlp:LogoutRequest/saml:NameID')
            if len(entries) == 1:
                name_id = entries[0]

        if name_id is None:
            raise Exception('Not NameID found in the Logout Request')

        name_id_data = {
            'Value': name_id.text
        }
        for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
            if attr in name_id.attrib:
                name_id_data[attr] = name_id.attrib[attr]

        return name_id_data
示例#28
0
    def process_signed_elements(self):
        """
        Verifies the signature nodes:
         - Checks that are Response or Assertion
         - Check that IDs and reference URI are unique and consistent.

        :returns: The signed elements tag names
        :rtype: list
        """
        sign_nodes = self.__query('//ds:Signature')

        signed_elements = []
        verified_seis = []
        verified_ids = []
        response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP
        assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML

        for sign_node in sign_nodes:
            signed_element = sign_node.getparent().tag
            if signed_element != response_tag and signed_element != assertion_tag:
                raise Exception('Invalid Signature Element %s SAML Response rejected' % signed_element)

            if not sign_node.getparent().get('ID'):
                raise Exception('Signed Element must contain an ID. SAML Response rejected')

            id_value = sign_node.getparent().get('ID')
            if id_value in verified_ids:
                raise Exception('Duplicated ID. SAML Response rejected')
            verified_ids.append(id_value)

            # Check that reference URI matches the parent ID and no duplicate References or IDs
            ref = OneLogin_Saml2_XML.query(sign_node, './/ds:Reference')
            if ref:
                ref = ref[0]
                if ref.get('URI'):
                    sei = ref.get('URI')[1:]

                    if sei != id_value:
                        raise Exception('Found an invalid Signed Element. SAML Response rejected')

                    if sei in verified_seis:
                        raise Exception('Duplicated Reference URI. SAML Response rejected')
                    verified_seis.append(sei)

            signed_elements.append(signed_element)

            if signed_elements:
                if not self.validate_signed_elements(signed_elements):
                    raise Exception('Found an unexpected Signature Element. SAML Response rejected')
        return signed_elements
示例#29
0
    def get_status(dom):
        """
        Gets Status from a Response.

        :param dom: The Response as XML
        :type: Document

        :returns: The Status, an array with the code and a message.
        :rtype: dict
        """
        status = {}

        status_entry = OneLogin_Saml2_XML.query(dom, "/samlp:Response/samlp:Status")
        if len(status_entry) == 0:
            raise Exception("Missing Status on response")

        code_entry = OneLogin_Saml2_XML.query(dom, "/samlp:Response/samlp:Status/samlp:StatusCode", status_entry[0])
        if len(code_entry) == 0:
            raise Exception("Missing Status Code on response")
        code = code_entry[0].values()[0]
        status["code"] = code

        message_entry = OneLogin_Saml2_XML.query(
            dom, "/samlp:Response/samlp:Status/samlp:StatusMessage", status_entry[0]
        )
        if len(message_entry) == 0:
            subcode_entry = OneLogin_Saml2_XML.query(
                dom, "/samlp:Response/samlp:Status/samlp:StatusCode/samlp:StatusCode", status_entry[0]
            )
            if len(subcode_entry) > 0:
                status["msg"] = subcode_entry[0].values()[0]
            else:
                status["msg"] = ""
        else:
            status["msg"] = message_entry[0].text

        return status
示例#30
0
    def __query(self, query):
        """
        Extracts nodes that match the query from the Response

        :param query: Xpath Expresion
        :type query: String

        :returns: The queried nodes
        :rtype: list
        """
        if self.encrypted:
            document = self.decrypted_document
        else:
            document = self.document
        return OneLogin_Saml2_XML.query(document, query)
示例#31
0
def get_status(dom):
    """
    Gets Status from a Response, including RealMe inner subcode, if present.

    This provides a replacement for OneLogin_Saml2_Utils.get_status(), which only
    returns a subcode if there is no StatusMessage. RealMe errors return both a
    subcode and a sub-code.

    :param dom: The Response as XML
    :type: Document

    :returns: The Status, an array with code, subcode and a message.
    :rtype: dict
    """
    status = {}

    status_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status')
    if len(status_entry) != 1:
        raise Exception('Missing valid Status on response')

    code_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status/samlp:StatusCode', status_entry[0])
    if len(code_entry) != 1:
        raise Exception('Missing valid Status Code on response')
    code = code_entry[0].values()[0]
    status['code'] = code

    subcode_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status/samlp:StatusCode/samlp:StatusCode', status_entry[0])
    if len(subcode_entry) == 1:
        status['subcode'] = subcode_entry[0].values()[0]

    status['msg'] = ''
    message_entry = OneLogin_Saml2_XML.query(dom, '/samlp:Response/samlp:Status/samlp:StatusMessage', status_entry[0])
    if len(message_entry) == 1:
        status['msg'] = message_entry[0].text

    return status
示例#32
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
        """

        elem = OneLogin_Saml2_XML.to_etree(request)
        issuer = None
        issuer_nodes = OneLogin_Saml2_XML.query(elem, '/samlp:LogoutRequest/saml:Issuer')
        if len(issuer_nodes) == 1:
            issuer = OneLogin_Saml2_XML.element_text(issuer_nodes[0])
        return issuer
示例#33
0
    def get_issuer(dom):
        issuer = {}
        issuer_entry = OneLogin_Saml2_XML.query(dom,
                                                '/samlp:Response/saml:Issuer')
        if len(issuer_entry) != 1:
            raise OneLogin_Saml2_ValidationError(
                'Missing Issuer on response',
                OneLogin_Saml2_ValidationError.MISSING_ISSUER)
        if not issuer_entry[0].text:
            raise OneLogin_Saml2_ValidationError(
                'Missing Issuer on response',
                OneLogin_Saml2_ValidationError.MISSING_ISSUER)

        issuer['Format'] = issuer_entry[0].get('Format', None)
        return issuer
示例#34
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
        """

        elem = OneLogin_Saml2_XML.to_etree(request)
        session_indexes = []
        session_index_nodes = OneLogin_Saml2_XML.query(elem, '/samlp:LogoutRequest/samlp:SessionIndex')
        for session_index_node in session_index_nodes:
            session_indexes.append(OneLogin_Saml2_XML.element_text(session_index_node))
        return session_indexes
示例#35
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
        """

        elem = OneLogin_Saml2_XML.to_etree(request)
        session_indexes = []
        session_index_nodes = OneLogin_Saml2_XML.query(elem, '/samlp:LogoutRequest/samlp:SessionIndex')
        for session_index_node in session_index_nodes:
            session_indexes.append(session_index_node.text)
        return session_indexes
示例#36
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
        """

        elem = OneLogin_Saml2_XML.to_etree(request)
        issuer = None
        issuer_nodes = OneLogin_Saml2_XML.query(elem, '/samlp:LogoutRequest/saml:Issuer')
        if len(issuer_nodes) == 1:
            issuer = issuer_nodes[0].text
        return issuer
示例#37
0
    def __query(self, query):
        """
        Extracts nodes that match the query from the Response

        :param query: Xpath Expresion
        :type query: String

        :returns: The queried nodes
        :rtype: list
        """
        if self.encrypted:
            document = self.decrypted_document
        else:
            document = self.document
        return OneLogin_Saml2_XML.query(document, query)
示例#38
0
def get_authentication_strength(dom):
    """
    Gets authentication strength from a Response
    :param dom: The Response as XML, with the Assertion decrypted
    :type: Document
    :returns: the authentication strength
    :rtype: AuthStrength
    """
    authn_context_entry = OneLogin_Saml2_XML.query(
        dom, '/samlp:Response/saml:Assertion/saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef')

    if len(authn_context_entry) != 1:
        raise Exception('Missing authentication context on response')

    authn_context = authn_context_entry[0].text

    log.debug("found AuthnContextClassRef: %s", authn_context)

    return AuthStrength.from_authn_context(authn_context)
示例#39
0
    def get_metadata(cls, url, validate_cert=True, timeout=None):
        """
        Gets the metadata XML from the provided URL
        :param url: Url where the XML of the Identity Provider Metadata is published.
        :type url: string

        :param validate_cert: If the url uses https schema, that flag enables or not the verification of the associated certificate.
        :type validate_cert: bool

        :param timeout: Timeout in seconds to wait for metadata response
        :type timeout: int

        :returns: metadata XML
        :rtype: string
        """
        valid = False

        if validate_cert:
            response = urllib2.urlopen(url, timeout=timeout)
        else:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
            response = urllib2.urlopen(url, context=ctx, timeout=timeout)
        xml = response.read()

        if xml:
            try:
                dom = OneLogin_Saml2_XML.to_etree(xml)
                idp_descriptor_nodes = OneLogin_Saml2_XML.query(
                    dom, '//md:IDPSSODescriptor')
                if idp_descriptor_nodes:
                    valid = True
            except Exception:
                pass

        if not valid:
            raise Exception('Not valid IdP XML found from URL: %s' % (url))

        return xml
示例#40
0
    def get_issuers(self):
        """
        Gets the issuers (from message and from assertion)

        :returns: The issuers
        :rtype: list
        """
        issuers = set()

        message_issuer_nodes = OneLogin_Saml2_XML.query(self.document, '/samlp:Response/saml:Issuer')
        if len(message_issuer_nodes) == 1:
            issuers.add(message_issuer_nodes[0].text)
        else:
            raise Exception('Issuer of the Response not found or multiple.')

        assertion_issuer_nodes = self.__query_assertion('/saml:Issuer')
        if len(assertion_issuer_nodes) == 1:
            issuers.add(assertion_issuer_nodes[0].text)
        else:
            raise Exception('Issuer of the Assertion not found or multiple.')

        return list(set(issuers))
示例#41
0
    def get_issuers(self):
        """
        Gets the issuers (from message and from assertion)

        :returns: The issuers
        :rtype: list
        """
        issuers = set()

        message_issuer_nodes = OneLogin_Saml2_XML.query(self.document, '/samlp:Response/saml:Issuer')
        if len(message_issuer_nodes) == 1:
            issuers.add(message_issuer_nodes[0].text)
        else:
            raise Exception('Issuer of the Response not found or multiple.')

        assertion_issuer_nodes = self.__query_assertion('/saml:Issuer')
        if len(assertion_issuer_nodes) == 1:
            issuers.add(assertion_issuer_nodes[0].text)
        else:
            raise Exception('Issuer of the Assertion not found or multiple.')

        return list(set(issuers))
示例#42
0
def logout_view(request, *args, **kwargs):
    backend = request.backend
    logout(request)

    # Пользователя разлогинили, теперь нужно ответить провайдеру.
    # Для начала нужно узнать, от какого именно IdentityProvider'а пришёл запрос.
    # Как минимму OneLogin не присылает в запросе никаких опознавательных знаков кроме атрибута Issuer.
    # В Issuer у него находится путь к его метадате. В конфиге тот же путь надо указать в entity_id.

    # вынимаем значение Issuer
    request_str = compat.to_string(
        OneLogin_Saml2_Utils.decode_base64_and_inflate(
            request.GET['SAMLRequest']))
    xml_doc = OneLogin_Saml2_XML.to_etree(request_str)
    issuer_nodes = OneLogin_Saml2_XML.query(
        xml_doc, '/samlp:LogoutRequest/saml:Issuer')
    if len(issuer_nodes) == 0:
        raise ValueError('no Issuer in logout request: ' + request_str)
    issuer = issuer_nodes[0].text

    # ищем имя IDP
    idp_configs = backend.setting('ENABLED_IDPS')
    idp_name = next((name for name, cfg in idp_configs.items()
                     if cfg.get('entity_id') == issuer), None)
    if idp_name is None:
        raise ValueError(
            f"IDP not found for Issuer '{issuer}' in logout request: " +
            request_str)

    # формируем адрес редиректа
    idp = backend.get_idp(idp_name)
    if idp.slo_config == {}:  # если конфига slo нет
        url = settings.LOGOUT_REDIRECT_URL
    else:
        url = backend._create_saml_auth(
            idp, remove_signature_from_get=True).process_slo()

    return HttpResponseRedirect(url)
示例#43
0
    def validate_sign(xml,
                      cert=None,
                      fingerprint=None,
                      fingerprintalg='sha1',
                      validatecert=False,
                      debug=False,
                      xpath=None,
                      multicerts=None):
        """
        Validates a signature (Message or Assertion).

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param xpath: The xpath of the signed element
        :type: string

        :param multicerts: Multiple public certs
        :type: list

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if xml is None or xml == '':
            raise Exception('Empty string supplied as input')

        elem = OneLogin_Saml2_XML.to_etree(xml)
        xmlsec.enable_debug_trace(debug)
        xmlsec.tree.add_ids(elem, ["ID"])

        if xpath:
            signature_nodes = OneLogin_Saml2_XML.query(elem, xpath)
        else:
            signature_nodes = OneLogin_Saml2_XML.query(
                elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH)

            if len(signature_nodes) == 0:
                signature_nodes = OneLogin_Saml2_XML.query(
                    elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)

        if len(signature_nodes) == 1:
            signature_node = signature_nodes[0]

            if not multicerts:
                return OneLogin_Saml2_Utils.validate_node_sign(
                    signature_node,
                    elem,
                    cert,
                    fingerprint,
                    fingerprintalg,
                    validatecert,
                    debug,
                    raise_exceptions=True)
            else:
                # If multiple certs are provided, I may ignore cert and
                # fingerprint provided by the method and just check the
                # certs multicerts
                fingerprint = fingerprintalg = None
                for cert in multicerts:
                    if OneLogin_Saml2_Utils.validate_node_sign(
                            signature_node,
                            elem,
                            cert,
                            fingerprint,
                            fingerprintalg,
                            validatecert,
                            False,
                            raise_exceptions=False):
                        return True
                raise OneLogin_Saml2_ValidationError(
                    'Signature validation failed. SAML Response rejected.',
                    OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)
        else:
            raise OneLogin_Saml2_ValidationError(
                'Expected exactly one signature node; got {}.'.format(
                    len(signature_nodes)),
                OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_SIGNATURES)
示例#44
0
    def add_sign(xml,
                 key,
                 cert,
                 debug=False,
                 sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1,
                 digest_algorithm=OneLogin_Saml2_Constants.SHA1):
        """
        Adds signature key and senders certificate to an element (Message or
        Assertion).

        :param xml: The element we should sign
        :type: string | Document

        :param key: The private key
        :type: string

        :param cert: The public
        :type: string

        :param debug: Activate the xmlsec debug
        :type: bool

        :param sign_algorithm: Signature algorithm method
        :type sign_algorithm: string

        :param digest_algorithm: Digest algorithm method
        :type digest_algorithm: string

        :returns: Signed XML
        :rtype: string
        """
        if xml is None or xml == '':
            raise Exception('Empty string supplied as input')

        elem = OneLogin_Saml2_XML.to_etree(xml)

        sign_algorithm_transform_map = {
            OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.Transform.DSA_SHA1,
            OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.Transform.RSA_SHA1,
            OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.Transform.RSA_SHA256,
            OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.Transform.RSA_SHA384,
            OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.Transform.RSA_SHA512
        }
        sign_algorithm_transform = sign_algorithm_transform_map.get(
            sign_algorithm, xmlsec.Transform.RSA_SHA1)

        signature = xmlsec.template.create(elem,
                                           xmlsec.Transform.EXCL_C14N,
                                           sign_algorithm_transform,
                                           ns='ds')

        issuer = OneLogin_Saml2_XML.query(elem, '//saml:Issuer')
        if len(issuer) > 0:
            issuer = issuer[0]
            issuer.addnext(signature)
            elem_to_sign = issuer.getparent()
        else:
            entity_descriptor = OneLogin_Saml2_XML.query(
                elem, '//md:EntityDescriptor')
            if len(entity_descriptor) > 0:
                elem.insert(0, signature)
            else:
                elem[0].insert(0, signature)
            elem_to_sign = elem

        elem_id = elem_to_sign.get('ID', None)
        if elem_id is not None:
            if elem_id:
                elem_id = '#' + elem_id
        else:
            generated_id = generated_id = OneLogin_Saml2_Utils.generate_unique_id(
            )
            elem_id = '#' + generated_id
            elem_to_sign.attrib['ID'] = generated_id

        xmlsec.enable_debug_trace(debug)
        xmlsec.tree.add_ids(elem_to_sign, ["ID"])

        digest_algorithm_transform_map = {
            OneLogin_Saml2_Constants.SHA1: xmlsec.Transform.SHA1,
            OneLogin_Saml2_Constants.SHA256: xmlsec.Transform.SHA256,
            OneLogin_Saml2_Constants.SHA384: xmlsec.Transform.SHA384,
            OneLogin_Saml2_Constants.SHA512: xmlsec.Transform.SHA512
        }
        digest_algorithm_transform = digest_algorithm_transform_map.get(
            digest_algorithm, xmlsec.Transform.SHA1)

        ref = xmlsec.template.add_reference(signature,
                                            digest_algorithm_transform,
                                            uri=elem_id)
        xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
        xmlsec.template.add_transform(ref, xmlsec.Transform.EXCL_C14N)
        key_info = xmlsec.template.ensure_key_info(signature)
        xmlsec.template.add_x509_data(key_info)

        dsig_ctx = xmlsec.SignatureContext()
        sign_key = xmlsec.Key.from_memory(key, xmlsec.KeyFormat.PEM, None)
        sign_key.load_cert_from_memory(cert, xmlsec.KeyFormat.PEM)

        dsig_ctx.key = sign_key
        dsig_ctx.sign(signature)

        return OneLogin_Saml2_XML.to_string(elem)
示例#45
0
    def validate_node_sign(signature_node,
                           elem,
                           cert=None,
                           fingerprint=None,
                           fingerprintalg='sha1',
                           validatecert=False,
                           debug=False):
        """
        Validates a signature node.

        :param signature_node: The signature node
        :type: Node

        :param xml: The element we should validate
        :type: Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if (cert is None or cert == '') and fingerprint:
            x509_certificate_nodes = OneLogin_Saml2_XML.query(
                signature_node,
                '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate')
            if len(x509_certificate_nodes) > 0:
                x509_certificate_node = x509_certificate_nodes[0]
                x509_cert_value = OneLogin_Saml2_XML.element_text(
                    x509_certificate_node)
                x509_cert_value_formatted = OneLogin_Saml2_Utils.format_cert(
                    x509_cert_value)
                x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint(
                    x509_cert_value_formatted, fingerprintalg)
                if fingerprint == x509_fingerprint_value:
                    cert = x509_cert_value_formatted

        if cert is None or cert == '':
            raise OneLogin_Saml2_Error(
                'Could not validate node signature: No certificate provided.',
                OneLogin_Saml2_Error.CERT_NOT_FOUND)

        # Check if Reference URI is empty
        reference_elem = OneLogin_Saml2_XML.query(signature_node,
                                                  '//ds:Reference')
        if len(reference_elem) > 0:
            if reference_elem[0].get('URI') == '':
                reference_elem[0].set(
                    'URI', '#%s' % signature_node.getparent().get('ID'))

        if validatecert:
            manager = xmlsec.KeysManager()
            manager.load_cert_from_memory(cert, xmlsec.KeyFormat.CERT_PEM,
                                          xmlsec.KeyDataType.TRUSTED)
            dsig_ctx = xmlsec.SignatureContext(manager)
        else:
            dsig_ctx = xmlsec.SignatureContext()
            dsig_ctx.key = xmlsec.Key.from_memory(cert,
                                                  xmlsec.KeyFormat.CERT_PEM,
                                                  None)

        dsig_ctx.set_enabled_key_data([xmlsec.KeyData.X509])

        try:
            dsig_ctx.verify(signature_node)
        except Exception as err:
            raise OneLogin_Saml2_ValidationError(
                'Signature validation failed. SAML Response rejected. %s',
                OneLogin_Saml2_ValidationError.INVALID_SIGNATURE, str(err))

        return True
    def parse(idp_metadata):
        """
        Parse the Identity Provider metadata and returns a dict with extracted data
        If there are multiple IDPSSODescriptor it will only parse the first
        :param idp_metadata: XML of the Identity Provider Metadata.
        :type idp_metadata: string
        :param url: If true and the URL is HTTPs, the cert of the domain is checked.
        :type url: bool
        :returns: settings dict with extracted data
        :rtype: string
        """
        data = {}

        dom = OneLogin_Saml2_XML.to_etree(idp_metadata)
        entity_descriptor_nodes = OneLogin_Saml2_XML.query(dom, '//md:EntityDescriptor')

        idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = idp_x509_cert = None

        if len(entity_descriptor_nodes) > 0:
            for entity_descriptor_node in entity_descriptor_nodes:
                idp_descriptor_nodes = OneLogin_Saml2_XML.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_XML.query(idp_descriptor_node, './md:NameIDFormat')
                    if len(name_id_format_nodes) > 0:
                        idp_name_id_format = name_id_format_nodes[0].text

                    sso_nodes = OneLogin_Saml2_XML.query(idp_descriptor_node, "./md:SingleSignOnService[@Binding='%s']" % OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT)
                    if len(sso_nodes) > 0:
                        idp_sso_url = sso_nodes[0].get('Location', None)

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

                    cert_nodes = OneLogin_Saml2_XML.query(idp_descriptor_node, "./md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
                    if len(cert_nodes) > 0:
                        idp_x509_cert = cert_nodes[0].text

                    data['idp'] = {}

                    if idp_entity_id is not None:
                        data['idp']['entityId'] = idp_entity_id
                    if idp_sso_url is not None:
                        data['idp']['singleLogoutService'] = {}
                        data['idp']['singleLogoutService']['url'] = idp_sso_url
                    if idp_slo_url is not None:
                        data['idp']['singleLogoutService'] = {}
                        data['idp']['singleLogoutService']['url'] = idp_slo_url
                    if idp_x509_cert is not None:
                        data['idp']['x509cert'] = idp_x509_cert

                    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

                    break
        return data
示例#47
0
    def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
        """
        Adds signature key and senders certificate to an element (Message or
        Assertion).

        :param xml: The element we should sign
        :type: string | Document

        :param key: The private key
        :type: string

        :param cert: The public
        :type: string

        :param debug: Activate the xmlsec debug
        :type: bool

        :param sign_algorithm: Signature algorithm method
        :type sign_algorithm: string
        """
        if xml is None or xml == '':
            raise Exception('Empty string supplied as input')

        elem = OneLogin_Saml2_XML.to_etree(xml)
        xmlsec.enable_debug_trace(debug)
        xmlsec.tree.add_ids(elem, ["ID"])
        # Sign the metadata with our private key.
        sign_algorithm_transform_map = {
            OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.Transform.DSA_SHA1,
            OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.Transform.RSA_SHA1,
            OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.Transform.RSA_SHA256,
            OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.Transform.RSA_SHA384,
            OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.Transform.RSA_SHA512
        }
        sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.Transform.RSA_SHA1)

        signature = xmlsec.template.create(elem, xmlsec.Transform.EXCL_C14N, sign_algorithm_transform, ns='ds')

        issuer = OneLogin_Saml2_XML.query(elem, '//saml:Issuer')
        if len(issuer) > 0:
            issuer = issuer[0]
            issuer.addnext(signature)
        else:
            elem[0].insert(0, signature)

        elem_id = elem.get('ID', None)
        if elem_id:
            elem_id = '#' + elem_id

        ref = xmlsec.template.add_reference(signature, xmlsec.Transform.SHA1, uri=elem_id)
        xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
        xmlsec.template.add_transform(ref, xmlsec.Transform.EXCL_C14N)
        key_info = xmlsec.template.ensure_key_info(signature)
        xmlsec.template.add_x509_data(key_info)

        dsig_ctx = xmlsec.SignatureContext()
        sign_key = xmlsec.Key.from_memory(key, xmlsec.KeyFormat.PEM, None)
        sign_key.load_cert_from_memory(cert, xmlsec.KeyFormat.PEM)

        dsig_ctx.key = sign_key
        dsig_ctx.sign(signature)

        return OneLogin_Saml2_XML.to_string(elem)
    def parse(
            idp_metadata,
            required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            index=0):
        """
        Parses the Identity Provider metadata and return a dict with extracted data.

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

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

        Parses 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 index: If the metadata contains more than 1 certificate, use index to get the right certificate.
        :type index: number

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

        dom = OneLogin_Saml2_XML.to_etree(idp_metadata)
        entity_descriptor_nodes = OneLogin_Saml2_XML.query(dom, '//md:EntityDescriptor')

        idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = idp_x509_cert = None

        if len(entity_descriptor_nodes) > 0:
            for entity_descriptor_node in entity_descriptor_nodes:
                idp_descriptor_nodes = OneLogin_Saml2_XML.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_XML.query(idp_descriptor_node, './md:NameIDFormat')
                    if len(name_id_format_nodes) > 0:
                        idp_name_id_format = name_id_format_nodes[0].text

                    sso_nodes = OneLogin_Saml2_XML.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_XML.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)

                    # Attempt to extract the cert/public key to be used for
                    # verifying signatures (as opposed to extracing a key to be
                    # used for encryption), by specifying `use=signing` in the
                    # XPath expression. If that does not yield a cert, retry
                    # using a more relaxed XPath expression (the `use` attribute
                    # is optional according to the saml-metadata-2.0-os spec).
                    cert_nodes = OneLogin_Saml2_XML.query(
                        idp_descriptor_node,
                        "./md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
                    )

                    if not cert_nodes:
                        cert_nodes = OneLogin_Saml2_XML.query(
                            idp_descriptor_node,
                            "./md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
                        )

                    if len(cert_nodes) > 0:
                        idp_x509_cert = OneLogin_Saml2_Utils.format_cert(cert_nodes[index].text, False)

                    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 idp_x509_cert is not None:
                        data['idp']['x509cert'] = idp_x509_cert

                    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

                    break
        return data
示例#49
0
    def validate_sign(xml,
                      cert=None,
                      fingerprint=None,
                      fingerprintalg='sha1',
                      validatecert=False,
                      debug=False,
                      xpath=None):
        """
        Validates a signature (Message or Assertion).

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param xpath: The xpath of the signed element
        :type: string

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if xml is None or xml == '':
            raise Exception('Empty string supplied as input')

        elem = OneLogin_Saml2_XML.to_etree(xml)
        xmlsec.enable_debug_trace(debug)
        xmlsec.tree.add_ids(elem, ["ID"])

        if xpath:
            signature_nodes = OneLogin_Saml2_XML.query(elem, xpath)
        else:
            signature_nodes = OneLogin_Saml2_XML.query(
                elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH)

            if len(signature_nodes) == 0:
                signature_nodes = OneLogin_Saml2_XML.query(
                    elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)

        if len(signature_nodes) == 1:
            signature_node = signature_nodes[0]
            # Raises expection if invalid
            return OneLogin_Saml2_Utils.validate_node_sign(
                signature_node,
                elem,
                cert,
                fingerprint,
                fingerprintalg,
                validatecert,
                debug,
                raise_exceptions=True)
        else:
            raise OneLogin_Saml2_ValidationError(
                'Expected exactly one signature node; got {}.'.format(
                    len(signature_nodes)),
                OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_SIGNATURES)
    def parse(
            idp_metadata,
            required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT):
        """
        Parses the Identity Provider metadata and return a dict with extracted data.

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

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

        Parses 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

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

        dom = OneLogin_Saml2_XML.to_etree(idp_metadata)
        entity_descriptor_nodes = OneLogin_Saml2_XML.query(dom, '//md:EntityDescriptor')

        idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = idp_x509_cert = None

        if len(entity_descriptor_nodes) > 0:
            for entity_descriptor_node in entity_descriptor_nodes:
                idp_descriptor_nodes = OneLogin_Saml2_XML.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_XML.query(idp_descriptor_node, './md:NameIDFormat')
                    if len(name_id_format_nodes) > 0:
                        idp_name_id_format = name_id_format_nodes[0].text

                    sso_nodes = OneLogin_Saml2_XML.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_XML.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)

                    cert_nodes = OneLogin_Saml2_XML.query(idp_descriptor_node, "./md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
                    if len(cert_nodes) > 0:
                        idp_x509_cert = cert_nodes[0].text

                    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 idp_x509_cert is not None:
                        data['idp']['x509cert'] = idp_x509_cert

                    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

                    break
        return data