Esempio n. 1
0
    def testValidateXML(self):
        """
        Tests the validate_xml method of the OneLogin_Saml2_Utils
        """
        metadata_unloaded = '<xml><EntityDescriptor>'
        self.assertEqual(
            OneLogin_Saml2_Utils.validate_xml(metadata_unloaded,
                                              'saml-schema-metadata-2.0.xsd'),
            'unloaded_xml')

        metadata_invalid = self.file_contents(
            join(self.data_path, 'metadata',
                 'noentity_metadata_settings1.xml'))
        self.assertEqual(
            OneLogin_Saml2_Utils.validate_xml(metadata_invalid,
                                              'saml-schema-metadata-2.0.xsd'),
            'invalid_xml')

        metadata_expired = self.file_contents(
            join(self.data_path, 'metadata', 'expired_metadata_settings1.xml'))
        res = OneLogin_Saml2_Utils.validate_xml(
            metadata_expired, 'saml-schema-metadata-2.0.xsd')
        self.assertTrue(isinstance(res, Document))

        metadata_ok = self.file_contents(
            join(self.data_path, 'metadata', 'metadata_settings1.xml'))
        res = OneLogin_Saml2_Utils.validate_xml(
            metadata_ok, 'saml-schema-metadata-2.0.xsd')
        self.assertTrue(isinstance(res, Document))

        dom = parseString(metadata_ok)
        res = OneLogin_Saml2_Utils.validate_xml(
            dom, 'saml-schema-metadata-2.0.xsd')
        self.assertTrue(isinstance(res, Document))
Esempio n. 2
0
    def testValidateXML(self):
        """
        Tests the validate_xml method of the OneLogin_Saml2_Utils
        """
        metadata_unloaded = '<xml><EntityDescriptor>'
        self.assertEqual(OneLogin_Saml2_Utils.validate_xml(metadata_unloaded, 'saml-schema-metadata-2.0.xsd'), 'unloaded_xml')

        metadata_invalid = self.file_contents(join(self.data_path, 'metadata', 'noentity_metadata_settings1.xml'))
        self.assertEqual(OneLogin_Saml2_Utils.validate_xml(metadata_invalid, 'saml-schema-metadata-2.0.xsd'), 'invalid_xml')

        metadata_expired = self.file_contents(join(self.data_path, 'metadata', 'expired_metadata_settings1.xml'))
        res = OneLogin_Saml2_Utils.validate_xml(metadata_expired, 'saml-schema-metadata-2.0.xsd')
        self.assertTrue(isinstance(res, Document))

        metadata_ok = self.file_contents(join(self.data_path, 'metadata', 'metadata_settings1.xml'))
        res = OneLogin_Saml2_Utils.validate_xml(metadata_ok, 'saml-schema-metadata-2.0.xsd')
        self.assertTrue(isinstance(res, Document))

        metadata_bad_order = self.file_contents(join(self.data_path, 'metadata', 'metadata_bad_order_settings1.xml'))
        res = OneLogin_Saml2_Utils.validate_xml(metadata_bad_order, 'saml-schema-metadata-2.0.xsd')
        self.assertFalse(isinstance(res, Document))

        metadata_signed = self.file_contents(join(self.data_path, 'metadata', 'signed_metadata_settings1.xml'))
        res = OneLogin_Saml2_Utils.validate_xml(metadata_signed, 'saml-schema-metadata-2.0.xsd')
        self.assertTrue(isinstance(res, Document))

        dom = parseString(metadata_ok)
        res = OneLogin_Saml2_Utils.validate_xml(dom, 'saml-schema-metadata-2.0.xsd')
        self.assertTrue(isinstance(res, Document))
Esempio n. 3
0
    def test_start_authentication(self, name, service_provider,
                                  identity_providers):
        configuration = create_autospec(spec=SAMLConfiguration)
        configuration.get_debug = MagicMock(return_value=False)
        configuration.get_strict = MagicMock(return_value=False)
        configuration.get_service_provider = MagicMock(
            return_value=service_provider)
        configuration.get_identity_providers = MagicMock(
            return_value=identity_providers)
        onelogin_configuration = SAMLOneLoginConfiguration(configuration)
        authentication_manager = SAMLAuthenticationManager(
            onelogin_configuration, SAMLSubjectParser())

        with self.app.test_request_context('/'):
            result = authentication_manager.start_authentication(
                self._db, fixtures.IDP_1_ENTITY_ID, '')

            query_items = urlparse.parse_qs(urlparse.urlsplit(result).query)
            saml_request = query_items['SAMLRequest'][0]
            decoded_saml_request = OneLogin_Saml2_Utils.decode_base64_and_inflate(
                saml_request)

            validation_result = OneLogin_Saml2_Utils.validate_xml(
                decoded_saml_request, 'saml-schema-protocol-2.0.xsd', False)
            assert isinstance(validation_result, Document)

            saml_request_dom = fromstring(decoded_saml_request)

            acs_url = saml_request_dom.get('AssertionConsumerServiceURL')
            eq_(acs_url,
                SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.acs_service.url)

            acs_binding = saml_request_dom.get('ProtocolBinding')
            eq_(
                acs_binding, SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.
                acs_service.binding.value)

            sso_url = saml_request_dom.get('Destination')
            eq_(sso_url, IDENTITY_PROVIDERS[0].sso_service.url)

            name_id_policy_nodes = OneLogin_Saml2_Utils.query(
                saml_request_dom, './samlp:NameIDPolicy')

            assert name_id_policy_nodes is not None
            eq_(len(name_id_policy_nodes), 1)

            name_id_policy_node = name_id_policy_nodes[0]
            name_id_format = name_id_policy_node.get('Format')

            eq_(name_id_format,
                SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.name_id_format)
Esempio n. 4
0
    def validate_metadata(self, xml):
        """
        Validates an XML SP Metadata.

        :param xml: Metadata's XML that will be validate
        :type xml: string

        :returns: The list of found errors
        :rtype: list
        """

        assert isinstance(xml, basestring)

        if len(xml) == 0:
            raise Exception('Empty string supplied as input')

        errors = []
        res = OneLogin_Saml2_Utils.validate_xml(
            xml, 'saml-schema-metadata-2.0.xsd', self.__debug)
        if not isinstance(res, Document):
            errors.append(res)
        else:
            dom = res
            element = dom.documentElement
            if element.tagName not in 'md:EntityDescriptor':
                errors.append('noEntityDescriptor_xml')
            else:
                if len(element.getElementsByTagName(
                        'md:SPSSODescriptor')) != 1:
                    errors.append('onlySPSSODescriptor_allowed_xml')
                else:
                    valid_until = cache_duration = expire_time = None

                    if element.hasAttribute('validUntil'):
                        valid_until = OneLogin_Saml2_Utils.parse_SAML_to_time(
                            element.getAttribute('validUntil'))
                    if element.hasAttribute('cacheDuration'):
                        cache_duration = element.getAttribute('cacheDuration')

                    expire_time = OneLogin_Saml2_Utils.get_expire_time(
                        cache_duration, valid_until)
                    if expire_time is not None and int(
                            time()) > int(expire_time):
                        errors.append('expired_xml')

        # TODO: Validate Sign

        return errors
Esempio n. 5
0
    def validate_metadata(self, xml):
        """
        Validates an XML SP Metadata.

        :param xml: Metadata's XML that will be validate
        :type xml: string

        :returns: The list of found errors
        :rtype: list
        """

        assert isinstance(xml, basestring)

        if len(xml) == 0:
            raise Exception('Empty string supplied as input')

        errors = []
        res = OneLogin_Saml2_Utils.validate_xml(xml, 'saml-schema-metadata-2.0.xsd', self.__debug)
        if not isinstance(res, Document):
            errors.append(res)
        else:
            dom = res
            element = dom.documentElement
            if element.tagName not in 'md:EntityDescriptor':
                errors.append('noEntityDescriptor_xml')
            else:
                if len(element.getElementsByTagName('md:SPSSODescriptor')) != 1:
                    errors.append('onlySPSSODescriptor_allowed_xml')
                else:
                    valid_until = cache_duration = expire_time = None

                    if element.hasAttribute('validUntil'):
                        valid_until = OneLogin_Saml2_Utils.parse_SAML_to_time(element.getAttribute('validUntil'))
                    if element.hasAttribute('cacheDuration'):
                        cache_duration = element.getAttribute('cacheDuration')

                    expire_time = OneLogin_Saml2_Utils.get_expire_time(cache_duration, valid_until)
                    if expire_time is not None and int(datetime.now().strftime('%s')) > int(expire_time):
                        errors.append('expired_xml')

        # TODO: Validate Sign

        return errors
Esempio n. 6
0
    def is_valid(self, request_data):
        """
        Checks if the Logout Request recieved is valid
        :param request_data: Request Data
        :type request_data: dict

        :return: If the Logout Request is or not valid
        :rtype: boolean
        """
        self.__error = None
        try:
            dom = fromstring(self.__logout_request)

            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 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 Exception(
                        'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd'
                    )

                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 Exception(
                            'Timing issues (please check your clock settings)')

                # Check destination
                if dom.get('Destination', None):
                    destination = dom.get('Destination')
                    if destination != '':
                        if current_url not in destination:
                            raise Exception(
                                'The LogoutRequest was received at '
                                '%(currentURL)s instead of %(destination)s' % {
                                    'currentURL': current_url,
                                    'destination': destination,
                                })

                # Check issuer
                issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom)
                if issuer is not None and issuer != idp_entity_id:
                    raise Exception('Invalid issuer in the Logout Request')

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

            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' % quote_plus(
                    get_data['SAMLRequest'])
                if 'RelayState' in get_data:
                    signed_query = '%s&RelayState=%s' % (
                        signed_query, quote_plus(get_data['RelayState']))
                signed_query = '%s&SigAlg=%s' % (signed_query,
                                                 quote_plus(sign_alg))

                if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
                    raise Exception(
                        'In order to validate the sign on the Logout Request, the x509cert of the IdP is required'
                    )
                cert = idp_data['x509cert']

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

            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__()
            return False
Esempio n. 7
0
    def is_valid(self, request_data, request_id=None):
        """
        Validates the response object.

        :param request_data: Request Data
        :type request_data: dict

        :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP
        :type request_id: string

        :returns: True if the SAML Response is valid, False if not
        :rtype: bool
        """
        self.__error = None
        try:
            # Checks SAML version
            if self.document.get('Version', None) != '2.0':
                raise Exception('Unsupported SAML version')

            # Checks that ID exists
            if self.document.get('ID', None) is None:
                raise Exception('Missing ID attribute on SAML Response')

            # Checks that the response only has one assertion
            if not self.validate_num_assertions():
                raise Exception('SAML Response must contain 1 assertion')

            # Checks that the response has the SUCCESS status
            self.check_status()

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data.get('entityId', '')
            sp_data = self.__settings.get_sp_data()
            sp_entity_id = sp_data.get('entityId', '')

            sign_nodes = self.__query('//ds:Signature')

            signed_elements = []
            for sign_node in sign_nodes:
                signed_elements.append(sign_node.getparent().tag)

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

                security = self.__settings.get_security_data()
                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(
                    request_data)

                # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
                in_response_to = self.document.get('InResponseTo', None)
                if in_response_to and request_id:
                    if in_response_to != request_id:
                        raise Exception(
                            'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s'
                            % (in_response_to, request_id))

                if not self.encrypted and security.get(
                        'wantAssertionsEncrypted', False):
                    raise Exception(
                        'The assertion of the Response is not encrypted and the SP require it'
                    )

                if security.get('wantNameIdEncrypted', False):
                    encrypted_nameid_nodes = self.__query_assertion(
                        '/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
                    if len(encrypted_nameid_nodes) == 0:
                        raise Exception(
                            'The NameID of the Response is not encrypted and the SP require it'
                        )

                # Checks that there is at least one AttributeStatement if required
                attribute_statement_nodes = self.__query_assertion(
                    '/saml:AttributeStatement')
                if security.get('wantAttributeStatement',
                                True) and not attribute_statement_nodes:
                    raise Exception(
                        'There is no AttributeStatement on the Response')

                # Validates Assertion timestamps
                if not self.validate_timestamps():
                    raise Exception(
                        'Timing issues (please check your clock settings)')

                encrypted_attributes_nodes = self.__query_assertion(
                    '/saml:AttributeStatement/saml:EncryptedAttribute')
                if encrypted_attributes_nodes:
                    raise Exception(
                        'There is an EncryptedAttribute in the Response and this SP not support them'
                    )

                # Checks destination
                destination = self.document.get('Destination', '')
                if destination:
                    if not destination.startswith(current_url):
                        # TODO: Review if following lines are required, since we can control the
                        # request_data
                        #  current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
                        #  if not destination.startswith(current_url_routed):
                        raise Exception(
                            'The response was received at %s instead of %s' %
                            (current_url, destination))

                # Checks audience
                valid_audiences = self.get_audiences()
                if valid_audiences and sp_entity_id not in valid_audiences:
                    raise Exception(
                        '%s is not a valid audience for this Response' %
                        sp_entity_id)

                # Checks the issuers
                issuers = self.get_issuers()
                for issuer in issuers:
                    if issuer is None or issuer != idp_entity_id:
                        raise Exception(
                            'Invalid issuer in the Assertion/Response')

                # Checks the session Expiration
                session_expiration = self.get_session_not_on_or_after()
                if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now(
                ):
                    raise Exception(
                        'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response'
                    )

                # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
                any_subject_confirmation = False
                subject_confirmation_nodes = self.__query_assertion(
                    '/saml:Subject/saml:SubjectConfirmation')

                for scn in subject_confirmation_nodes:
                    method = scn.get('Method', None)
                    if method and method != OneLogin_Saml2_Constants.CM_BEARER:
                        continue
                    sc_data = scn.find(
                        'saml:SubjectConfirmationData',
                        namespaces=OneLogin_Saml2_Constants.NSMAP)
                    if sc_data is None:
                        continue
                    else:
                        irt = sc_data.get('InResponseTo', None)
                        if irt != in_response_to:
                            continue
                        recipient = sc_data.get('Recipient', None)
                        if recipient and current_url not in recipient:
                            continue
                        nooa = sc_data.get('NotOnOrAfter', None)
                        if nooa:
                            parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nooa)
                            if parsed_nooa <= OneLogin_Saml2_Utils.now():
                                continue
                        nb = sc_data.get('NotBefore', None)
                        if nb:
                            parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nb)
                            if parsed_nb > OneLogin_Saml2_Utils.now():
                                continue
                        any_subject_confirmation = True
                        break

                if not any_subject_confirmation:
                    raise Exception(
                        'A valid SubjectConfirmation was not found on this Response'
                    )

                if security.get('wantAssertionsSigned', False) and (
                        '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML
                ) not in signed_elements:
                    raise Exception(
                        'The Assertion of the Response is not signed and the SP require it'
                    )

                if security.get('wantMessagesSigned', False) and (
                        '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP
                ) not in signed_elements:
                    raise Exception(
                        'The Message of the Response is not signed and the SP require it'
                    )

                if len(signed_elements) > 0:
                    if len(signed_elements) > 2:
                        raise Exception(
                            'Too many Signatures found. SAML Response rejected'
                        )
                    cert = idp_data.get('x509cert', None)
                    fingerprint = idp_data.get('certFingerprint', None)
                    fingerprintalg = idp_data.get('certFingerprintAlgorithm',
                                                  None)

                    # If find a Signature on the Response, validates it checking the original response
                    if '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements:
                        document_to_validate = self.document
                    # Otherwise validates the assertion (decrypted assertion if was encrypted)
                    else:
                        if self.encrypted:
                            document_to_validate = self.decrypted_document
                        else:
                            document_to_validate = self.document
                    if not OneLogin_Saml2_Utils.validate_sign(
                            document_to_validate, cert, fingerprint,
                            fingerprintalg):
                        raise Exception(
                            'Signature validation failed. SAML Response rejected'
                        )
                else:
                    raise Exception(
                        'No Signature found. SAML Response rejected')

            return True
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print err.__str__()
            return False
    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
Esempio n. 9
0
    def is_valid(self, request_data, request_id=None, raise_exceptions=False):
        """
        Validates the response object.

        :param request_data: Request Data
        :type request_data: dict

        :param request_id: Optional argument. The ID of the AuthNRequest 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

        :returns: True if the SAML Response is valid, False if not
        :rtype: bool
        """
        self.__error = None
        try:
            # Checks SAML version
            if self.document.get('Version', None) != '2.0':
                raise OneLogin_Saml2_ValidationError(
                    'Unsupported SAML version',
                    OneLogin_Saml2_ValidationError.UNSUPPORTED_SAML_VERSION
                )

            # Checks that ID exists
            if self.document.get('ID', None) is None:
                raise OneLogin_Saml2_ValidationError(
                    'Missing ID attribute on SAML Response',
                    OneLogin_Saml2_ValidationError.MISSING_ID
                )

            # Checks that the response has the SUCCESS status
            self.check_status()

            # Checks that the response only has one assertion
            if not self.validate_num_assertions():
                raise OneLogin_Saml2_ValidationError(
                    'SAML Response must contain 1 assertion',
                    OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS
                )

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data.get('entityId', '')
            sp_data = self.__settings.get_sp_data()
            sp_entity_id = sp_data.get('entityId', '')

            signed_elements = self.process_signed_elements()

            has_signed_response = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements
            has_signed_assertion = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML in signed_elements

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

                # If encrypted, check also the decrypted document
                if self.encrypted:
                    res = OneLogin_Saml2_Utils.validate_xml(
                        tostring(self.decrypted_document),
                        'saml-schema-protocol-2.0.xsd',
                        self.__settings.is_debug_active()
                    )
                    if not isinstance(res, Document):
                        raise OneLogin_Saml2_ValidationError(
                            no_valid_xml_msg,
                            OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT
                        )

                security = self.__settings.get_security_data()
                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)

                in_response_to = self.document.get('InResponseTo', None)
                if request_id is None and in_response_to is not None and security.get('rejectUnsolicitedResponsesWithInResponseTo', False):
                    raise OneLogin_Saml2_ValidationError(
                        'The Response has an InResponseTo attribute: %s while no InResponseTo was expected' % in_response_to,
                        OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO
                    )

                # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
                if request_id is not None and in_response_to != request_id:
                    raise OneLogin_Saml2_ValidationError(
                        'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id),
                        OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO
                    )

                if not self.encrypted and security.get('wantAssertionsEncrypted', False):
                    raise OneLogin_Saml2_ValidationError(
                        'The assertion of the Response is not encrypted and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_ENCRYPTED_ASSERTION
                    )

                if security.get('wantNameIdEncrypted', False):
                    encrypted_nameid_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
                    if len(encrypted_nameid_nodes) != 1:
                        raise OneLogin_Saml2_ValidationError(
                            'The NameID of the Response is not encrypted and the SP require it',
                            OneLogin_Saml2_ValidationError.NO_ENCRYPTED_NAMEID
                        )

                # Checks that a Conditions element exists
                if not self.check_one_condition():
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion must include a Conditions element',
                        OneLogin_Saml2_ValidationError.MISSING_CONDITIONS
                    )

                # Validates Assertion timestamps
                self.validate_timestamps(raise_exceptions=True)

                # Checks that an AuthnStatement element exists and is unique
                if not self.check_one_authnstatement():
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion must include an AuthnStatement element',
                        OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_AUTHSTATEMENTS
                    )

                # Checks that the response has all of the AuthnContexts that we provided in the request.
                # Only check if failOnAuthnContextMismatch is true and requestedAuthnContext is set to a list.
                requested_authn_contexts = security.get('requestedAuthnContext', True)

                if security.get('failOnAuthnContextMismatch', False) and requested_authn_contexts and requested_authn_contexts is not True:
                    authn_contexts = self.get_authn_contexts()
                    unmatched_contexts = set(requested_authn_contexts).difference(authn_contexts)
                    if unmatched_contexts:
                        raise OneLogin_Saml2_ValidationError(
                            'The AuthnContext "%s" didn\'t include requested context "%s"' % (', '.join(authn_contexts), ', '.join(unmatched_contexts)),
                            OneLogin_Saml2_ValidationError.AUTHN_CONTEXT_MISMATCH
                        )

                # Checks that there is at least one AttributeStatement if required
                attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement')
                if security.get('wantAttributeStatement', True) and not attribute_statement_nodes:
                    raise OneLogin_Saml2_ValidationError(
                        'There is no AttributeStatement on the Response',
                        OneLogin_Saml2_ValidationError.NO_ATTRIBUTESTATEMENT
                    )

                encrypted_attributes_nodes = self.__query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute')
                if encrypted_attributes_nodes:
                    raise OneLogin_Saml2_ValidationError(
                        'There is an EncryptedAttribute in the Response and this SP not support them',
                        OneLogin_Saml2_ValidationError.ENCRYPTED_ATTRIBUTES
                    )

                # Checks destination
                destination = self.document.get('Destination', None)
                if destination:
                    if not destination.startswith(current_url):
                        # TODO: Review if following lines are required, since we can control the
                        # request_data
                        #  current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
                        #  if not destination.startswith(current_url_routed):
                        raise OneLogin_Saml2_ValidationError(
                            'The response was received at %s instead of %s' % (current_url, destination),
                            OneLogin_Saml2_ValidationError.WRONG_DESTINATION
                        )
                elif destination == '':
                    raise OneLogin_Saml2_ValidationError(
                        'The response has an empty Destination value',
                        OneLogin_Saml2_ValidationError.EMPTY_DESTINATION
                    )

                # Checks audience
                valid_audiences = self.get_audiences()
                if valid_audiences and sp_entity_id not in valid_audiences:
                    raise OneLogin_Saml2_ValidationError(
                        '%s is not a valid audience for this Response' % sp_entity_id,
                        OneLogin_Saml2_ValidationError.WRONG_AUDIENCE
                    )

                # Checks the issuers
                issuers = self.get_issuers()
                for issuer in issuers:
                    if issuer is None or issuer != idp_entity_id:
                        raise OneLogin_Saml2_ValidationError(
                            'Invalid issuer in the Assertion/Response (expected %(idpEntityId)s, got %(issuer)s)' %
                            {
                                'idpEntityId': idp_entity_id,
                                'issuer': issuer
                            },
                            OneLogin_Saml2_ValidationError.WRONG_ISSUER
                        )

                # Checks the session Expiration
                session_expiration = self.get_session_not_on_or_after()
                if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now():
                    raise OneLogin_Saml2_ValidationError(
                        'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response',
                        OneLogin_Saml2_ValidationError.SESSION_EXPIRED
                    )

                # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
                any_subject_confirmation = False
                subject_confirmation_nodes = self.__query_assertion('/saml:Subject/saml:SubjectConfirmation')

                for scn in subject_confirmation_nodes:
                    method = scn.get('Method', None)
                    if method and method != OneLogin_Saml2_Constants.CM_BEARER:
                        continue
                    sc_data = scn.find('saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP)
                    if sc_data is None:
                        continue
                    else:
                        irt = sc_data.get('InResponseTo', None)
                        if (in_response_to is None and irt is not None and
                           security.get('rejectUnsolicitedResponsesWithInResponseTo', False)) or \
                           in_response_to and irt and irt != in_response_to:
                            continue
                        recipient = sc_data.get('Recipient', None)
                        if recipient and current_url not in recipient:
                            continue
                        nooa = sc_data.get('NotOnOrAfter', None)
                        if nooa:
                            parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa)
                            if parsed_nooa <= OneLogin_Saml2_Utils.now():
                                continue
                        nb = sc_data.get('NotBefore', None)
                        if nb:
                            parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(nb)
                            if parsed_nb > OneLogin_Saml2_Utils.now():
                                continue

                        if nooa:
                            self.valid_scd_not_on_or_after = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa)

                        any_subject_confirmation = True
                        break

                if not any_subject_confirmation:
                    raise OneLogin_Saml2_ValidationError(
                        'A valid SubjectConfirmation was not found on this Response',
                        OneLogin_Saml2_ValidationError.WRONG_SUBJECTCONFIRMATION
                    )

                if security.get('wantAssertionsSigned', False) and not has_signed_assertion:
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion of the Response is not signed and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_SIGNED_ASSERTION
                    )

                if security.get('wantMessagesSigned', False) and not has_signed_response:
                    raise OneLogin_Saml2_ValidationError(
                        'The Message of the Response is not signed and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE
                    )

            if not signed_elements or (not has_signed_response and not has_signed_assertion):
                raise OneLogin_Saml2_ValidationError(
                    'No Signature found. SAML Response rejected',
                    OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND
                )
            else:
                cert = idp_data.get('x509cert', None)
                fingerprint = idp_data.get('certFingerprint', None)
                fingerprintalg = idp_data.get('certFingerprintAlgorithm', None)

                multicerts = None
                if 'x509certMulti' in idp_data and 'signing' in idp_data['x509certMulti'] and idp_data['x509certMulti']['signing']:
                    multicerts = idp_data['x509certMulti']['signing']

                # If find a Signature on the Response, validates it checking the original response
                if has_signed_response and not OneLogin_Saml2_Utils.validate_sign(self.document, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False):
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. SAML Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
                    )

                document_check_assertion = self.decrypted_document if self.encrypted else self.document
                if has_signed_assertion and not OneLogin_Saml2_Utils.validate_sign(document_check_assertion, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False):
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. SAML Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
                    )

            return True
        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
Esempio n. 10
0
    def is_valid(self, request_data, request_id=None, raise_exceptions=False):
        """
        Validates the response object.

        :param request_data: Request Data
        :type request_data: dict

        :param request_id: Optional argument. The ID of the AuthNRequest 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

        :returns: True if the SAML Response is valid, False if not
        :rtype: bool
        """
        self.__error = None
        try:
            # Checks SAML version
            if self.document.get('Version', None) != '2.0':
                raise OneLogin_Saml2_ValidationError(
                    'Unsupported SAML version',
                    OneLogin_Saml2_ValidationError.UNSUPPORTED_SAML_VERSION)

            # Checks that ID exists
            if self.document.get('ID', None) is None:
                raise OneLogin_Saml2_ValidationError(
                    'Missing ID attribute on SAML Response',
                    OneLogin_Saml2_ValidationError.MISSING_ID)

            # Checks that the response has the SUCCESS status
            self.check_status()

            # Checks that the response only has one assertion
            if not self.validate_num_assertions():
                raise OneLogin_Saml2_ValidationError(
                    'SAML Response must contain 1 assertion',
                    OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS)

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data.get('entityId', '')
            sp_data = self.__settings.get_sp_data()
            sp_entity_id = sp_data.get('entityId', '')

            signed_elements = self.process_signed_elements()

            has_signed_response = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements
            has_signed_assertion = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML in signed_elements

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

                # If encrypted, check also the decrypted document
                if self.encrypted:
                    res = OneLogin_Saml2_Utils.validate_xml(
                        tostring(self.decrypted_document),
                        'saml-schema-protocol-2.0.xsd',
                        self.__settings.is_debug_active())
                    if not isinstance(res, Document):
                        raise OneLogin_Saml2_ValidationError(
                            no_valid_xml_msg,
                            OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT)

                security = self.__settings.get_security_data()
                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(
                    request_data)

                in_response_to = self.document.get('InResponseTo', None)
                if request_id is None and in_response_to is not None and security.get(
                        'rejectUnsolicitedResponsesWithInResponseTo', False):
                    raise OneLogin_Saml2_ValidationError(
                        'The Response has an InResponseTo attribute: %s while no InResponseTo was expected'
                        % in_response_to,
                        OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO)

                # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
                if request_id is not None and in_response_to != request_id:
                    raise OneLogin_Saml2_ValidationError(
                        'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s'
                        % (in_response_to, request_id),
                        OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO)

                if not self.encrypted and security.get(
                        'wantAssertionsEncrypted', False):
                    raise OneLogin_Saml2_ValidationError(
                        'The assertion of the Response is not encrypted and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_ENCRYPTED_ASSERTION)

                if security.get('wantNameIdEncrypted', False):
                    encrypted_nameid_nodes = self.__query_assertion(
                        '/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
                    if len(encrypted_nameid_nodes) != 1:
                        raise OneLogin_Saml2_ValidationError(
                            'The NameID of the Response is not encrypted and the SP require it',
                            OneLogin_Saml2_ValidationError.NO_ENCRYPTED_NAMEID)

                # Checks that a Conditions element exists
                if not self.check_one_condition():
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion must include a Conditions element',
                        OneLogin_Saml2_ValidationError.MISSING_CONDITIONS)

                # Validates Assertion timestamps
                self.validate_timestamps(raise_exceptions=True)

                # Checks that an AuthnStatement element exists and is unique
                if not self.check_one_authnstatement():
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion must include an AuthnStatement element',
                        OneLogin_Saml2_ValidationError.
                        WRONG_NUMBER_OF_AUTHSTATEMENTS)

                # Checks that there is at least one AttributeStatement if required
                attribute_statement_nodes = self.__query_assertion(
                    '/saml:AttributeStatement')
                if security.get('wantAttributeStatement',
                                True) and not attribute_statement_nodes:
                    raise OneLogin_Saml2_ValidationError(
                        'There is no AttributeStatement on the Response',
                        OneLogin_Saml2_ValidationError.NO_ATTRIBUTESTATEMENT)

                encrypted_attributes_nodes = self.__query_assertion(
                    '/saml:AttributeStatement/saml:EncryptedAttribute')
                if encrypted_attributes_nodes:
                    raise OneLogin_Saml2_ValidationError(
                        'There is an EncryptedAttribute in the Response and this SP not support them',
                        OneLogin_Saml2_ValidationError.ENCRYPTED_ATTRIBUTES)

                # Checks destination
                destination = self.document.get('Destination', None)
                if destination:
                    if not destination.startswith(current_url):
                        # TODO: Review if following lines are required, since we can control the
                        # request_data
                        #  current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
                        #  if not destination.startswith(current_url_routed):
                        raise OneLogin_Saml2_ValidationError(
                            'The response was received at %s instead of %s' %
                            (current_url, destination),
                            OneLogin_Saml2_ValidationError.WRONG_DESTINATION)
                elif destination == '':
                    raise OneLogin_Saml2_ValidationError(
                        'The response has an empty Destination value',
                        OneLogin_Saml2_ValidationError.EMPTY_DESTINATION)

                # Checks audience
                valid_audiences = self.get_audiences()
                if valid_audiences and sp_entity_id not in valid_audiences:
                    raise OneLogin_Saml2_ValidationError(
                        '%s is not a valid audience for this Response' %
                        sp_entity_id,
                        OneLogin_Saml2_ValidationError.WRONG_AUDIENCE)

                # Checks the issuers
                issuers = self.get_issuers()
                for issuer in issuers:
                    if issuer is None or issuer != idp_entity_id:
                        raise OneLogin_Saml2_ValidationError(
                            'Invalid issuer in the Assertion/Response (expected %(idpEntityId)s, got %(issuer)s)'
                            % {
                                'idpEntityId': idp_entity_id,
                                'issuer': issuer
                            }, OneLogin_Saml2_ValidationError.WRONG_ISSUER)

                # Checks the session Expiration
                session_expiration = self.get_session_not_on_or_after()
                if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now(
                ):
                    raise OneLogin_Saml2_ValidationError(
                        'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response',
                        OneLogin_Saml2_ValidationError.SESSION_EXPIRED)

                # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
                any_subject_confirmation = False
                subject_confirmation_nodes = self.__query_assertion(
                    '/saml:Subject/saml:SubjectConfirmation')

                for scn in subject_confirmation_nodes:
                    method = scn.get('Method', None)
                    if method and method != OneLogin_Saml2_Constants.CM_BEARER:
                        continue
                    sc_data = scn.find(
                        'saml:SubjectConfirmationData',
                        namespaces=OneLogin_Saml2_Constants.NSMAP)
                    if sc_data is None:
                        continue
                    else:
                        irt = sc_data.get('InResponseTo', None)
                        if (in_response_to is None and irt is not None and
                           security.get('rejectUnsolicitedResponsesWithInResponseTo', False)) or \
                           in_response_to and irt and irt != in_response_to:
                            continue
                        recipient = sc_data.get('Recipient', None)
                        if recipient and current_url not in recipient:
                            continue
                        nooa = sc_data.get('NotOnOrAfter', None)
                        if nooa:
                            parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nooa)
                            if parsed_nooa <= OneLogin_Saml2_Utils.now():
                                continue
                        nb = sc_data.get('NotBefore', None)
                        if nb:
                            parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nb)
                            if parsed_nb > OneLogin_Saml2_Utils.now():
                                continue

                        if nooa:
                            self.valid_scd_not_on_or_after = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nooa)

                        any_subject_confirmation = True
                        break

                if not any_subject_confirmation:
                    raise OneLogin_Saml2_ValidationError(
                        'A valid SubjectConfirmation was not found on this Response',
                        OneLogin_Saml2_ValidationError.
                        WRONG_SUBJECTCONFIRMATION)

                if security.get('wantAssertionsSigned',
                                False) and not has_signed_assertion:
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion of the Response is not signed and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_SIGNED_ASSERTION)

                if security.get('wantMessagesSigned',
                                False) and not has_signed_response:
                    raise OneLogin_Saml2_ValidationError(
                        'The Message of the Response is not signed and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE)

            if not signed_elements or (not has_signed_response
                                       and not has_signed_assertion):
                raise OneLogin_Saml2_ValidationError(
                    'No Signature found. SAML Response rejected',
                    OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND)
            else:
                cert = idp_data.get('x509cert', None)
                fingerprint = idp_data.get('certFingerprint', None)
                fingerprintalg = idp_data.get('certFingerprintAlgorithm', None)

                multicerts = None
                if 'x509certMulti' in idp_data and 'signing' in idp_data[
                        'x509certMulti'] and idp_data['x509certMulti'][
                            'signing']:
                    multicerts = idp_data['x509certMulti']['signing']

                # If find a Signature on the Response, validates it checking the original response
                if has_signed_response and not OneLogin_Saml2_Utils.validate_sign(
                        self.document,
                        cert,
                        fingerprint,
                        fingerprintalg,
                        xpath=OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH,
                        multicerts=multicerts,
                        raise_exceptions=False):
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. SAML Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)

                document_check_assertion = self.decrypted_document if self.encrypted else self.document
                if has_signed_assertion and not OneLogin_Saml2_Utils.validate_sign(
                        document_check_assertion,
                        cert,
                        fingerprint,
                        fingerprintalg,
                        xpath=OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH,
                        multicerts=multicerts,
                        raise_exceptions=False):
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. SAML Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)

            return True
        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
Esempio n. 11
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
Esempio n. 12
0
    def is_valid(settings, request, get_data, debug=False):
        """
        Checks if the Logout Request recieved is valid
        :param settings: Settings
        :type settings: OneLogin_Saml2_Settings
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :return: If the Logout Request is or not valid
        :rtype: boolean
        """
        try:
            if isinstance(request, Document):
                dom = request
            else:
                dom = parseString(request)

            idp_data = settings.get_idp_data()
            idp_entity_id = idp_data['entityId']

            if settings.is_strict():
                res = OneLogin_Saml2_Utils.validate_xml(dom, 'saml-schema-protocol-2.0.xsd', debug)
                if not isinstance(res, Document):
                    raise Exception('Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd')

                security = settings.get_security_data()

                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(get_data)

                # Check NotOnOrAfter
                if dom.documentElement.hasAttribute('NotOnOrAfter'):
                    na = OneLogin_Saml2_Utils.parse_SAML_to_time(dom.documentElement.getAttribute('NotOnOrAfter'))
                    if na <= OneLogin_Saml2_Utils.now():
                        raise Exception('Timing issues (please check your clock settings)')

                # Check destination
                if dom.documentElement.hasAttribute('Destination'):
                    destination = dom.documentElement.getAttribute('Destination')
                    if destination != '':
                        if current_url not in destination:
                            raise Exception('The LogoutRequest was received at $currentURL instead of $destination')

                # Check issuer
                issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom)
                if issuer is not None and issuer != idp_entity_id:
                    raise Exception('Invalid issuer in the Logout Request')

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

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

                if sign_alg != OneLogin_Saml2_Constants.RSA_SHA1:
                    raise Exception('Invalid signAlg in the recieved Logout Request')

                signed_query = 'SAMLRequest=%s' % quote_plus(get_data['SAMLRequest'])
                if 'RelayState' in get_data:
                    signed_query = '%s&RelayState=%s' % (signed_query, quote_plus(get_data['RelayState']))
                signed_query = '%s&SigAlg=%s' % (signed_query, quote_plus(sign_alg))

                if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
                    raise Exception('In order to validate the sign on the Logout Request, the x509cert of the IdP is required')
                cert = idp_data['x509cert']

                if not OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert):
                    raise Exception('Signature validation failed. Logout Request rejected')

            return True
        except Exception as err:
            debug = settings.is_debug_active()
            if debug:
                print err
            return False
Esempio n. 13
0
    def is_valid(self, request_data, request_id=None):
        """
        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
        :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 Exception('Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd')

                security = self.__settings.get_security_data()

                # Check if the InResponseTo of the Logout Response matchs 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 Exception('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))

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

                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 Exception('The LogoutRequest was received at $currentURL instead of $destination')

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

            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))

                if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
                    raise Exception('In order to validate the sign on the Logout Response, the x509cert of the IdP is required')
                cert = idp_data['x509cert']

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

            return True
        # pylint: disable=R0801
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print err.__str__()
            return False
Esempio n. 14
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
                if dom.get('Destination', None):
                    destination = dom.get('Destination')
                    if destination != '':
                        if current_url not in destination:
                            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
Esempio n. 15
0
    def is_valid(self, request_data, request_id=None):
        """
        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
        :return: Returns if the SAML LogoutResponse is or not valid
        :rtype: boolean
        """
        self.__error = None
        try:
            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data["entityId"]
            get_data = request_data["get_data"]

            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 Exception("Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd")

                security = self.__settings.get_security_data()

                # Check if the InResponseTo of the Logout Response matchs 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 Exception(
                            "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)
                        )

                # Check issuer
                issuer = self.get_issuer()
                if issuer is not None and issuer != idp_entity_id:
                    raise Exception("Invalid issuer in the Logout Request")

                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 Exception("The LogoutRequest was received at $currentURL instead of $destination")

                if security["wantMessagesSigned"]:
                    if "Signature" not in get_data:
                        raise Exception("The Message of the Logout Response is not signed and the SP require it")

            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" % quote_plus(get_data["SAMLResponse"])
                if "RelayState" in get_data:
                    signed_query = "%s&RelayState=%s" % (signed_query, quote_plus(get_data["RelayState"]))
                signed_query = "%s&SigAlg=%s" % (signed_query, quote_plus(sign_alg))

                if "x509cert" not in idp_data or idp_data["x509cert"] is None:
                    raise Exception(
                        "In order to validate the sign on the Logout Response, the x509cert of the IdP is required"
                    )
                cert = idp_data["x509cert"]

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

            return True
        # pylint: disable=R0801
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print err.__str__()
            return False
Esempio n. 16
0
    def is_valid(self, request_data, request_id=None):
        """
        Validates the response object.

        :param request_data: Request Data
        :type request_data: dict

        :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP
        :type request_id: string

        :returns: True if the SAML Response is valid, False if not
        :rtype: bool
        """
        self.__error = None
        try:
            # Checks SAML version
            if self.document.get('Version', None) != '2.0':
                raise Exception('Unsupported SAML version')

            # Checks that ID exists
            if self.document.get('ID', None) is None:
                raise Exception('Missing ID attribute on SAML Response')

            # Checks that the response only has one assertion
            if not self.validate_num_assertions():
                raise Exception('SAML Response must contain 1 assertion')

            # Checks that the response has the SUCCESS status
            self.check_status()

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data.get('entityId', '')
            sp_data = self.__settings.get_sp_data()
            sp_entity_id = sp_data.get('entityId', '')

            signed_elements = self.process_signed_elements()

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

                # If encrypted, check also the decrypted document
                if self.encrypted:
                    res = OneLogin_Saml2_Utils.validate_xml(etree.tostring(self.decrypted_document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
                    if not isinstance(res, Document):
                        raise Exception(no_valid_xml_msg)

                security = self.__settings.get_security_data()
                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)

                # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
                in_response_to = self.document.get('InResponseTo', None)
                if in_response_to and request_id:
                    if in_response_to != request_id:
                        raise Exception('The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id))

                if not self.encrypted and security.get('wantAssertionsEncrypted', False):
                    raise Exception('The assertion of the Response is not encrypted and the SP require it')

                if security.get('wantNameIdEncrypted', False):
                    encrypted_nameid_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
                    if len(encrypted_nameid_nodes) == 0:
                        raise Exception('The NameID of the Response is not encrypted and the SP require it')

                # Checks that there is at least one AttributeStatement if required
                attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement')
                if security.get('wantAttributeStatement', True) and not attribute_statement_nodes:
                    raise Exception('There is no AttributeStatement on the Response')

                # Validates Assertion timestamps
                if not self.validate_timestamps():
                    raise Exception('Timing issues (please check your clock settings)')

                encrypted_attributes_nodes = self.__query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute')
                if encrypted_attributes_nodes:
                    raise Exception('There is an EncryptedAttribute in the Response and this SP not support them')

                # Checks destination
                destination = self.document.get('Destination', '')
                if destination:
                    if not destination.startswith(current_url):
                        # TODO: Review if following lines are required, since we can control the
                        # request_data
                        #  current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
                        #  if not destination.startswith(current_url_routed):
                        raise Exception('The response was received at %s instead of %s' % (current_url, destination))

                # Checks audience
                valid_audiences = self.get_audiences()
                if valid_audiences and sp_entity_id not in valid_audiences:
                    raise Exception('%s is not a valid audience for this Response' % sp_entity_id)

                # Checks the issuers
                issuers = self.get_issuers()
                for issuer in issuers:
                    if issuer is None or issuer != idp_entity_id:
                        raise Exception('Invalid issuer in the Assertion/Response')

                # Checks the session Expiration
                session_expiration = self.get_session_not_on_or_after()
                if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now():
                    raise Exception('The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response')

                # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
                any_subject_confirmation = False
                subject_confirmation_nodes = self.__query_assertion('/saml:Subject/saml:SubjectConfirmation')

                for scn in subject_confirmation_nodes:
                    method = scn.get('Method', None)
                    if method and method != OneLogin_Saml2_Constants.CM_BEARER:
                        continue
                    sc_data = scn.find('saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP)
                    if sc_data is None:
                        continue
                    else:
                        irt = sc_data.get('InResponseTo', None)
                        if in_response_to and irt and irt != in_response_to:
                            continue
                        recipient = sc_data.get('Recipient', None)
                        if recipient and current_url not in recipient:
                            continue
                        nooa = sc_data.get('NotOnOrAfter', None)
                        if nooa:
                            parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa)
                            if parsed_nooa <= OneLogin_Saml2_Utils.now():
                                continue
                        nb = sc_data.get('NotBefore', None)
                        if nb:
                            parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(nb)
                            if parsed_nb > OneLogin_Saml2_Utils.now():
                                continue
                        any_subject_confirmation = True
                        break

                if not any_subject_confirmation:
                    raise Exception('A valid SubjectConfirmation was not found on this Response')

                if security.get('wantAssertionsSigned', False) and ('{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML) not in signed_elements:
                    raise Exception('The Assertion of the Response is not signed and the SP require it')

                if security.get('wantMessagesSigned', False) and ('{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP) not in signed_elements:
                    raise Exception('The Message of the Response is not signed and the SP require it')

            if len(signed_elements) > 0:
                if len(signed_elements) > 2:
                    raise Exception('Too many Signatures found. SAML Response rejected')
                certs = idp_data.get('x509certs', [])
                fingerprint = idp_data.get('certFingerprint', None)
                fingerprintalg = idp_data.get('certFingerprintAlgorithm', None)

                # If find a Signature on the Response, validates it checking the original response
                if '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements:
                    document_to_validate = self.document
                # Otherwise validates the assertion (decrypted assertion if was encrypted)
                else:
                    if self.encrypted:
                        document_to_validate = self.decrypted_document
                    else:
                        document_to_validate = self.document
                if certs:
                    validated = any(OneLogin_Saml2_Utils.validate_sign(document_to_validate, cert, fingerprint, fingerprintalg)
                                    for cert in certs)
                else:
                    validated = OneLogin_Saml2_Utils.validate_sign(document_to_validate, None, fingerprint, fingerprintalg)
                if not validated:
                    raise Exception('Signature validation failed. SAML Response rejected')
            else:
                raise Exception('No Signature found. SAML Response rejected')

            return True
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print err.__str__()
            return False
Esempio n. 17
0
    def is_valid(self, request_data, request_id=None):
        """
        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
        :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 Exception(
                        'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd'
                    )

                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 Exception(
                            '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))

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

                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 Exception(
                                'The LogoutRequest was received at $currentURL instead of $destination'
                            )

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

            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))

                if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
                    raise Exception(
                        'In order to validate the sign on the Logout Response, the x509cert of the IdP is required'
                    )
                cert = idp_data['x509cert']

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

            return True
        # pylint: disable=R0801
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print err.__str__()
            return False
Esempio n. 18
0
    def is_valid(self, request_data):
        """
        Checks if the Logout Request received is valid
        :param request_data: Request Data
        :type request_data: dict

        :return: If the Logout Request is or not valid
        :rtype: boolean
        """
        self.__error = None
        lowercase_urlencoding = False
        try:
            dom = fromstring(self.__logout_request)

            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 Exception('Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd')

                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 Exception('Timing issues (please check your clock settings)')

                # Check destination
                if dom.get('Destination', None):
                    destination = dom.get('Destination')
                    if destination != '':
                        if current_url not in destination:
                            raise Exception(
                                'The LogoutRequest was received at '
                                '%(currentURL)s instead of %(destination)s' %
                                {
                                    'currentURL': current_url,
                                    'destination': destination,
                                }
                            )

                # Check issuer
                issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom)
                if issuer is not None and issuer != idp_entity_id:
                    raise Exception('Invalid issuer in the Logout Request')

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

            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))

                if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
                    raise Exception('In order to validate the sign on the Logout Request, the x509cert of the IdP is required')
                cert = idp_data['x509cert']

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

            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__()
            return False
Esempio n. 19
0
    def is_valid(settings, request, get_data, debug=False):
        """
        Checks if the Logout Request recieved is valid
        :param settings: Settings
        :type settings: OneLogin_Saml2_Settings
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :return: If the Logout Request is or not valid
        :rtype: boolean
        """
        try:
            if isinstance(request, Document):
                dom = request
            else:
                dom = parseString(request)

            idp_data = settings.get_idp_data()
            idp_entity_id = idp_data['entityId']

            if settings.is_strict():
                res = OneLogin_Saml2_Utils.validate_xml(
                    dom, 'saml-schema-protocol-2.0.xsd', debug)
                if not isinstance(res, Document):
                    raise Exception(
                        'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd'
                    )

                security = settings.get_security_data()

                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(
                    get_data)

                # Check NotOnOrAfter
                if dom.documentElement.hasAttribute('NotOnOrAfter'):
                    na = OneLogin_Saml2_Utils.parse_SAML_to_time(
                        dom.documentElement.getAttribute('NotOnOrAfter'))
                    if na <= OneLogin_Saml2_Utils.now():
                        raise Exception(
                            'Timing issues (please check your clock settings)')

                # Check destination
                if dom.documentElement.hasAttribute('Destination'):
                    destination = dom.documentElement.getAttribute(
                        'Destination')
                    if destination != '':
                        if current_url not in destination:
                            raise Exception(
                                'The LogoutRequest was received at $currentURL instead of $destination'
                            )

                # Check issuer
                issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom)
                if issuer is not None and issuer != idp_entity_id:
                    raise Exception('Invalid issuer in the Logout Request')

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

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

                if sign_alg != OneLogin_Saml2_Constants.RSA_SHA1:
                    raise Exception(
                        'Invalid signAlg in the recieved Logout Request')

                signed_query = 'SAMLRequest=%s' % quote_plus(
                    get_data['SAMLRequest'])
                if 'RelayState' in get_data:
                    signed_query = '%s&RelayState=%s' % (
                        signed_query, quote_plus(get_data['RelayState']))
                signed_query = '%s&SigAlg=%s' % (signed_query,
                                                 quote_plus(sign_alg))

                if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
                    raise Exception(
                        'In order to validate the sign on the Logout Request, the x509cert of the IdP is required'
                    )
                cert = idp_data['x509cert']

                if not OneLogin_Saml2_Utils.validate_binary_sign(
                        signed_query, b64decode(get_data['Signature']), cert):
                    raise Exception(
                        'Signature validation failed. Logout Request rejected')

            return True
        except Exception as err:
            debug = settings.is_debug_active()
            if debug:
                print err
            return False