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