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_XML.validate_xml( self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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 in_response_to = self.document.get('InResponseTo', None) if request_id is not None and in_response_to and in_response_to != request_id: 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 destination = self.document.get('Destination', None) if destination and 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' ) return True # pylint: disable=R0801 except Exception as err: self.__error = str(err) debug = self.__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 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_XML.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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 in_response_to = self.document.get('InResponseTo', None) if request_id is not None and in_response_to and in_response_to != request_id: 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 destination = self.document.get('Destination', None) if destination and 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') return True # pylint: disable=R0801 except Exception as err: self.__error = str(err) debug = self.__settings.is_debug_active() if debug: print(err) return False
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, compat.text_types) if len(xml) == 0: raise Exception('Empty string supplied as input') errors = [] root = OneLogin_Saml2_XML.validate_xml(xml, 'saml-schema-metadata-2.0.xsd', self.__debug) if isinstance(root, str): errors.append(root) else: if root.tag != '{%s}EntityDescriptor' % OneLogin_Saml2_Constants.NS_MD: errors.append('noEntityDescriptor_xml') else: if (len( root.findall( './/md:SPSSODescriptor', namespaces=OneLogin_Saml2_Constants.NSMAP))) != 1: errors.append('onlySPSSODescriptor_allowed_xml') else: valid_until, cache_duration = root.get( 'validUntil'), root.get('cacheDuration') if valid_until: valid_until = OneLogin_Saml2_Utils.parse_SAML_to_time( valid_until) 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, compat.text_types) if len(xml) == 0: raise Exception('Empty string supplied as input') errors = [] root = OneLogin_Saml2_XML.validate_xml(xml, 'saml-schema-metadata-2.0.xsd', self.__debug) if isinstance(root, str): errors.append(root) else: if root.tag != '{%s}EntityDescriptor' % OneLogin_Saml2_Constants.NS_MD: errors.append('noEntityDescriptor_xml') else: if (len(root.findall('.//md:SPSSODescriptor', namespaces=OneLogin_Saml2_Constants.NSMAP))) != 1: errors.append('onlySPSSODescriptor_allowed_xml') else: valid_until, cache_duration = root.get('validUntil'), root.get('cacheDuration') if valid_until: valid_until = OneLogin_Saml2_Utils.parse_SAML_to_time(valid_until) 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 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 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 ) # Checks that the response has the SUCCESS status self.check_status() idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data['entityId'] sp_data = self.__settings.get_sp_data() sp_entity_id = sp_data['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_XML.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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_XML.validate_xml(self.decrypted_document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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) # 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 is not None and request_id is not None: if 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['wantAssertionsEncrypted']: 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['wantNameIdEncrypted']: 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', 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 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 OneLogin_Saml2_ValidationError( 'A valid SubjectConfirmation was not found on this Response', OneLogin_Saml2_ValidationError.WRONG_SUBJECTCONFIRMATION ) if security['wantAssertionsSigned'] 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['wantMessagesSigned'] 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) if fingerprint: fingerprint = OneLogin_Saml2_Utils.format_finger_print(fingerprint) fingerprintalg = idp_data.get('certFingerprintAlgorithm', None) # 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, 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, 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 = str(err) debug = self.__settings.is_debug_active() if debug: print(err) if raise_exceptions: raise 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 try: root = OneLogin_Saml2_XML.to_etree(self.__logout_request) idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data['entityId'] get_data = ('get_data' in request_data and request_data['get_data']) or dict() if self.__settings.is_strict(): res = OneLogin_Saml2_XML.validate_xml( root, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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 root.get('NotOnOrAfter', None): na = OneLogin_Saml2_Utils.parse_SAML_to_time( root.get('NotOnOrAfter')) if na <= OneLogin_Saml2_Utils.now(): raise Exception( 'Timing issues (please check your clock settings)') # Check destination if root.get('Destination', None): destination = root.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(root) 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' ) return True except Exception as err: # pylint: disable=R0801 self.__error = str(err) debug = self.__settings.is_debug_active() if debug: print(err) 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['entityId'] sp_data = self.__settings.get_sp_data() sp_entity_id = sp_data['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_XML.validate_xml( self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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['wantAssertionsEncrypted']: raise Exception( 'The assertion of the Response is not encrypted and the SP require it' ) if security['wantNameIdEncrypted']: 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[ 'wantAttributeStatement'] 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['wantAssertionsSigned'] 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['wantMessagesSigned'] 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['x509cert'] fingerprint = idp_data['certFingerprint'] fingerprintalg = idp_data['certFingerprintAlgorithm'] # 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 = str(err) debug = self.__settings.is_debug_active() if debug: print(err) 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 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_XML.validate_xml( self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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() # Check if the InResponseTo of the Logout Response matches the ID of the Logout Request (requestId) if provided in_response_to = self.document.get('InResponseTo', None) if request_id is not None and in_response_to and in_response_to != request_id: 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 destination = self.document.get('Destination', None) if destination and 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 '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) return True # pylint: disable=R0801 except Exception as err: self.__error = str(err) debug = self.__settings.is_debug_active() if debug: print(err) if raise_exceptions: raise 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 try: root = OneLogin_Saml2_XML.to_etree(self.__logout_request) idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data['entityId'] get_data = ('get_data' in request_data and request_data['get_data']) or dict() if self.__settings.is_strict(): res = OneLogin_Saml2_XML.validate_xml(root, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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 root.get('NotOnOrAfter', None): na = OneLogin_Saml2_Utils.parse_SAML_to_time(root.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 root.get('Destination', None): destination = root.get('Destination') if destination != '': if current_url not in destination: raise OneLogin_Saml2_ValidationError( '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(root) 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 ) return True except Exception as err: # pylint: disable=R0801 self.__error = str(err) debug = self.__settings.is_debug_active() if debug: print(err) if raise_exceptions: raise return False
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: root = OneLogin_Saml2_XML.to_etree(self.__logout_request) idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data['entityId'] get_data = ('get_data' in request_data and request_data['get_data']) or dict() if self.__settings.is_strict(): res = OneLogin_Saml2_XML.validate_xml(root, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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 root.get('NotOnOrAfter', None): na = OneLogin_Saml2_Utils.parse_SAML_to_time(root.get('NotOnOrAfter')) if na <= OneLogin_Saml2_Utils.now(): raise Exception('Timing issues (please check your clock settings)') # Check destination if root.get('Destination', None): destination = root.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(root) 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') return True except Exception as err: # pylint: disable=R0801 self.__error = str(err) debug = self.__settings.is_debug_active() if debug: print(err) 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['entityId'] sp_data = self.__settings.get_sp_data() sp_entity_id = sp_data['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_XML.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if isinstance(res, str): 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['wantAssertionsEncrypted']: raise Exception('The assertion of the Response is not encrypted and the SP require it') if security['wantNameIdEncrypted']: 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['wantAttributeStatement'] 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['wantAssertionsSigned'] 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['wantMessagesSigned'] 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['x509cert'] fingerprint = idp_data['certFingerprint'] fingerprintalg = idp_data['certFingerprintAlgorithm'] # 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 = str(err) debug = self.__settings.is_debug_active() if debug: print(err) return False