def validate_timestamps(self): """ Verifies that the document is valid according to Conditions Element :returns: (True, '') if the condition is valid, (False, error_msg) otherwise :rtype: (bool, str) """ conditions_nodes = self.__query_assertion('/saml:Conditions') for conditions_node in conditions_nodes: nb_attr = conditions_node.get('NotBefore') nooa_attr = conditions_node.get('NotOnOrAfter') nb_attr_time = OneLogin_Saml2_Utils.parse_SAML_to_time(nb_attr) now = OneLogin_Saml2_Utils.now() if nb_attr and nb_attr_time > now + self.__settings.get_allowed_clock_drift(): return False, ('There was a problem in validating the response: Current time (%s) is earlier than ' 'NotBefore condition (%s)' % (datetime.fromtimestamp(now), datetime.fromtimestamp(nb_attr_time))) nooa_attr_time = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa_attr) now = OneLogin_Saml2_Utils.now() if nooa_attr and nooa_attr_time + self.__settings.get_allowed_clock_drift() <= now: return False, ('There was a problem in validating the response: Current time (%s) is later than ' 'NotOnOrAfter condition (%s)' % (datetime.fromtimestamp(now), datetime.fromtimestamp(nooa_attr_time))) return True, ''
def build(self, in_response_to): """ Creates a Logout Response object. :param in_response_to: InResponseTo value for the Logout Response. :type in_response_to: string """ sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() uid = OneLogin_Saml2_Utils.generate_unique_id() issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) logout_response = """<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0" IssueInstant="%(issue_instant)s" Destination="%(destination)s" InResponseTo="%(in_response_to)s" > <saml:Issuer>%(entity_id)s</saml:Issuer> <samlp:Status> <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /> </samlp:Status> </samlp:LogoutResponse>""" % { "id": uid, "issue_instant": issue_instant, "destination": idp_data["singleLogoutService"]["url"], "in_response_to": in_response_to, "entity_id": sp_data["entityId"], } self.__logout_response = logout_response
def validate_timestamps(self): """ Verifies that the document is valid according to Conditions Element :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean :returns: True if the condition is valid, False otherwise :rtype: bool """ conditions_nodes = self.__query_assertion('/saml:Conditions') for conditions_node in conditions_nodes: nb_attr = conditions_node.get('NotBefore') nooa_attr = conditions_node.get('NotOnOrAfter') if nb_attr and OneLogin_Saml2_Utils.parse_SAML_to_time(nb_attr) > OneLogin_Saml2_Utils.now() + OneLogin_Saml2_Constants.ALLOWED_CLOCK_DRIFT: raise OneLogin_Saml2_ValidationError( 'Could not validate timestamp: not yet valid. Check system clock.', OneLogin_Saml2_ValidationError.ASSERTION_TOO_EARLY ) if nooa_attr and OneLogin_Saml2_Utils.parse_SAML_to_time(nooa_attr) + OneLogin_Saml2_Constants.ALLOWED_CLOCK_DRIFT <= OneLogin_Saml2_Utils.now(): raise OneLogin_Saml2_ValidationError( 'Could not validate timestamp: expired. Check system clock.', OneLogin_Saml2_ValidationError.ASSERTION_EXPIRED ) return True
def __init__(self, settings): """ Constructs the AuthnRequest object. Arguments are: * (OneLogin_Saml2_Settings) settings. Setting data """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) destination = idp_data['singleSignOnService']['url'] name_id_policy_format = sp_data['NameIDFormat'] if 'wantNameIdEncrypted' in security and security['wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data.keys() if 'en-US' in langs: lang = 'en-US' else: lang = langs[0] if 'displayname' in organization_data[lang] and organization_data[lang]['displayname'] is not None: provider_name_str = 'ProviderName="%s"' % organization_data[lang]['displayname'] request = """<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0" %(provider_name)s IssueInstant="%(issue_instant)s" Destination="%(destination)s" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="%(assertion_url)s"> <saml:Issuer>%(entity_id)s</saml:Issuer> <samlp:NameIDPolicy Format="%(name_id_policy)s" AllowCreate="true" /> </samlp:AuthnRequest>""" % \ { 'id': uid, 'provider_name': provider_name_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'name_id_policy': name_id_policy_format, } self.__authn_request = request
def __init__(self, settings, request=None): """ Constructs the Logout Request object. Arguments are: * (OneLogin_Saml2_Settings) settings. Setting data """ self.__settings = settings self.__error = None if request is None: sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() name_id_value = OneLogin_Saml2_Utils.generate_unique_id() issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) cert = None if 'nameIdEncrypted' in security and security['nameIdEncrypted']: cert = idp_data['x509cert'] name_id = OneLogin_Saml2_Utils.generate_name_id( name_id_value, sp_data['entityId'], sp_data['NameIDFormat'], cert ) logout_request = """<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0" IssueInstant="%(issue_instant)s" Destination="%(single_logout_url)s"> <saml:Issuer>%(entity_id)s</saml:Issuer> %(name_id)s </samlp:LogoutRequest>""" % \ { 'id': uid, 'issue_instant': issue_instant, 'single_logout_url': idp_data['singleLogoutService']['url'], 'entity_id': sp_data['entityId'], 'name_id': name_id, } else: decoded = b64decode(request) # We try to inflate try: inflated = decompress(decoded, -15) logout_request = inflated except Exception: logout_request = decoded self.__logout_request = logout_request
def validate_timestamps(self): """ Verifies that the document is valid according to Conditions Element :returns: True if the condition is valid, False otherwise :rtype: bool """ conditions_nodes = self.__query_assertion('/saml:Conditions') for conditions_node in conditions_nodes: nb_attr = conditions_node.get('NotBefore') nooa_attr = conditions_node.get('NotOnOrAfter') if nb_attr and OneLogin_Saml2_Utils.parse_SAML_to_time(nb_attr) > OneLogin_Saml2_Utils.now() + OneLogin_Saml2_Constants.ALLOWED_CLOCK_DRIFT: return False if nooa_attr and OneLogin_Saml2_Utils.parse_SAML_to_time(nooa_attr) + OneLogin_Saml2_Constants.ALLOWED_CLOCK_DRIFT <= OneLogin_Saml2_Utils.now(): return False return True
def build(self, in_response_to): """ Creates a Logout Response object. :param in_response_to: InResponseTo value for the Logout Response. :type in_response_to: string """ sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() uid = OneLogin_Saml2_Utils.generate_unique_id() issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) logout_response = OneLogin_Saml2_Templates.LOGOUT_RESPONSE % \ { 'id': uid, 'issue_instant': issue_instant, 'destination': idp_data['singleLogoutService']['url'], 'in_response_to': in_response_to, 'entity_id': sp_data['entityId'], 'status': "urn:oasis:names:tc:SAML:2.0:status:Success" } self.__logout_response = logout_response
def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True): """ Constructs the AuthnRequest object. :param settings: OSetting data :type settings: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNRequest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNRequest will set the Ispassive='true'. :type is_passive: bool :param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element. :type set_nameid_policy: bool """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML( OneLogin_Saml2_Utils.now()) destination = idp_data['singleSignOnService']['url'] provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data if 'en-US' in langs: lang = 'en-US' else: lang = sorted(langs)[0] display_name = 'displayname' in organization_data[ lang] and organization_data[lang]['displayname'] if display_name: provider_name_str = "\n" + ' ProviderName="%s"' % organization_data[ lang]['displayname'] force_authn_str = '' if force_authn is True: force_authn_str = "\n" + ' ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = "\n" + ' IsPassive="true"' nameid_policy_str = '' if set_nameid_policy: name_id_policy_format = sp_data['NameIDFormat'] if security['wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED nameid_policy_str = """ <saml2p:NameIDPolicy Format="%s" AllowCreate="true" />""" % name_id_policy_format requested_authn_context_str = '' if security['requestedAuthnContext'] is not False: authn_comparison = 'exact' if 'requestedAuthnContextComparison' in security.keys(): authn_comparison = security['requestedAuthnContextComparison'] if security['requestedAuthnContext'] is True: requested_authn_context_str = """ <saml2p:RequestedAuthnContext Comparison="%s"> <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef> </saml2p:RequestedAuthnContext>""" % authn_comparison else: requested_authn_context_str = ' <saml2p:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml2:AuthnContextClassRef>%s</saml2:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </saml2p:RequestedAuthnContext>' attr_consuming_service_str = '' if 'attributeConsumingService' in sp_data and sp_data[ 'attributeConsumingService']: attr_consuming_service_str = "\n AttributeConsumingServiceIndex=\"1\"" request = OneLogin_Saml2_Templates.AUTHN_REQUEST % \ { 'id': uid, 'provider_name': provider_name_str, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'nameid_policy_str': nameid_policy_str, 'requested_authn_context_str': requested_authn_context_str, 'attr_consuming_service_str': attr_consuming_service_str, } self.__authn_request = request
def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True): """ Constructs the AuthnRequest object. :param settings: OSetting data :type return_to: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNReuqest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNReuqest will set the Ispassive='true'. :type is_passive: bool :param set_nameid_policy: Optional argument. When true the AuthNReuqest will set a nameIdPolicy element. :type set_nameid_policy: bool """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML( OneLogin_Saml2_Utils.now()) destination = idp_data['singleSignOnService']['url'] provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data.keys() if 'en-US' in langs: lang = 'en-US' else: lang = langs[0] if 'displayname' in organization_data[lang] and organization_data[ lang]['displayname'] is not None: provider_name_str = "\n" + ' ProviderName="%s"' % organization_data[ lang]['displayname'] force_authn_str = '' if force_authn is True: force_authn_str = "\n" + ' ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = "\n" + ' IsPassive="true"' nameid_policy_str = '' if set_nameid_policy: name_id_policy_format = sp_data['NameIDFormat'] if 'wantNameIdEncrypted' in security and security[ 'wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED nameid_policy_str = """ <samlp:NameIDPolicy Format="%s" AllowCreate="true" />""" % name_id_policy_format requested_authn_context_str = '' if 'requestedAuthnContext' in security.keys( ) and security['requestedAuthnContext'] is not False: authn_comparison = 'exact' if 'requestedAuthnContextComparison' in security.keys(): authn_comparison = security['requestedAuthnContextComparison'] if security['requestedAuthnContext'] is True: requested_authn_context_str = "\n" + """ <samlp:RequestedAuthnContext Comparison="%s"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" % authn_comparison else: requested_authn_context_str = "\n" + ' <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </samlp:RequestedAuthnContext>' attr_consuming_service_str = '' if 'attributeConsumingService' in sp_data and sp_data[ 'attributeConsumingService']: attr_consuming_service_str = 'AttributeConsumingServiceIndex="1"' request = """<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0"%(provider_name)s%(force_authn_str)s%(is_passive_str)s IssueInstant="%(issue_instant)s" Destination="%(destination)s" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="%(assertion_url)s" %(attr_consuming_service_str)s> <saml:Issuer>%(entity_id)s</saml:Issuer>%(nameid_policy_str)s%(requested_authn_context_str)s </samlp:AuthnRequest>""" % \ { 'id': uid, 'provider_name': provider_name_str, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'nameid_policy_str': nameid_policy_str, 'requested_authn_context_str': requested_authn_context_str, 'attr_consuming_service_str': attr_consuming_service_str } self.__authn_request = request
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 __init__(self, settings, request=None, name_id=None, session_index=None, nq=None): """ Constructs the Logout Request object. :param settings: Setting data :type request_data: OneLogin_Saml2_Settings :param request: Optional. A LogoutRequest to be loaded instead build one. :type request: string :param name_id: The NameID that will be set in the LogoutRequest. :type name_id: string :param session_index: SessionIndex that identifies the session of the user. :type session_index: string :param nq: IDP Name Qualifier :type: string """ self.__settings = settings self.__error = None self.id = None if request is None: sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML( OneLogin_Saml2_Utils.now()) cert = None if 'nameIdEncrypted' in security and security['nameIdEncrypted']: cert = idp_data['x509cert'] if name_id is not None: nameIdFormat = sp_data['NameIDFormat'] spNameQualifier = None else: name_id = idp_data['entityId'] nameIdFormat = OneLogin_Saml2_Constants.NAMEID_ENTITY spNameQualifier = sp_data['entityId'] name_id_obj = OneLogin_Saml2_Utils.generate_name_id( name_id, spNameQualifier, nameIdFormat, cert) if session_index: session_index_str = '<samlp:SessionIndex>%s</samlp:SessionIndex>' % session_index else: session_index_str = '' logout_request = """<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0" IssueInstant="%(issue_instant)s" Destination="%(single_logout_url)s"> <saml:Issuer>%(entity_id)s</saml:Issuer> %(name_id)s %(session_index)s </samlp:LogoutRequest>""" % \ { 'id': uid, 'issue_instant': issue_instant, 'single_logout_url': idp_data['singleLogoutService']['url'], 'entity_id': sp_data['entityId'], 'name_id': name_id_obj, 'session_index': session_index_str, } else: decoded = b64decode(request) # We try to inflate try: inflated = decompress(decoded, -15) logout_request = inflated except Exception: logout_request = decoded self.id = self.get_id(logout_request) self.__logout_request = logout_request
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): """ 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(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 __init__(self, settings, force_authn=False, is_passive=False): """ Constructs the AuthnRequest object. :param settings: OSetting data :type return_to: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNReuqest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNReuqest will set the Ispassive='true'. :type is_passive: bool """ self._settings = settings sp_data = self._settings.get_sp_data() idp_data = self._settings.get_idp_data() security = self._settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self._id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) destination = idp_data['singleSignOnService']['url'] name_id_policy_format = sp_data['NameIDFormat'] if 'wantNameIdEncrypted' in security and security['wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data.keys() if 'en-US' in langs: lang = 'en-US' else: lang = langs[0] if 'displayname' in organization_data[lang] and organization_data[lang]['displayname'] is not None: provider_name_str = 'ProviderName="%s"' % organization_data[lang]['displayname'] force_authn_str = '' if force_authn is True: force_authn_str = 'ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = 'IsPassive="true"' requested_authn_context_str = '' if 'requestedAuthnContext' in security.keys() and security['requestedAuthnContext'] is not False: authn_comparison = 'exact' if 'requestedAuthnContextComparison' in security.keys(): authn_comparison = security['requestedAuthnContextComparison'] if security['requestedAuthnContext'] is True: requested_authn_context_str = """ <samlp:RequestedAuthnContext Comparison="%s"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" % authn_comparison else: requested_authn_context_str = ' <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </samlp:RequestedAuthnContext>' request = """<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0" %(provider_name)s %(force_authn_str)s %(is_passive_str)s IssueInstant="%(issue_instant)s" Destination="%(destination)s" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="%(assertion_url)s"> <saml:Issuer>%(entity_id)s</saml:Issuer> <samlp:NameIDPolicy Format="%(name_id_policy)s" AllowCreate="true" /> %(requested_authn_context_str)s </samlp:AuthnRequest>""" % \ { 'id': uid, 'provider_name': provider_name_str, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'name_id_policy': name_id_policy_format, 'requested_authn_context_str': requested_authn_context_str, } self._authn_request = request
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) 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 __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True): """ Constructs the AuthnRequest object. :param settings: OSetting data :type return_to: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNRequest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNRequest will set the Ispassive='true'. :type is_passive: bool :param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element. :type set_nameid_policy: bool """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML( OneLogin_Saml2_Utils.now()) # destination = idp_data['singleSignOnService']['url'] destination = 'https://fed.paci.gov.kw/idp/SSO.saml2' provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data.keys() if 'en-US' in langs: lang = 'en-US' else: lang = langs[0] if 'displayname' in organization_data[lang] and organization_data[ lang]['displayname'] is not None: provider_name_str = "\n" + ' ProviderName="%s"' % organization_data[ lang]['displayname'] force_authn_str = '' if force_authn is True: force_authn_str = "\n" + ' ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = "\n" + ' IsPassive="true"' nameid_policy_str = '' if set_nameid_policy: name_id_policy_format = sp_data['NameIDFormat'] if 'wantNameIdEncrypted' in security and security[ 'wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED nameid_policy_str = """ <samlp:NameIDPolicy Format="%s" AllowCreate="true" />""" % name_id_policy_format requested_authn_context_str = '' if 'requestedAuthnContext' in security.keys( ) and security['requestedAuthnContext'] is not False: authn_comparison = 'exact' if 'requestedAuthnContextComparison' in security.keys(): authn_comparison = security['requestedAuthnContextComparison'] if security['requestedAuthnContext'] is True: requested_authn_context_str = "\n" + """ <samlp:RequestedAuthnContext Comparison="%s"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" % authn_comparison else: requested_authn_context_str = "\n" + ' <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </samlp:RequestedAuthnContext>' attr_consuming_service_str = '' if 'attributeConsumingService' in sp_data and sp_data[ 'attributeConsumingService']: attr_consuming_service_str = 'AttributeConsumingServiceIndex="1"' certificate = "MIIDWTCCAsKgAwIBAgIJAN2rSHgfq3x/MA0GCSqGSIb3DQEBBQUAMHwxCzAJBgNVBAYTAktXMRwwGgYDVQQIExNUaGUgU1RBVEUgT0YgS1VXQUlUMQ8wDQYDVQQHEwZLdXdhaXQxJDAiBgNVBAoTG01pbmlzdHJ5IG9mIEZvcmVpZ24gQWZmYWlyczEYMBYGA1UEAxMPYWlkLm1vZmEuZ292Lmt3MB4XDTE3MDgyODA2NDQ0M1oXDTI3MDgyODA2NDQ0M1owfDELMAkGA1UEBhMCS1cxHDAaBgNVBAgTE1RoZSBTVEFURSBPRiBLVVdBSVQxDzANBgNVBAcTBkt1d2FpdDEkMCIGA1UEChMbTWluaXN0cnkgb2YgRm9yZWlnbiBBZmZhaXJzMRgwFgYDVQQDEw9haWQubW9mYS5nb3Yua3cwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANQe0kiwithRqgLMU6jx8NG9PVrkCAVF84sL73jENoFac8ZmT398U3Gw5ZUfJlVVvfU4VyFCI7WCLbjzuFwu2McCdosa0CaG/HMDdYuDmXyOMCbyGKii5txuqlo6o35wq09hefL69i1yMIBdcMqPQLYVJVa6/loOMV++/QkEzA7xAgMBAAGjgeIwgd8wHQYDVR0OBBYEFKEdEz9D/YkpcYE6lpYvpiEy6WjgMIGvBgNVHSMEgacwgaSAFKEdEz9D/YkpcYE6lpYvpiEy6WjgoYGApH4wfDELMAkGA1UEBhMCS1cxHDAaBgNVBAgTE1RoZSBTVEFURSBPRiBLVVdBSVQxDzANBgNVBAcTBkt1d2FpdDEkMCIGA1UEChMbTWluaXN0cnkgb2YgRm9yZWlnbiBBZmZhaXJzMRgwFgYDVQQDEw9haWQubW9mYS5nb3Yua3eCCQDdq0h4H6t8fzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAImSBs54cT0LmLJ2tQEC/vfuPcUptSnhI7OZwBMCpND669ajfMxN6YuxDH4Nf4yR1cpD+0XRbMNtrfxq8XZ/5khsjI1uBDJ2odSu+/MiBe7P5ZMdVzM0GIq1hCT9ZZ6O6056ykP8QhoLJsGw3qm+N1FiGvDTLnxwBV4p9lLES5CX" signature_value = 'owbA6nJRn8TMQojq27rkqMBk+z2s8Fly1F68MEMd1InH6vFpVQqvwn7NrEP7YEJnTiHH3y8vrQvpHqBYuXoJjoZpjLdmV3jlprrzjDF+ZFUeqqfUO9h8JAVPTtxwrIEj0bfzH76pCU9h+Fu0kEekQ0UjKGHUEOZbd1+W7lmcc7U=' assertion__consumer_service_url = 'https://api.mofa2.mykuwaitnet.net/saml?acs' digest_value = 'Eph2yJzbGPhlVQThAl1OHWF/bmM=' saml_issuer = 'https://api.mofa2.mykuwaitnet.net/' request = """<samlp:AuthnRequest ID="%(id)s" Version="2.0" IssueInstant="%(issue_instant)s" Destination="%(destination)s" ForceAuthn="false" IsPassive="false" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="%(assertion__consumer_service_url)s" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">%(saml_issuer)s</saml:Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI="#%(id)s"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><InclusiveNamespaces PrefixList="#default samlp saml ds xs xsi" xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>%(digest_value)s</DigestValue></Reference></SignedInfo><SignatureValue>%(signature_value)s</SignatureValue><KeyInfo><X509Data><X509Certificate>%(certificate)s</X509Certificate></X509Data></KeyInfo></Signature><samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" AllowCreate="true" /></samlp:AuthnRequest>""" % \ { 'id': uid, 'provider_name': provider_name_str, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'nameid_policy_str': nameid_policy_str, 'requested_authn_context_str': requested_authn_context_str, 'attr_consuming_service_str': attr_consuming_service_str, 'certificate': certificate, 'signature_value': signature_value, 'digest_value': digest_value, 'assertion__consumer_service_url': assertion__consumer_service_url, 'saml_issuer':saml_issuer } self.__authn_request = request
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.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( etree.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( etree.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) # 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.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', 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.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) # 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 = err.__str__() debug = self.__settings.is_debug_active() if debug: print err.__str__() if raise_exceptions: raise err return False
def __init__(self, settings, force_authn=False, is_passive=False): """ Constructs the AuthnRequest object. :param settings: OSetting data :type return_to: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNReuqest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNReuqest will set the Ispassive='true'. :type is_passive: bool """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) destination = idp_data["singleSignOnService"]["url"] name_id_policy_format = sp_data["NameIDFormat"] if "wantNameIdEncrypted" in security and security["wantNameIdEncrypted"]: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED provider_name_str = "" organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data.keys() if "en-US" in langs: lang = "en-US" else: lang = langs[0] if "displayname" in organization_data[lang] and organization_data[lang]["displayname"] is not None: provider_name_str = 'ProviderName="%s"' % organization_data[lang]["displayname"] force_authn_str = "" if force_authn is True: force_authn_str = 'ForceAuthn="true"' is_passive_str = "" if is_passive is True: is_passive_str = 'IsPassive="true"' requested_authn_context_str = "" if "requestedAuthnContext" in security.keys() and security["requestedAuthnContext"] is not False: authn_comparison = "exact" if "requestedAuthnContextComparison" in security.keys(): authn_comparison = security["requestedAuthnContextComparison"] if security["requestedAuthnContext"] is True: requested_authn_context_str = ( """ <samlp:RequestedAuthnContext Comparison="%s"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" % authn_comparison ) else: requested_authn_context_str = ' <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security["requestedAuthnContext"]: requested_authn_context_str += ( "<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>" % authn_context ) requested_authn_context_str += " </samlp:RequestedAuthnContext>" attr_consuming_service_str = "" if "attributeConsumingService" in sp_data and sp_data["attributeConsumingService"]: # TODO: Do we have to account for the case when we have multiple attributeconsumers? # like will the index be > 1? attr_consuming_service_str = 'AttributeConsumingServiceIndex="1"' request = """<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0" %(provider_name)s %(force_authn_str)s %(is_passive_str)s IssueInstant="%(issue_instant)s" Destination="%(destination)s" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="%(assertion_url)s" %(attr_consuming_service_str)s> <saml:Issuer>%(entity_id)s</saml:Issuer> <samlp:NameIDPolicy Format="%(name_id_policy)s" AllowCreate="true" /> %(requested_authn_context_str)s </samlp:AuthnRequest>""" % { "id": uid, "provider_name": provider_name_str, "force_authn_str": force_authn_str, "is_passive_str": is_passive_str, "issue_instant": issue_instant, "destination": destination, "assertion_url": sp_data["assertionConsumerService"]["url"], "entity_id": sp_data["entityId"], "name_id_policy": name_id_policy_format, "requested_authn_context_str": requested_authn_context_str, "attr_consuming_service_str": attr_consuming_service_str, } self.__authn_request = request
def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None): """ Constructs the AuthnRequest object. :param settings: OSetting data :type settings: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNRequest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNRequest will set the Ispassive='true'. :type is_passive: bool :param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element. :type set_nameid_policy: bool :param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated :type name_id_value_req: string """ self._settings = settings sp_data = self._settings.get_sp_data() idp_data = self._settings.get_idp_data() security = self._settings.get_security_data() self._id = self._generate_request_id() issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML( OneLogin_Saml2_Utils.now()) destination = idp_data['singleSignOnService']['url'] provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data if 'en-US' in langs: lang = 'en-US' else: lang = sorted(langs)[0] display_name = 'displayname' in organization_data[ lang] and organization_data[lang]['displayname'] if display_name: provider_name_str = "\n" + ' ProviderName="%s"' % organization_data[ lang]['displayname'] force_authn_str = '' if force_authn is True: force_authn_str = "\n" + ' ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = "\n" + ' IsPassive="true"' subject_str = '' if name_id_value_req: subject_str = """ <saml:Subject> <saml:NameID Format="%s">%s</saml:NameID> <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"></saml:SubjectConfirmation> </saml:Subject>""" % (sp_data['NameIDFormat'], name_id_value_req) nameid_policy_str = '' if set_nameid_policy: name_id_policy_format = sp_data['NameIDFormat'] if security['wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED nameid_policy_str = """ <samlp:NameIDPolicy Format="%s" AllowCreate="true" />""" % name_id_policy_format requested_authn_context_str = '' if security['requestedAuthnContext'] is not False: authn_comparison = security['requestedAuthnContextComparison'] if security['requestedAuthnContext'] is True: requested_authn_context_str = """ <samlp:RequestedAuthnContext Comparison="%s"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" % authn_comparison else: requested_authn_context_str = ' <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </samlp:RequestedAuthnContext>' attr_consuming_service_str = '' if 'attributeConsumingService' in sp_data and sp_data[ 'attributeConsumingService']: attr_consuming_service_str = "\n AttributeConsumingServiceIndex=\"%s\"" % sp_data[ 'attributeConsumingService'].get('index', '1') request = OneLogin_Saml2_Templates.AUTHN_REQUEST % \ { 'id': self._id, 'provider_name': provider_name_str, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'subject_str': subject_str, 'nameid_policy_str': nameid_policy_str, 'requested_authn_context_str': requested_authn_context_str, 'attr_consuming_service_str': attr_consuming_service_str, 'acs_binding': sp_data['assertionConsumerService'].get('binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST') } self._authn_request = request
def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True): """ Constructs the AuthnRequest object. :param settings: OSetting data :type return_to: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNRequest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNRequest will set the Ispassive='true'. :type is_passive: bool :param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element. :type set_nameid_policy: bool """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML( OneLogin_Saml2_Utils.now()) # destination = idp_data['singleSignOnService']['url'] #destination = 'https://fed.paci.gov.kw/idp/SSO.saml2' destination = 'https://smartidqa2.paci.gov.kw/' provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data.keys() if 'en-US' in langs: lang = 'en-US' else: lang = langs[0] if 'displayname' in organization_data[lang] and organization_data[ lang]['displayname'] is not None: provider_name_str = "\n" + ' ProviderName="%s"' % organization_data[ lang]['displayname'] force_authn_str = '' if force_authn is True: force_authn_str = "\n" + ' ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = "\n" + ' IsPassive="true"' nameid_policy_str = '' if set_nameid_policy: name_id_policy_format = sp_data['NameIDFormat'] if 'wantNameIdEncrypted' in security and security[ 'wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED nameid_policy_str = """ <samlp:NameIDPolicy Format="%s" AllowCreate="true" />""" % name_id_policy_format requested_authn_context_str = '' if 'requestedAuthnContext' in security.keys( ) and security['requestedAuthnContext'] is not False: authn_comparison = 'exact' if 'requestedAuthnContextComparison' in security.keys(): authn_comparison = security['requestedAuthnContextComparison'] if security['requestedAuthnContext'] is True: requested_authn_context_str = "\n" + """ <samlp:RequestedAuthnContext Comparison="%s"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" % authn_comparison else: requested_authn_context_str = "\n" + ' <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </samlp:RequestedAuthnContext>' attr_consuming_service_str = '' if 'attributeConsumingService' in sp_data and sp_data[ 'attributeConsumingService']: attr_consuming_service_str = 'AttributeConsumingServiceIndex="1"' certificate = "MIIDqzCCApKgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBvMQswCQYDVQQGEwJrdzEPMA0GA1UECAwGS3V3YWl0MSQwIgYDVQQKDBtNaW5pc3RyeSBvZiBGb3JlaWduIEFmZmFpcnMxKTAnBgNVBAMMIGh0dHBzOi8vYXBpLmRldi5haWQubW9mYS5nb3Yua3cvMB4XDTE4MDQwMjA3MTM0MloXDTI4MDMzMDA3MTM0MlowbzELMAkGA1UEBhMCa3cxDzANBgNVBAgMBkt1d2FpdDEkMCIGA1UECgwbTWluaXN0cnkgb2YgRm9yZWlnbiBBZmZhaXJzMSkwJwYDVQQDDCBodHRwczovL2FwaS5kZXYuYWlkLm1vZmEuZ292Lmt3LzCCASMwDQYJKoZIhvcNAQEBBQADggEQADCCAQsCggECAM0iMD7x+k44+WJlRwoBSp9WA7maFhaGLZl0bK44aYZ9HRK7LdTJdkbBlDb3csQHgGzoPZliFj+Zp7NS0VWpi1r5qu4cWzhUjUWzRck7Kb3pL/v/n4ipzx+5jo9S5MRE+aGXJJ7NnaR84D5q9LQC3vt9bUj+ar4mpbYu+20IN0MkyKlnn+1YZF9oXZ9k3IrSsOUbeyXswM2ICmowxfLj9zaXYQM7CX6XB9KTThSplr62AgayXCjVLmhPhhZXMxZ+d/H4wdX+mDYcA+v+UfCa+tmywn0A3DsgiBMm1iHOo9jJou4Q/6rebU0PcEf5m4/dz/WdSxtiirDlGdNdJ4xqoeHTAgMBAAGjUDBOMB0GA1UdDgQWBBQC+/qFWLrqp7LWVLqgS4ud/pin5zAfBgNVHSMEGDAWgBQC+/qFWLrqp7LWVLqgS4ud/pin5zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAgCwpKemu0QnnzkTyNs/o3mZqgMxxJopXHmzLYECAsGoN6901Wnj0NinGPCl1K7MSGhtGeu2yieQx0cDExLhOEdlu9LPwYjp2iZ7Nv/dxpEz3RyJAcnaf+vI2A8dBUI9eFRQzefPIWOcEVY/qaTuqQVUgGoi+1qfNADhqKHyQdqhgPy7tulaUaXuW2WHnjcEjyy8G6wqbvfcLXmSl2akXh29ICUErKm8PT5n7FNefQGmg8AuvSEnfH+NB06/Qcqy5x6+Lw/OOJo0DkR3CghhMgA/jqpnhO3LgT+dG+gIREl1rWMCiX/8xwomWpIcEAksWSL5ZKasUSKf6wpnHJoeiaMyGQ==" signature_value = 'owbA6nJRn8TMQojq27rkqMBk+z2s8Fly1F68MEMd1InH6vFpVQqvwn7NrEP7YEJnTiHH3y8vrQvpHqBYuXoJjoZpjLdmV3jlprrzjDF+ZFUeqqfUO9h8JAVPTtxwrIEj0bfzH76pCU9h+Fu0kEekQ0UjKGHUEOZbd1+W7lmcc7U=' assertion__consumer_service_url = 'https://api.dev.aid.mofa.gov.kw/saml?acs' digest_value = 'Eph2yJzbGPhlVQThAl1OHWF/bmM=' saml_issuer = 'https://api.dev.aid.mofa.gov.kw/' request = """<samlp:AuthnRequest ID="%(id)s" Version="2.0" IssueInstant="%(issue_instant)s" Destination="%(destination)s" ForceAuthn="false" IsPassive="false" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="%(assertion__consumer_service_url)s" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">%(saml_issuer)s</saml:Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI="#%(id)s"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><InclusiveNamespaces PrefixList="#default samlp saml ds xs xsi" xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>%(digest_value)s</DigestValue></Reference></SignedInfo><SignatureValue>%(signature_value)s</SignatureValue><KeyInfo><X509Data><X509Certificate>%(certificate)s</X509Certificate></X509Data></KeyInfo></Signature><samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" AllowCreate="true" /></samlp:AuthnRequest>""" % \ { 'id': uid, 'provider_name': provider_name_str, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'nameid_policy_str': nameid_policy_str, 'requested_authn_context_str': requested_authn_context_str, 'attr_consuming_service_str': attr_consuming_service_str, 'certificate': certificate, 'signature_value': signature_value, 'digest_value': digest_value, 'assertion__consumer_service_url': assertion__consumer_service_url, 'saml_issuer':saml_issuer } self.__authn_request = request
def __init__(self, settings, request=None, name_id=None, session_index=None, nq=None, name_id_format=None): """ Constructs the Logout Request object. :param settings: Setting data :type request_data: OneLogin_Saml2_Settings :param request: Optional. A LogoutRequest to be loaded instead build one. :type request: string :param name_id: The NameID that will be set in the LogoutRequest. :type name_id: string :param session_index: SessionIndex that identifies the session of the user. :type session_index: string :param nq: IDP Name Qualifier :type: string :param name_id_format: The NameID Format that will be set in the LogoutRequest. :type: string """ self.__settings = settings self.__error = None self.id = None if request is None: sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML( OneLogin_Saml2_Utils.now()) cert = None if 'nameIdEncrypted' in security and security['nameIdEncrypted']: exists_multix509enc = 'x509certMulti' in idp_data and \ 'encryption' in idp_data['x509certMulti'] and \ idp_data['x509certMulti']['encryption'] if exists_multix509enc: cert = idp_data['x509certMulti']['encryption'][0] else: cert = idp_data['x509cert'] if name_id is not None: if not name_id_format and sp_data[ 'NameIDFormat'] != OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED: name_id_format = sp_data['NameIDFormat'] else: name_id_format = OneLogin_Saml2_Constants.NAMEID_ENTITY spNameQualifier = None if name_id_format == OneLogin_Saml2_Constants.NAMEID_ENTITY: name_id = idp_data['entityId'] nq = None elif nq is not None: # We only gonna include SPNameQualifier if NameQualifier is provided # SPID: no! spNameQualifier = sp_data['entityId'] pass name_id_obj = OneLogin_Saml2_Utils.generate_name_id( name_id, spNameQualifier, name_id_format, cert, False, nq) if session_index: session_index_str = '<samlp:SessionIndex>%s</samlp:SessionIndex>' % session_index else: session_index_str = '' destination_url_parts = urlparse( idp_data['singleLogoutService']['url']) destination = "%s://%s" % (destination_url_parts.scheme, destination_url_parts.netloc) logout_request = """<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0" IssueInstant="%(issue_instant)s" Destination="%(single_logout_url)s"> <saml:Issuer NameQualifier="%(entity_id)s" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" >%(entity_id)s</saml:Issuer> %(name_id)s %(session_index)s </samlp:LogoutRequest>""" % \ { 'id': uid, 'issue_instant': issue_instant, 'single_logout_url': destination, 'entity_id': sp_data['entityId'], 'name_id': name_id_obj, 'session_index': session_index_str, } else: decoded = b64decode(request) # We try to inflate try: inflated = decompress(decoded, -15) logout_request = inflated except Exception: logout_request = decoded self.id = self.get_id(logout_request) self.__logout_request = logout_request
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 attribute_statement_nodes = self.__query_assertion( '/saml:AttributeStatement') if not attribute_statement_nodes: raise Exception( 'There is no AttributeStatement on the Response') # Validates Asserion 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: cert = idp_data.get('x509cert', None) fingerprint = idp_data.get('certFingerprint', None) fingerprintalg = idp_data.get('certFingerprintAlgorithm', None) # Only validates the first sign found if '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements: document_to_validate = self.document 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(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 __init__(self, settings, request=None, name_id=None, session_index=None, nq=None, name_id_format=None, spnq=None): """ Constructs the Logout Request object. :param settings: Setting data :type settings: OneLogin_Saml2_Settings :param request: Optional. A LogoutRequest to be loaded instead build one. :type request: string :param name_id: The NameID that will be set in the LogoutRequest. :type name_id: string :param session_index: SessionIndex that identifies the session of the user. :type session_index: string :param nq: IDP Name Qualifier :type: string :param name_id_format: The NameID Format that will be set in the LogoutRequest. :type: string :param spnq: SP Name Qualifier :type: string """ self.__settings = settings self.__error = None self.id = None if request is None: sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML( OneLogin_Saml2_Utils.now()) cert = None if security['nameIdEncrypted']: exists_multix509enc = 'x509certMulti' in idp_data and \ 'encryption' in idp_data['x509certMulti'] and \ idp_data['x509certMulti']['encryption'] if exists_multix509enc: cert = idp_data['x509certMulti']['encryption'][0] else: cert = self.__settings.get_idp_cert() if name_id is not None: if not name_id_format and sp_data[ 'NameIDFormat'] != OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED: name_id_format = sp_data['NameIDFormat'] else: name_id = idp_data['entityId'] name_id_format = OneLogin_Saml2_Constants.NAMEID_ENTITY # From saml-core-2.0-os 8.3.6, when the entity Format is used: # "The NameQualifier, SPNameQualifier, and SPProvidedID attributes # MUST be omitted. if name_id_format and name_id_format == OneLogin_Saml2_Constants.NAMEID_ENTITY: nq = None spnq = None # NameID Format UNSPECIFIED omitted if name_id_format and name_id_format == OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED: name_id_format = None name_id_obj = OneLogin_Saml2_Utils.generate_name_id( name_id, spnq, name_id_format, cert, False, nq) if session_index: session_index_str = '<samlp:SessionIndex>%s</samlp:SessionIndex>' % session_index else: session_index_str = '' logout_request = OneLogin_Saml2_Templates.LOGOUT_REQUEST % \ { 'id': uid, 'issue_instant': issue_instant, 'single_logout_url': idp_data['singleLogoutService']['url'], 'entity_id': sp_data['entityId'], 'name_id': name_id_obj, 'session_index': session_index_str, } else: logout_request = OneLogin_Saml2_Utils.decode_base64_and_inflate( request, ignore_zip=True) self.id = self.get_id(logout_request) self.__logout_request = compat.to_string(logout_request)
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 __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None): """ Constructs the AuthnRequest object. :param settings: OSetting data :type settings: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNRequest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNRequest will set the Ispassive='true'. :type is_passive: bool :param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element. :type set_nameid_policy: bool :param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated :type name_id_value_req: string """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) destination = idp_data['singleSignOnService']['url'] force_authn_str = '' if force_authn is True: force_authn_str = "\n" + ' ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = "\n" + ' IsPassive="true"' subject_str = '' if name_id_value_req: subject_str = """ <saml:Subject> <saml:NameID Format="%s">%s</saml:NameID> <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"></saml:SubjectConfirmation> </saml:Subject>""" % (sp_data['NameIDFormat'], name_id_value_req) nameid_policy_str = '' if set_nameid_policy: name_id_policy_format = sp_data['NameIDFormat'] if security['wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED nameid_policy_str = """ <samlp:NameIDPolicy Format="%s"/>""" % name_id_policy_format requested_authn_context_str = '' if security['requestedAuthnContext'] is not False: authn_comparison = 'exact' if 'requestedAuthnContextComparison' in security.keys(): authn_comparison = security['requestedAuthnContextComparison'] if security['requestedAuthnContext'] is True: requested_authn_context_str = """ <samlp:RequestedAuthnContext Comparison="%s"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" % authn_comparison else: requested_authn_context_str = ' <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </samlp:RequestedAuthnContext>' attr_consuming_service_str = '' if 'attributeConsumingService' in sp_data and sp_data['attributeConsumingService']: attr_consuming_service_str = "\n AttributeConsumingServiceIndex=\"0\"" attr_issuer_str = '' if 'Format' in sp_data.get('Issuer', dict()) and sp_data.get('Issuer', dict()).get('Format'): attr_issuer_str += " Format=\"%s\"" % sp_data['Issuer']['Format'] if 'NameQualifier' in sp_data.get('Issuer', dict()) and sp_data.get('Issuer', dict()).get('NameQualifier'): attr_issuer_str += " NameQualifier=\"%s\"" % sp_data['Issuer']['NameQualifier'] request = OneLogin_Saml2_Templates.AUTHN_REQUEST % \ { 'id': uid, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'attr_issuer_str': attr_issuer_str, 'subject_str': subject_str, 'nameid_policy_str': nameid_policy_str, 'requested_authn_context_str': requested_authn_context_str, 'attr_consuming_service_str': attr_consuming_service_str, } self.__authn_request = request
def __init__(self, settings, request=None, name_id=None, session_index=None): """ Constructs the Logout Request object. :param settings: Setting data :type settings: OneLogin_Saml2_Settings :param request: Optional. A LogoutRequest to be loaded instead build one. :type request: string :param name_id: The NameID that will be set in the LogoutRequest. :type name_id: string :param session_index: SessionIndex that identifies the session of the user. :type session_index: string """ self.__settings = settings self.__error = None self.id = None if request is None: sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) cert = None if security['nameIdEncrypted']: cert = idp_data['x509cert'] if name_id is not None: name_id_format = sp_data['NameIDFormat'] sp_name_qualifier = None else: name_id = idp_data['entityId'] name_id_format = OneLogin_Saml2_Constants.NAMEID_ENTITY sp_name_qualifier = sp_data['entityId'] name_id_obj = OneLogin_Saml2_Utils.generate_name_id( name_id, sp_name_qualifier, name_id_format, cert ) if session_index: session_index_str = '<samlp:SessionIndex>%s</samlp:SessionIndex>' % session_index else: session_index_str = '' logout_request = OneLogin_Saml2_Templates.LOGOUT_REQUEST % \ { 'id': uid, 'issue_instant': issue_instant, 'single_logout_url': idp_data['singleLogoutService']['url'], 'entity_id': sp_data['entityId'], 'name_id': name_id_obj, 'session_index': session_index_str, } else: logout_request = OneLogin_Saml2_Utils.decode_base64_and_inflate(request, ignore_zip=True) self.id = self.get_id(logout_request) self.__logout_request = logout_request
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', 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 __init__(self, settings, force_authn=False, is_passive=False): """ Constructs the AuthnRequest object. :param settings: OSetting data :type settings: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNReuqest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNReuqest will set the Ispassive='true'. :type is_passive: bool """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) destination = idp_data['singleSignOnService']['url'] name_id_policy_format = sp_data['NameIDFormat'] if security['wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data if 'en-US' in langs: lang = 'en-US' else: lang = sorted(langs)[0] display_name = 'displayname' in organization_data[lang] and organization_data[lang]['displayname'] if display_name: provider_name_str = 'ProviderName="%s"' % organization_data[lang]['displayname'] force_authn_str = '' if force_authn is True: force_authn_str = 'ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = 'IsPassive="true"' requested_authn_context_str = '' if security['requestedAuthnContext'] is not False: if security['requestedAuthnContext'] is True: requested_authn_context_str = """ <samlp:RequestedAuthnContext Comparison="exact"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" else: requested_authn_context_str = ' <samlp:RequestedAuthnContext Comparison="exact">' for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </samlp:RequestedAuthnContext>' request = OneLogin_Saml2_Templates.AUTHN_REQUEST % \ { 'id': uid, 'provider_name': provider_name_str, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'name_id_policy': name_id_policy_format, 'requested_authn_context_str': requested_authn_context_str, } self.__authn_request = request
def __init__(self, settings, request=None, name_id=None, session_index=None, nq=None): """ Constructs the Logout Request object. :param settings: Setting data :type settings: OneLogin_Saml2_Settings :param request: Optional. A LogoutRequest to be loaded instead build one. :type request: string :param name_id: The NameID that will be set in the LogoutRequest. :type name_id: string :param session_index: SessionIndex that identifies the session of the user. :type session_index: string :param nq: IDP Name Qualifier :type: string """ self.__settings = settings self.__error = None self.id = None if request is None: sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) cert = None if security['nameIdEncrypted']: cert = idp_data['x509cert'] if name_id is not None: name_id_format = sp_data['NameIDFormat'] sp_name_qualifier = None else: name_id = idp_data['entityId'] name_id_format = OneLogin_Saml2_Constants.NAMEID_ENTITY sp_name_qualifier = sp_data['entityId'] name_id_obj = OneLogin_Saml2_Utils.generate_name_id( name_id, sp_name_qualifier, name_id_format, cert, nq=nq, ) if session_index: session_index_str = '<samlp:SessionIndex>%s</samlp:SessionIndex>' % session_index else: session_index_str = '' logout_request = OneLogin_Saml2_Templates.LOGOUT_REQUEST % \ { 'id': uid, 'issue_instant': issue_instant, 'single_logout_url': idp_data['singleLogoutService']['url'], 'entity_id': sp_data['entityId'], 'name_id': name_id_obj, 'session_index': session_index_str, } else: logout_request = OneLogin_Saml2_Utils.decode_base64_and_inflate(request, ignore_zip=True) self.id = self.get_id(logout_request) self.__logout_request = compat.to_string(logout_request)
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): """ 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 __init__(self, settings, request=None, name_id=None, session_index=None, nq=None): """ Constructs the Logout Request object. :param settings: Setting data :type request_data: OneLogin_Saml2_Settings :param request: Optional. A LogoutRequest to be loaded instead build one. :type request: string :param name_id: The NameID that will be set in the LogoutRequest. :type name_id: string :param session_index: SessionIndex that identifies the session of the user. :type session_index: string :param nq: IDP Name Qualifier :type: string """ self.__settings = settings self.__error = None self.id = None if request is None: sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) cert = None if 'nameIdEncrypted' in security and security['nameIdEncrypted']: cert = idp_data['x509cert'] if name_id is not None: nameIdFormat = sp_data['NameIDFormat'] spNameQualifier = None else: name_id = idp_data['entityId'] nameIdFormat = OneLogin_Saml2_Constants.NAMEID_ENTITY spNameQualifier = sp_data['entityId'] name_id_obj = OneLogin_Saml2_Utils.generate_name_id( name_id, spNameQualifier, nameIdFormat, cert ) if session_index: session_index_str = '<samlp:SessionIndex>%s</samlp:SessionIndex>' % session_index else: session_index_str = '' logout_request = """<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0" IssueInstant="%(issue_instant)s" Destination="%(single_logout_url)s"> <saml:Issuer>%(entity_id)s</saml:Issuer> %(name_id)s %(session_index)s </samlp:LogoutRequest>""" % \ { 'id': uid, 'issue_instant': issue_instant, 'single_logout_url': idp_data['singleLogoutService']['url'], 'entity_id': sp_data['entityId'], 'name_id': name_id_obj, 'session_index': session_index_str, } else: decoded = b64decode(request) # We try to inflate try: inflated = decompress(decoded, -15) logout_request = inflated except Exception: logout_request = decoded self.id = self.get_id(logout_request) self.__logout_request = logout_request
def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True): """ Constructs the AuthnRequest object. :param settings: OSetting data :type return_to: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNReuqest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNReuqest will set the Ispassive='true'. :type is_passive: bool :param set_nameid_policy: Optional argument. When true the AuthNReuqest will set a nameIdPolicy element. :type set_nameid_policy: bool """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) destination = idp_data['singleSignOnService']['url'] provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data.keys() if 'en-US' in langs: lang = 'en-US' else: lang = langs[0] if 'displayname' in organization_data[lang] and organization_data[lang]['displayname'] is not None: provider_name_str = "\n" + ' ProviderName="%s"' % organization_data[lang]['displayname'] force_authn_str = '' if force_authn is True: force_authn_str = "\n" + ' ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = "\n" + ' IsPassive="true"' nameid_policy_str = '' if set_nameid_policy: name_id_policy_format = sp_data['NameIDFormat'] if 'wantNameIdEncrypted' in security and security['wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED nameid_policy_str = """ <samlp:NameIDPolicy Format="%s" AllowCreate="true" />""" % name_id_policy_format requested_authn_context_str = '' if 'requestedAuthnContext' in security.keys() and security['requestedAuthnContext'] is not False: authn_comparison = 'exact' if 'requestedAuthnContextComparison' in security.keys(): authn_comparison = security['requestedAuthnContextComparison'] if security['requestedAuthnContext'] is True: requested_authn_context_str = "\n" + """ <samlp:RequestedAuthnContext Comparison="%s"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" % authn_comparison else: requested_authn_context_str = "\n" + ' <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </samlp:RequestedAuthnContext>' attr_consuming_service_str = '' if 'attributeConsumingService' in sp_data and sp_data['attributeConsumingService']: attr_consuming_service_str = 'AttributeConsumingServiceIndex="1"' request = """<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0"%(provider_name)s%(force_authn_str)s%(is_passive_str)s IssueInstant="%(issue_instant)s" Destination="%(destination)s" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="%(assertion_url)s" %(attr_consuming_service_str)s> <saml:Issuer>%(entity_id)s</saml:Issuer>%(nameid_policy_str)s%(requested_authn_context_str)s </samlp:AuthnRequest>""" % \ { 'id': uid, 'provider_name': provider_name_str, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'nameid_policy_str': nameid_policy_str, 'requested_authn_context_str': requested_authn_context_str, 'attr_consuming_service_str': attr_consuming_service_str } #from https://github.com/onelogin/python-saml/pull/78. credit to @tachang # Only the urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST binding gets the enveloped signature if settings.get_idp_data()['singleSignOnService'].get('binding', None) == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' and \ security['authnRequestsSigned'] is True: log.debug("Generating AuthnRequest using urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST binding") if 'signatureAlgorithm' in security: key = settings.get_sp_key() if not key: raise OneLogin_Saml2_Error("Attempt to sign the AuthnRequest but unable to load the SP private key") cert = settings.get_sp_cert() if not key: raise OneLogin_Saml2_Error("Attempt to sign the AuthnRequest but unable to load the SP cert") doc = parseString(request) security_algo = security['signatureAlgorithm'] digest_method_algo = security['digestMethodAlgorithm'] self.__authn_request = OneLogin_Saml2_Utils.add_sign_with_id(doc, uid, key, cert, sign_algorithm=security_algo, digest_algorithm=digest_method_algo, debug=False) log.debug("Generated AuthnRequest: {}".format(self.__authn_request)) else: self.__authn_request = request log.debug("Generated AuthnRequest: {}".format(self.__authn_request)) else: self.__authn_request = request