예제 #1
0
    def __build_signature(self,
                          saml_data,
                          relay_state,
                          saml_type,
                          sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
        """
        Builds the Signature
        :param saml_data: The SAML Data
        :type saml_data: string

        :param relay_state: The target URL the user should be redirected to
        :type relay_state: string

        :param saml_type: The target URL the user should be redirected to
        :type saml_type: string  SAMLRequest | SAMLResponse

        :param sign_algorithm: Signature algorithm method
        :type sign_algorithm: string
        """
        assert saml_type in ['SAMLRequest', 'SAMLResponse']

        # Load the key into the xmlsec context
        key = self.__settings.get_sp_key()

        if not key:
            raise OneLogin_Saml2_Error(
                "Trying to sign the %s but can't load the SP private key" %
                saml_type, OneLogin_Saml2_Error.SP_CERTS_NOT_FOUND)

        xmlsec.initialize()

        dsig_ctx = xmlsec.DSigCtx()
        dsig_ctx.signKey = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem,
                                                 None)

        saml_data_str = '%s=%s' % (saml_type, quote_plus(saml_data))
        relay_state_str = 'RelayState=%s' % quote_plus(relay_state)
        alg_str = 'SigAlg=%s' % quote_plus(sign_algorithm)

        sign_data = [saml_data_str, relay_state_str, alg_str]
        msg = '&'.join(sign_data)

        # Sign the metadata with our private key.
        sign_algorithm_transform_map = {
            OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1,
            OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1,
            OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256,
            OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384,
            OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512
        }
        sign_algorithm_transform = sign_algorithm_transform_map.get(
            sign_algorithm, xmlsec.TransformRsaSha1)

        signature = dsig_ctx.signBinary(str(msg), sign_algorithm_transform)
        return b64encode(signature)
예제 #2
0
파일: auth.py 프로젝트: xarus01/python-saml
    def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name_id_format=None):
        """
        Initiates the SLO process.

        :param return_to: Optional argument. The target URL the user should be redirected to after logout.
        :type return_to: string

        :param name_id: The NameID that will be set in the LogoutRequest.
        :type name_id: string

        :param session_index: SessionIndex that identifies the session of the user.
        :type session_index: string

        :param nq: IDP Name Qualifier
        :type: string

        :param name_id_format: The NameID Format that will be set in the LogoutRequest.
        :type: string

        :returns: Redirection url
        """
        slo_url = self.get_slo_url()
        if slo_url is None:
            raise OneLogin_Saml2_Error(
                'The IdP does not support Single Log Out',
                OneLogin_Saml2_Error.SAML_SINGLE_LOGOUT_NOT_SUPPORTED
            )

        if name_id is None and self.__nameid is not None:
            name_id = self.__nameid
        if name_id_format is None and self.__nameid_format is not None:
            name_id_format = self.__nameid_format

        logout_request = OneLogin_Saml2_Logout_Request(
            self.__settings,
            name_id=name_id,
            session_index=session_index,
            nq=nq,
            name_id_format=name_id_format
        )
        self.__last_request = logout_request.get_xml()
        self.__last_request_id = logout_request.id
        saml_request = logout_request.get_request()

        parameters = {'SAMLRequest': logout_request.get_request()}
        if return_to is not None:
            parameters['RelayState'] = return_to
        else:
            parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self.__request_data)

        security = self.__settings.get_security_data()
        if security.get('logoutRequestSigned', False):
            parameters['SigAlg'] = security['signatureAlgorithm']
            parameters['Signature'] = self.build_request_signature(saml_request, parameters['RelayState'], security['signatureAlgorithm'])
        return self.redirect_to(slo_url, parameters)
예제 #3
0
    def process_slo(self, keep_local_session=False, request_id=None, delete_session_cb=None):
        """
        Process the SAML Logout Response / Logout Request sent by the IdP.

        :param keep_local_session: When false will destroy the local session, otherwise will destroy it
        :type keep_local_session: bool

        :param request_id: The ID of the LogoutRequest sent by this SP to the IdP
        :type request_id: string

        :returns: Redirection url
        """
        self.__errors = []

        if 'get_data' in self.__request_data and 'SAMLResponse' in self.__request_data['get_data']:
            logout_response = OneLogin_Saml2_Logout_Response(self.__settings, self.__request_data['get_data']['SAMLResponse'])
            if not logout_response.is_valid(self.__request_data, request_id):
                self.__errors.append('invalid_logout_response')
                self.__error_reason = logout_response.get_error()
            elif logout_response.get_status() != OneLogin_Saml2_Constants.STATUS_SUCCESS:
                self.__errors.append('logout_not_success')
            elif not keep_local_session:
                OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)

        elif 'get_data' in self.__request_data and 'SAMLRequest' in self.__request_data['get_data']:
            logout_request = OneLogin_Saml2_Logout_Request(self.__settings, self.__request_data['get_data']['SAMLRequest'])
            if not logout_request.is_valid(self.__request_data):
                self.__errors.append('invalid_logout_request')
                self.__error_reason = logout_request.get_error()
            else:
                if not keep_local_session:
                    OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)

                in_response_to = logout_request.id
                response_builder = OneLogin_Saml2_Logout_Response(self.__settings)
                response_builder.build(in_response_to)
                logout_response = response_builder.get_response()

                parameters = {'SAMLResponse': logout_response}
                if 'RelayState' in self.__request_data['get_data']:
                    parameters['RelayState'] = self.__request_data['get_data']['RelayState']

                security = self.__settings.get_security_data()
                if 'logoutResponseSigned' in security and security['logoutResponseSigned']:
                    parameters['SigAlg'] = security['signatureAlgorithm']
                    parameters['Signature'] = self.build_response_signature(logout_response, parameters.get('RelayState', None), security['signatureAlgorithm'])

                return self.redirect_to(self.get_slo_url(), parameters)
        else:
            self.__errors.append('invalid_binding')
            raise OneLogin_Saml2_Error(
                'SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding',
                OneLogin_Saml2_Error.SAML_LOGOUTMESSAGE_NOT_FOUND
            )
예제 #4
0
    def redirect(url, parameters={}, request_data={}):
        """
        Executes a redirection to the provided url (or return the target url).

        :param url: The target url
        :type: string

        :param parameters: Extra parameters to be passed as part of the url
        :type: dict

        :param request_data: The request as a dict
        :type: dict

        :returns: Url
        :rtype: string
        """
        assert isinstance(url, basestring)
        assert isinstance(parameters, dict)

        if url.startswith('/'):
            url = '%s%s' % (
                OneLogin_Saml2_Utils.get_self_url_host(request_data), url)

        # Verify that the URL is to a http or https site.
        if re.search('^https?://', url) is None:
            raise OneLogin_Saml2_Error(
                'Redirect to invalid URL: ' + url,
                OneLogin_Saml2_Error.REDIRECT_INVALID_URL)

        # Add encoded parameters
        if url.find('?') < 0:
            param_prefix = '?'
        else:
            param_prefix = '&'

        for name, value in parameters.items():

            if value is None:
                param = quote_plus(name)
            elif isinstance(value, list):
                param = ''
                for val in value:
                    param += quote_plus(name) + '[]=' + quote_plus(val) + '&'
                if len(param) > 0:
                    param = param[0:-1]
            else:
                param = quote_plus(name) + '=' + quote_plus(value)

            if param:
                url += param_prefix + param
                param_prefix = '&'

        return url
예제 #5
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
예제 #6
0
    def __build_signature(self,
                          data,
                          saml_type,
                          sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
        """
        Builds the Signature
        :param data: The Request data
        :type data: dict

        :param saml_type: The target URL the user should be redirected to
        :type saml_type: string  SAMLRequest | SAMLResponse

        :param sign_algorithm: Signature algorithm method
        :type sign_algorithm: string
        """
        assert saml_type in ('SAMLRequest', 'SAMLResponse')
        key = self.get_settings().get_sp_key()

        if not key:
            raise OneLogin_Saml2_Error(
                "Trying to sign the %s but can't load the SP private key." %
                saml_type, OneLogin_Saml2_Error.SP_CERTS_NOT_FOUND)

        msg = self.__build_sign_query(data[saml_type],
                                      data.get('RelayState', None),
                                      sign_algorithm, saml_type)

        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 = OneLogin_Saml2_Utils.sign_binary(
            msg, key, sign_algorithm_transform,
            self.__settings.is_debug_active())
        data['Signature'] = OneLogin_Saml2_Utils.b64encode(signature)
        data['SigAlg'] = sign_algorithm
예제 #7
0
    def process_response(self, request_id=None):
        """
        Process the SAML Response sent by the IdP.

        :param request_id: Is an optional argument. Is the ID of the AuthNRequest sent by this SP to the IdP.
        :type request_id: string

        :raises: OneLogin_Saml2_Error.SAML_RESPONSE_NOT_FOUND, when a POST with a SAMLResponse is not found
        """
        self.__errors = []
        self.__error_reason = None

        if 'post_data' in self.__request_data and 'SAMLResponse' in self.__request_data[
                'post_data']:
            # AuthnResponse -- HTTP_POST Binding
            response = OneLogin_Saml2_Response(
                self.__settings,
                self.__request_data['post_data']['SAMLResponse'])
            self.__last_response = response.get_xml_document()
            if response.is_valid(self.__request_data, request_id):
                self.__attributes = response.get_attributes()
                self.__nameid = response.get_nameid()
                self.__nameid_format = response.get_nameid_format()
                self.__session_index = response.get_session_index()
                self.__session_expiration = response.get_session_not_on_or_after(
                )
                self.__last_message_id = response.get_id()
                self.__last_assertion_id = response.get_assertion_id()
                self.__last_authn_contexts = response.get_authn_contexts()
                self.__last_assertion_not_on_or_after = response.get_assertion_not_on_or_after(
                )
                self.__authenticated = True

            else:
                self.__errors.append('invalid_response')
                self.__error_reason = response.get_error()

        else:
            self.__errors.append('invalid_binding')
            raise OneLogin_Saml2_Error(
                'SAML Response not found, Only supported HTTP_POST Binding',
                OneLogin_Saml2_Error.SAML_RESPONSE_NOT_FOUND)
예제 #8
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
        """
        error_callback_method = None
        if debug:
            error_callback_method = print_xmlsec_errors
        xmlsec.set_error_callback(error_callback_method)

        xmlsec.addIDs(elem, ["ID"])

        if (cert is None or cert == '') and fingerprint:
            x509_certificate_nodes = OneLogin_Saml2_Utils.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)

        # Check if Reference URI is empty
        # reference_elem = OneLogin_Saml2_Utils.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 cert is None or cert == '':
            raise OneLogin_Saml2_Error(
                'Could not validate node signature: No certificate provided.',
                OneLogin_Saml2_Error.CERT_NOT_FOUND)

        file_cert = OneLogin_Saml2_Utils.write_temp_file(cert)

        if validatecert:
            mngr = xmlsec.KeysMngr()
            mngr.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem,
                          xmlsec.KeyDataTypeTrusted)
            dsig_ctx = xmlsec.DSigCtx(mngr)
        else:
            dsig_ctx = xmlsec.DSigCtx()
            dsig_ctx.signKey = xmlsec.Key.load(file_cert.name,
                                               xmlsec.KeyDataFormatCertPem,
                                               None)

        file_cert.close()

        dsig_ctx.setEnabledKeyData([xmlsec.KeyDataX509])

        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,
                err.__str__())

        return True
예제 #9
0
    def __decrypt_assertion(self, dom):
        """
        Decrypts the Assertion

        :raises: Exception if no private key available

        :param dom: Encrypted Assertion
        :type dom: 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_Utils.query(
            dom, '/samlp:Response/saml:EncryptedAssertion')
        if encrypted_assertion_nodes:
            encrypted_data_nodes = OneLogin_Saml2_Utils.query(
                encrypted_assertion_nodes[0],
                '//saml:EncryptedAssertion/xenc:EncryptedData')
            if encrypted_data_nodes:
                keyinfo = OneLogin_Saml2_Utils.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_Utils.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=debug, inplace=True)
                dom.replace(encrypted_assertion_nodes[0], decrypted)

        return dom
예제 #10
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
예제 #11
0
    def get_sp_metadata(self):
        """
        Gets the SP metadata. The XML representation.

        :returns: SP metadata (xml)
        :rtype: string
        """
        metadata = OneLogin_Saml2_Metadata.builder(
            self.__sp, self.__security['authnRequestsSigned'],
            self.__security['wantAssertionsSigned'],
            self.__security['metadataValidUntil'],
            self.__security['metadataCacheDuration'], self.get_contacts(),
            self.get_organization())

        add_encryption = self.__security[
            'wantNameIdEncrypted'] or self.__security['wantAssertionsEncrypted']

        cert_new = self.get_sp_cert_new()
        metadata = OneLogin_Saml2_Metadata.add_x509_key_descriptors(
            metadata, cert_new, add_encryption)

        cert = self.get_sp_cert()
        metadata = OneLogin_Saml2_Metadata.add_x509_key_descriptors(
            metadata, cert, add_encryption)

        # Sign metadata
        if 'signMetadata' in self.__security and self.__security[
                'signMetadata'] is not False:
            if self.__security['signMetadata'] is True:
                # Use the SP's normal key to sign the metadata:
                if not cert:
                    raise OneLogin_Saml2_Error(
                        'Cannot sign metadata: missing SP public key certificate.',
                        OneLogin_Saml2_Error.PUBLIC_CERT_FILE_NOT_FOUND)
                cert_metadata = cert
                key_metadata = self.get_sp_key()
                if not key_metadata:
                    raise OneLogin_Saml2_Error(
                        'Cannot sign metadata: missing SP private key.',
                        OneLogin_Saml2_Error.PRIVATE_KEY_FILE_NOT_FOUND)
            else:
                # Use a custom key to sign the metadata:
                if ('keyFileName' not in self.__security['signMetadata'] or
                        'certFileName' not in self.__security['signMetadata']):
                    raise OneLogin_Saml2_Error(
                        'Invalid Setting: signMetadata value of the sp is not valid',
                        OneLogin_Saml2_Error.SETTINGS_INVALID_SYNTAX)
                key_file_name = self.__security['signMetadata']['keyFileName']
                cert_file_name = self.__security['signMetadata'][
                    'certFileName']
                key_metadata_file = self.__paths['cert'] + key_file_name
                cert_metadata_file = self.__paths['cert'] + cert_file_name

                try:
                    with open(key_metadata_file, 'r') as f_metadata_key:
                        key_metadata = f_metadata_key.read()
                except IOError:
                    raise OneLogin_Saml2_Error(
                        'Private key file not readable: %s',
                        OneLogin_Saml2_Error.PRIVATE_KEY_FILE_NOT_FOUND,
                        key_metadata_file)

                try:
                    with open(cert_metadata_file, 'r') as f_metadata_cert:
                        cert_metadata = f_metadata_cert.read()
                except IOError:
                    raise OneLogin_Saml2_Error(
                        'Public cert file not readable: %s',
                        OneLogin_Saml2_Error.PUBLIC_CERT_FILE_NOT_FOUND,
                        cert_metadata_file)

            signature_algorithm = self.__security['signatureAlgorithm']
            digest_algorithm = self.__security['digestAlgorithm']

            metadata = OneLogin_Saml2_Metadata.sign_metadata(
                metadata, key_metadata, cert_metadata, signature_algorithm,
                digest_algorithm)

        return metadata
예제 #12
0
 def runTest(self):
     exception = OneLogin_Saml2_Error('test')
     self.assertEqual(exception.message, 'test')
예제 #13
0
    def get_sp_metadata(self):
        """
        Gets the SP metadata. The XML representation.
        :returns: SP metadata (xml)
        :rtype: string
        """
        metadata = MetaDataBuilder(
            self._OneLogin_Saml2_Settings__sp,
            self._OneLogin_Saml2_Settings__security['authnRequestsSigned'],
            self._OneLogin_Saml2_Settings__security['wantAssertionsSigned'],
            self._OneLogin_Saml2_Settings__security['metadataValidUntil'],
            self._OneLogin_Saml2_Settings__security['metadataCacheDuration'],
            self.get_contacts(), self.get_organization())

        ##### SP SPid patch
        ## mapping xml tag
        tagMapping = {
            "md0:AssertionConsumerService": 'assertionConsumerService',
            "md0:AttributeConsumingService": 'attributeConsumingService',
            "md0:ServiceName": 'serviceName',
            "md0:ServiceDescription": 'serviceDescription',
            "md0:RequestedAttribute": 'requestedAttributes',
            "md1:AttributeValue": 'attributeValue'
        }

        ## edit meta data
        ns = {
            'md0': OneLogin_Saml2_Constants.NS_MD,
            'md1': OneLogin_Saml2_Constants.NS_SAML
        }
        xml.etree.ElementTree.register_namespace(
            'md0', OneLogin_Saml2_Constants.NS_MD)
        xml.etree.ElementTree.register_namespace(
            'md1', OneLogin_Saml2_Constants.NS_SAML)

        parsedMetadata = xml.etree.ElementTree.fromstring(metadata)
        SPSSODescriptor = parsedMetadata.find('md0:SPSSODescriptor', ns)

        if not self._OneLogin_Saml2_Settings__security[
                'putMetadataCacheDuration']:
            parsedMetadata.attrib.pop('cacheDuration')

        if not self._OneLogin_Saml2_Settings__security['putMetadataValidUntil']:
            parsedMetadata.attrib.pop('validUntil')

        ## fix AssertionConsumerService index 0
        assertionConsumerService = SPSSODescriptor.find(
            'md0:AssertionConsumerService', ns)
        assertionConsumerService.attrib['index'] = '0'
        assertionConsumerService.set('isDefault', 'true')

        ## fix AttributeConsumingService index 0
        attributeConsumingService = SPSSODescriptor.find(
            'md0:AttributeConsumingService', ns)
        attributeConsumingService.attrib['index'] = '0'
        attributeConsumingService.find('md0:ServiceName', ns).attrib[
            '{http://www.w3.org/XML/1998/namespace}lang'] = self._OneLogin_Saml2_Settings__sp[
                'lang']
        attributeConsumingService.find('md0:ServiceDescription', ns).attrib[
            '{http://www.w3.org/XML/1998/namespace}lang'] = self._OneLogin_Saml2_Settings__sp[
                'lang']

        ## add other AssertionConsumerService
        try:
            for index, value1 in enumerate(self._OneLogin_Saml2_Settings__sp[
                    'otherAssertionConsumerService']):

                #indexValue = str(index+1)
                tag = "md0:AssertionConsumerService"
                element = xml.etree.ElementTree.Element(
                    tag,
                    attrib={
                        'Location': value1[tagMapping[tag]]['url'],
                        'Binding': value1[tagMapping[tag]]['binding'],
                        'index': value1[tagMapping[tag]]['index']
                    })

                SPSSODescriptor.insert(index + 3, element)

                tag1 = "md0:AttributeConsumingService"
                element1 = xml.etree.ElementTree.Element(
                    tag1, attrib={'index': value1[tagMapping[tag]]['index']})
                SPSSODescriptor.append(element1)

                tag2 = "md0:ServiceName"
                element2 = xml.etree.ElementTree.Element(
                    tag2,
                    attrib={
                        'xml:lang': self._OneLogin_Saml2_Settings__sp['lang']
                    })
                element2.text = value1[tagMapping[tag]][tagMapping[tag1]][
                    tagMapping[tag2]]
                element1.append(element2)

                tag2 = "md0:ServiceDescription"
                element2 = xml.etree.ElementTree.Element(
                    tag2,
                    attrib={
                        'xml:lang': self._OneLogin_Saml2_Settings__sp['lang']
                    })
                element2.text = value1[tagMapping[tag]][tagMapping[tag1]][
                    tagMapping[tag2]]
                element1.append(element2)

                tag3 = "md0:RequestedAttribute"
                for index2, value2 in enumerate(value1[tagMapping[tag]][
                        tagMapping[tag1]][tagMapping[tag3]]):
                    if value2['isRequired']:
                        attrRequired = "true"
                    else:
                        attrRequired = "false"
                    attributi = {
                        'Name': value2['name'],
                        'FriendlyName': value2['friendlyName'],
                        'isRequired': attrRequired
                    }
                    element3 = xml.etree.ElementTree.Element(tag3,
                                                             attrib=attributi)
                    element1.append(element3)

                    for index3, value3 in enumerate(
                            value1[tagMapping[tag]][tagMapping[tag1]][
                                tagMapping[tag3]][index2]['attributeValue']):
                        xml.etree.ElementTree.Element(tag3, attrib=attributi)
                        tag4 = "md1:AttributeValue"
                        element4 = xml.etree.ElementTree.Element(tag4)
                        element4.text = value3
                        element3.append(element4)
        except:
            pass

        ## add other singleLogoutService
        try:
            for index, value1 in enumerate(
                    self.
                    _OneLogin_Saml2_Settings__sp['othersSingleLogoutService']):

                #indexValue = str(index+1)
                tag = "md0:SingleLogoutService"
                element = xml.etree.ElementTree.Element(tag,
                                                        attrib={
                                                            'Location':
                                                            value1['url'],
                                                            'Binding':
                                                            value1['binding']
                                                        })

                SPSSODescriptor.insert(index + 1, element)
        except:
            pass

        metadata = xml.etree.ElementTree.tostring(parsedMetadata,
                                                  encoding="unicode")
        ####
        add_encryption = self._OneLogin_Saml2_Settings__security[
            'wantNameIdEncrypted'] or self._OneLogin_Saml2_Settings__security[
                'wantAssertionsEncrypted']

        cert_new = self.get_sp_cert_new()
        metadata = OneLogin_Saml2_Metadata.add_x509_key_descriptors(
            metadata, cert_new, add_encryption)

        cert = self.get_sp_cert()
        metadata = OneLogin_Saml2_Metadata.add_x509_key_descriptors(
            metadata, cert, add_encryption)

        # Sign metadata
        if 'signMetadata' in self._OneLogin_Saml2_Settings__security and self._OneLogin_Saml2_Settings__security[
                'signMetadata'] is not False:
            if self._OneLogin_Saml2_Settings__security['signMetadata'] is True:
                # Use the SP's normal key to sign the metadata:
                if not cert:
                    raise OneLogin_Saml2_Error(
                        'Cannot sign metadata: missing SP public key certificate.',
                        OneLogin_Saml2_Error.PUBLIC_CERT_FILE_NOT_FOUND)
                cert_metadata = cert
                key_metadata = self.get_sp_key()
                if not key_metadata:
                    raise OneLogin_Saml2_Error(
                        'Cannot sign metadata: missing SP private key.',
                        OneLogin_Saml2_Error.PRIVATE_KEY_FILE_NOT_FOUND)
            else:
                # Use a custom key to sign the metadata:
                if ('keyFileName' not in
                        self._OneLogin_Saml2_Settings__security['signMetadata']
                        or 'certFileName' not in self.
                        _OneLogin_Saml2_Settings__security['signMetadata']):
                    raise OneLogin_Saml2_Error(
                        'Invalid Setting: signMetadata value of the sp is not valid',
                        OneLogin_Saml2_Error.SETTINGS_INVALID_SYNTAX)
                key_file_name = self._OneLogin_Saml2_Settings__security[
                    'signMetadata']['keyFileName']
                cert_file_name = self._OneLogin_Saml2_Settings__security[
                    'signMetadata']['certFileName']
                key_metadata_file = self._OneLogin_Saml2_Settings__paths[
                    'cert'] + key_file_name
                cert_metadata_file = self._OneLogin_Saml2_Settings__paths[
                    'cert'] + cert_file_name

                try:
                    with open(key_metadata_file, 'rb') as f_metadata_key:
                        key_metadata = f_metadata_key.read().decode("utf-8")
                except IOError:
                    raise OneLogin_Saml2_Error(
                        'Private key file not readable: %s',
                        OneLogin_Saml2_Error.PRIVATE_KEY_FILE_NOT_FOUND,
                        key_metadata_file)

                try:
                    with open(cert_metadata_file, 'rb') as f_metadata_cert:
                        cert_metadata = f_metadata_cert.read().decode("utf-8")
                except IOError:
                    raise OneLogin_Saml2_Error(
                        'Public cert file not readable: %s',
                        OneLogin_Saml2_Error.PUBLIC_CERT_FILE_NOT_FOUND,
                        cert_metadata_file)

            signature_algorithm = self._OneLogin_Saml2_Settings__security[
                'signatureAlgorithm']
            digest_algorithm = self._OneLogin_Saml2_Settings__security[
                'digestAlgorithm']

            #metadata = OneLogin_Saml2_Metadata.sign_metadata(metadata, key_metadata, cert_metadata, signature_algorithm, digest_algorithm)
            metadata = AddSign(metadata, key_metadata, cert_metadata, False,
                               signature_algorithm, digest_algorithm)

        return metadata
    def is_valid(self, request_data, request_id=None, raise_exceptions=False):
        """
        Determines if the SAML LogoutResponse is valid
        :param request_id: The ID of the LogoutRequest sent by this SP to the IdP
        :type request_id: string
        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        :return: Returns if the SAML LogoutResponse is or not valid
        :rtype: boolean
        """
        self.__error = None
        lowercase_urlencoding = False
        try:
            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data['entityId']
            get_data = request_data['get_data']

            if 'lowercase_urlencoding' in request_data.keys():
                lowercase_urlencoding = request_data['lowercase_urlencoding']

            if self.__settings.is_strict():
                res = OneLogin_Saml2_Utils.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
                if not isinstance(res, Document):
                    raise OneLogin_Saml2_ValidationError(
                        'Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd',
                        OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT
                    )

                security = self.__settings.get_security_data()

                # Check if the InResponseTo of the Logout Response matches the ID of the Logout Request (requestId) if provided
                if request_id is not None and self.document.documentElement.hasAttribute('InResponseTo'):
                    in_response_to = self.document.documentElement.getAttribute('InResponseTo')
                    if request_id != in_response_to:
                        raise OneLogin_Saml2_ValidationError(
                            'The InResponseTo of the Logout Response: %s, does not match the ID of the Logout request sent by the SP: %s' % (in_response_to, request_id),
                            OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO
                        )

                # Check issuer
                issuer = self.get_issuer()
                if issuer is not None and issuer != idp_entity_id:
                    raise OneLogin_Saml2_ValidationError(
                        'Invalid issuer in the Logout Request',
                        OneLogin_Saml2_ValidationError.WRONG_ISSUER
                    )

                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)

                # Check destination
                if self.document.documentElement.hasAttribute('Destination'):
                    destination = self.document.documentElement.getAttribute('Destination')
                    if destination != '':
                        if current_url not in destination:
                            raise OneLogin_Saml2_ValidationError(
                                'The LogoutResponse was received at %s instead of %s' % (current_url, destination),
                                OneLogin_Saml2_ValidationError.WRONG_DESTINATION
                            )

                if security['wantMessagesSigned']:
                    if security['wantValidMessageSignature']:
                        if 'Signature' not in get_data:
                            raise OneLogin_Saml2_ValidationError(
                                'The Message of the Logout Response is not signed and the SP require it',
                                OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE
                            )

            if 'Signature' in get_data:
                if 'SigAlg' not in get_data:
                    sign_alg = OneLogin_Saml2_Constants.RSA_SHA1
                else:
                    sign_alg = get_data['SigAlg']

                signed_query = 'SAMLResponse=%s' % OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SAMLResponse', lowercase_urlencoding=lowercase_urlencoding)
                if 'RelayState' in get_data:
                    signed_query = '%s&RelayState=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'RelayState', lowercase_urlencoding=lowercase_urlencoding))
                signed_query = '%s&SigAlg=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1, lowercase_urlencoding=lowercase_urlencoding))

                exists_x509cert = 'x509cert' in idp_data and idp_data['x509cert']
                exists_multix509sign = 'x509certMulti' in idp_data and \
                    'signing' in idp_data['x509certMulti'] and \
                    idp_data['x509certMulti']['signing']

                if not (exists_x509cert or exists_multix509sign):
                    raise OneLogin_Saml2_Error(
                        'In order to validate the sign on the Logout Response, the x509cert of the IdP is required',
                        OneLogin_Saml2_Error.CERT_NOT_FOUND
                    )
                if exists_multix509sign:
                    for cert in idp_data['x509certMulti']['signing']:
                        if OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg):
                            return True
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. Logout Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
                    )
                else:
                    cert = idp_data['x509cert']

                    if not OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg):
                        raise OneLogin_Saml2_ValidationError(
                            'Signature validation failed. Logout Response rejected',
                            OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
                        )

            return True
        # pylint: disable=R0801
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print err.__str__()
            if raise_exceptions:
                raise err
            return False
예제 #15
0
    def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True):
        """
        Constructs the AuthnRequest object.

        :param settings: OSetting data
        :type return_to: OneLogin_Saml2_Settings

        :param force_authn: Optional argument. When true the AuthNReuqest will set the ForceAuthn='true'.
        :type force_authn: bool

        :param is_passive: Optional argument. When true the AuthNReuqest will set the Ispassive='true'.
        :type is_passive: bool

        :param set_nameid_policy: Optional argument. When true the AuthNReuqest will set a nameIdPolicy element.
        :type set_nameid_policy: bool
        """
        self.__settings = settings

        sp_data = self.__settings.get_sp_data()
        idp_data = self.__settings.get_idp_data()
        security = self.__settings.get_security_data()

        uid = OneLogin_Saml2_Utils.generate_unique_id()
        self.__id = uid
        issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())

        destination = idp_data['singleSignOnService']['url']

        provider_name_str = ''
        organization_data = settings.get_organization()
        if isinstance(organization_data, dict) and organization_data:
            langs = organization_data.keys()
            if 'en-US' in langs:
                lang = 'en-US'
            else:
                lang = langs[0]
            if 'displayname' in organization_data[lang] and organization_data[lang]['displayname'] is not None:
                provider_name_str = "\n" + '    ProviderName="%s"' % organization_data[lang]['displayname']

        force_authn_str = ''
        if force_authn is True:
            force_authn_str = "\n" + '    ForceAuthn="true"'

        is_passive_str = ''
        if is_passive is True:
            is_passive_str = "\n" + '    IsPassive="true"'

        nameid_policy_str = ''
        if set_nameid_policy:
            name_id_policy_format = sp_data['NameIDFormat']
            if 'wantNameIdEncrypted' in security and security['wantNameIdEncrypted']:
                name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED

            nameid_policy_str = """
    <samlp:NameIDPolicy
        Format="%s"
        AllowCreate="true" />""" % name_id_policy_format

        requested_authn_context_str = ''
        if 'requestedAuthnContext' in security.keys() and security['requestedAuthnContext'] is not False:
            authn_comparison = 'exact'
            if 'requestedAuthnContextComparison' in security.keys():
                authn_comparison = security['requestedAuthnContextComparison']

            if security['requestedAuthnContext'] is True:
                requested_authn_context_str = "\n" + """    <samlp:RequestedAuthnContext Comparison="%s">
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
    </samlp:RequestedAuthnContext>""" % authn_comparison
            else:
                requested_authn_context_str = "\n" + '     <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison
                for authn_context in security['requestedAuthnContext']:
                    requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context
                requested_authn_context_str += '    </samlp:RequestedAuthnContext>'

        attr_consuming_service_str = ''
        if 'attributeConsumingService' in sp_data and sp_data['attributeConsumingService']:
            attr_consuming_service_str = 'AttributeConsumingServiceIndex="1"'

        request = """<samlp:AuthnRequest
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="%(id)s"
    Version="2.0"%(provider_name)s%(force_authn_str)s%(is_passive_str)s
    IssueInstant="%(issue_instant)s"
    Destination="%(destination)s"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    AssertionConsumerServiceURL="%(assertion_url)s"
    %(attr_consuming_service_str)s>
    <saml:Issuer>%(entity_id)s</saml:Issuer>%(nameid_policy_str)s%(requested_authn_context_str)s
</samlp:AuthnRequest>""" % \
                  {
                      'id': uid,
                      'provider_name': provider_name_str,
                      'force_authn_str': force_authn_str,
                      'is_passive_str': is_passive_str,
                      'issue_instant': issue_instant,
                      'destination': destination,
                      'assertion_url': sp_data['assertionConsumerService']['url'],
                      'entity_id': sp_data['entityId'],
                      'nameid_policy_str': nameid_policy_str,
                      'requested_authn_context_str': requested_authn_context_str,
                      'attr_consuming_service_str': attr_consuming_service_str
                  }

        #from https://github.com/onelogin/python-saml/pull/78. credit to @tachang
        # Only the urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST binding gets the enveloped signature
        if settings.get_idp_data()['singleSignOnService'].get('binding',
                                                              None) == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' and \
                        security['authnRequestsSigned'] is True:

            log.debug("Generating AuthnRequest using urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST binding")

            if 'signatureAlgorithm' in security:
                key = settings.get_sp_key()
                if not key:
                    raise OneLogin_Saml2_Error("Attempt to sign the AuthnRequest but unable to load the SP private key")
                cert = settings.get_sp_cert()
                if not key:
                    raise OneLogin_Saml2_Error("Attempt to sign the AuthnRequest but unable to load the SP cert")
                doc = parseString(request)
                security_algo = security['signatureAlgorithm']
                digest_method_algo = security['digestMethodAlgorithm']
                self.__authn_request = OneLogin_Saml2_Utils.add_sign_with_id(doc, uid, key, cert,
                                                                             sign_algorithm=security_algo,
                                                                             digest_algorithm=digest_method_algo,
                                                                             debug=False)
                log.debug("Generated AuthnRequest: {}".format(self.__authn_request))
            else:
                self.__authn_request = request

            log.debug("Generated AuthnRequest: {}".format(self.__authn_request))
        else:
            self.__authn_request = request
예제 #16
0
    def __init__(self,
                 settings=None,
                 custom_base_path=None,
                 sp_validation_only=False):
        """
        Initializes the settings:
        - Sets the paths of the different folders
        - Loads settings info from settings file or array/object provided

        :param settings: SAML Toolkit Settings
        :type settings: dict

        :param custom_base_path: Path where are stored the settings file and the cert folder
        :type custom_base_path: string
        """
        self.__sp_validation_only = sp_validation_only
        self.__paths = {}
        self.__strict = False
        self.__debug = False
        self.__sp = {}
        self.__idp = {}
        self.__security = {}
        self.__contacts = {}
        self.__organization = {}
        self.__errors = []

        self.__load_paths(base_path=custom_base_path)
        self.__update_paths(settings)

        if settings is None:
            try:
                valid = self.__load_settings_from_file()
            except Exception as e:
                raise e
            if not valid:
                raise OneLogin_Saml2_Error(
                    'Invalid dict settings at the file: %s',
                    OneLogin_Saml2_Error.SETTINGS_INVALID,
                    ','.join(self.__errors))
            self.__add_default_values()
        elif isinstance(settings, str):
            try:
                valid = self.__load_settings_from_file(settings)
            except Exception as e:
                raise e
            if not valid:
                raise OneLogin_Saml2_Error(
                    'Invalid dict settings at the file: %s',
                    OneLogin_Saml2_Error.SETTINGS_INVALID,
                    ','.join(self.__errors))
            self.__add_default_values()
        elif isinstance(settings, dict):
            if not self.__load_settings_from_dict(settings):
                raise OneLogin_Saml2_Error(
                    'Invalid dict settings: %s',
                    OneLogin_Saml2_Error.SETTINGS_INVALID,
                    ','.join(self.__errors))
        else:
            raise OneLogin_Saml2_Error(
                'Unsupported settings object',
                OneLogin_Saml2_Error.UNSUPPORTED_SETTINGS_OBJECT)

        self.format_idp_cert()
        self.format_sp_cert()
        if 'x509certNew' in self.__sp:
            self.format_sp_cert_new()
        self.format_sp_key()
        if 'x509certMulti' in self.__idp:
            self.format_idp_cert_multi()
예제 #17
0
 def runTest(self):
     exception = OneLogin_Saml2_Error('test')
     self.assertEqual(str(exception), 'test')
예제 #18
0
    def is_valid(self, request_data, raise_exceptions=False):
        """
        Checks if the Logout Request received is valid
        :param request_data: Request Data
        :type request_data: dict
        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        :return: If the Logout Request is or not valid
        :rtype: boolean
        """
        self.__error = None
        lowercase_urlencoding = False
        try:
            dom = fromstring(self.__logout_request, forbid_dtd=True)

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data['entityId']

            if 'get_data' in request_data.keys():
                get_data = request_data['get_data']
            else:
                get_data = {}

            if 'lowercase_urlencoding' in request_data.keys():
                lowercase_urlencoding = request_data['lowercase_urlencoding']

            if self.__settings.is_strict():
                res = OneLogin_Saml2_Utils.validate_xml(
                    dom, 'saml-schema-protocol-2.0.xsd',
                    self.__settings.is_debug_active())
                if not isinstance(res, Document):
                    raise OneLogin_Saml2_ValidationError(
                        'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd',
                        OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT)

                security = self.__settings.get_security_data()

                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(
                    request_data)

                # Check NotOnOrAfter
                if dom.get('NotOnOrAfter', None):
                    na = OneLogin_Saml2_Utils.parse_SAML_to_time(
                        dom.get('NotOnOrAfter'))
                    if na <= OneLogin_Saml2_Utils.now():
                        raise OneLogin_Saml2_ValidationError(
                            'Could not validate timestamp: expired. Check system clock.',
                            OneLogin_Saml2_ValidationError.RESPONSE_EXPIRED)

                # Check destination
                destination = dom.get('Destination')
                if destination:
                    if not OneLogin_Saml2_Utils.normalize_url(
                            url=destination).startswith(
                                OneLogin_Saml2_Utils.normalize_url(
                                    url=current_url)):
                        raise Exception(
                            'The LogoutRequest was received at '
                            '%(currentURL)s instead of %(destination)s' % {
                                'currentURL': current_url,
                                'destination': destination,
                            },
                            OneLogin_Saml2_ValidationError.WRONG_DESTINATION)

                # Check issuer
                issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom)
                if issuer is not None and issuer != idp_entity_id:
                    raise OneLogin_Saml2_ValidationError(
                        'Invalid issuer in the Logout Request (expected %(idpEntityId)s, got %(issuer)s)'
                        % {
                            'idpEntityId': idp_entity_id,
                            'issuer': issuer
                        }, OneLogin_Saml2_ValidationError.WRONG_ISSUER)

                if security['wantMessagesSigned']:
                    if 'Signature' not in get_data:
                        raise OneLogin_Saml2_ValidationError(
                            'The Message of the Logout Request is not signed and the SP require it',
                            OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE)

            if 'Signature' in get_data:
                if 'SigAlg' not in get_data:
                    sign_alg = OneLogin_Saml2_Constants.RSA_SHA1
                else:
                    sign_alg = get_data['SigAlg']

                signed_query = 'SAMLRequest=%s' % OneLogin_Saml2_Utils.get_encoded_parameter(
                    get_data,
                    'SAMLRequest',
                    lowercase_urlencoding=lowercase_urlencoding)
                if 'RelayState' in get_data:
                    signed_query = '%s&RelayState=%s' % (
                        signed_query,
                        OneLogin_Saml2_Utils.get_encoded_parameter(
                            get_data,
                            'RelayState',
                            lowercase_urlencoding=lowercase_urlencoding))
                signed_query = '%s&SigAlg=%s' % (
                    signed_query,
                    OneLogin_Saml2_Utils.get_encoded_parameter(
                        get_data,
                        'SigAlg',
                        OneLogin_Saml2_Constants.RSA_SHA1,
                        lowercase_urlencoding=lowercase_urlencoding))

                exists_x509cert = 'x509cert' in idp_data and idp_data[
                    'x509cert']
                exists_multix509sign = 'x509certMulti' in idp_data and \
                    'signing' in idp_data['x509certMulti'] and \
                    idp_data['x509certMulti']['signing']

                if not (exists_x509cert or exists_multix509sign):
                    raise OneLogin_Saml2_Error(
                        'In order to validate the sign on the Logout Request, the x509cert of the IdP is required',
                        OneLogin_Saml2_Error.CERT_NOT_FOUND)
                if exists_multix509sign:
                    for cert in idp_data['x509certMulti']['signing']:
                        if OneLogin_Saml2_Utils.validate_binary_sign(
                                signed_query, b64decode(get_data['Signature']),
                                cert, sign_alg):
                            return True
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. Logout Request rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)
                else:
                    cert = idp_data['x509cert']

                    if not OneLogin_Saml2_Utils.validate_binary_sign(
                            signed_query, b64decode(get_data['Signature']),
                            cert, sign_alg):
                        raise OneLogin_Saml2_ValidationError(
                            'Signature validation failed. Logout Request rejected',
                            OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)
            return True
        except Exception as err:
            # pylint: disable=R0801sign_alg
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print(err.__str__())
            if raise_exceptions:
                raise err
            return False