diff --git a/README.md b/README.md index 3f70a5b1..5f6865a5 100644 --- a/README.md +++ b/README.md @@ -822,17 +822,17 @@ target_url = 'https://example.com' auth.logout(return_to=target_url) ``` -Also there are 4 optional parameters that can be set: +Also there are another 5 optional parameters that can be set: * ``name_id``. That will be used to build the LogoutRequest. If not ``name_id`` parameter is set and the auth object processed a SAML Response with a NameId, then this NameId will be used. * ``session_index``. SessionIndex that identifies the session of the user. * ``nq``. IDP Name Qualifier * ``name_id_format``. The NameID Format that will be set in the LogoutRequest +* ``spnq``: The ``NameID SP NameQualifier`` will be set in the ``LogoutRequest``. If no name_id is provided, the LogoutRequest will contain a NameID with the entity Format. If name_id is provided and no name_id_format is provided, the NameIDFormat of the settings will be used. -If nq is provided, the SPNameQualifier will be also attached to the NameId. If a match on the LogoutResponse ID and the LogoutRequest ID to be sent is required, that LogoutRequest ID must to be extracted and stored for future validation, we can get that ID by: @@ -858,7 +858,12 @@ elif 'sso2' in request.args: # Another SSO init action return_to = '%sattrs/' % request.host_url # but set a custom RelayState URL return redirect(auth.login(return_to)) elif 'slo' in request.args: # SLO action. Will sent a Logout Request to IdP - return redirect(auth.logout()) + nameid = request.session['samlNameId'] + nameid_format = request.session['samlNameIdFormat'] + nameid_nq = request.session['samlNameIdNameQualifier'] + nameid_spnq = request.session['samlNameIdSPNameQualifier'] + session_index = request.session['samlSessionIndex'] + return redirect(auth.logout(None, nameid, session_index, nameid_nq, nameid_format, nameid_spnq)) elif 'acs' in request.args: # Assertion Consumer Service auth.process_response() # Process the Response of the IdP errors = auth.get_errors() # This method receives an array with the errors @@ -867,6 +872,11 @@ elif 'acs' in request.args: # Assertion Consumer Service msg = "Not authenticated" # data retrieved or not (user authenticated) else: request.session['samlUserdata'] = auth.get_attributes() # Retrieves user data + request.session['samlNameId'] = auth.get_nameid() + request.session['samlNameIdFormat'] = auth.get_nameid_format() + request.session['samlNameIdNameQualifier'] = auth.get_nameid_nq() + request.session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq() + request.session['samlSessionIndex'] = auth.get_session_index() self_url = OneLogin_Saml2_Utils.get_self_url(req) if 'RelayState' in request.form and self_url != request.form['RelayState']: return redirect(auth.redirect_to(request.form['RelayState'])) # Redirect if there is a relayState diff --git a/demo-django/demo/settings.py b/demo-django/demo/settings.py index 792ea82f..5305f382 100644 --- a/demo-django/demo/settings.py +++ b/demo-django/demo/settings.py @@ -22,8 +22,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] - +ALLOWED_HOSTS = ['pitbulk.no-ip.org'] # Application definition diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py index 2ae0ebac..40ebdd66 100644 --- a/demo-django/demo/views.py +++ b/demo-django/demo/views.py @@ -50,14 +50,19 @@ def index(request): return_to = OneLogin_Saml2_Utils.get_self_url(req) + reverse('attrs') return HttpResponseRedirect(auth.login(return_to)) elif 'slo' in req['get_data']: - name_id = None - session_index = None + name_id = session_index = name_id_format = name_id_nq = name_id_spnq = None if 'samlNameId' in request.session: name_id = request.session['samlNameId'] if 'samlSessionIndex' in request.session: session_index = request.session['samlSessionIndex'] + if 'samlNameIdFormat' in request.session: + name_id_format = request.session['samlNameIdFormat'] + if 'samlNameIdNameQualifier' in request.session: + name_id_nq = request.session['samlNameIdNameQualifier'] + if 'samlNameIdSPNameQualifier' in request.session: + name_id_spnq = request.session['samlNameIdSPNameQualifier'] - return HttpResponseRedirect(auth.logout(name_id=name_id, session_index=session_index)) + return HttpResponseRedirect(auth.logout(name_id=name_id, session_index=session_index, nq=name_id_nq, name_id_format=name_id_format, spnq=name_id_spnq)) # If LogoutRequest ID need to be stored in order to later validate it, do instead # slo_built_url = auth.logout(name_id=name_id, session_index=session_index) @@ -77,6 +82,9 @@ def index(request): del request.session['AuthNRequestID'] request.session['samlUserdata'] = auth.get_attributes() request.session['samlNameId'] = auth.get_nameid() + request.session['samlNameIdFormat'] = auth.get_nameid_format() + request.session['samlNameIdNameQualifier'] = auth.get_nameid_nq() + request.session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq() request.session['samlSessionIndex'] = auth.get_session_index() if 'RelayState' in req['post_data'] and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']: return HttpResponseRedirect(auth.redirect_to(req['post_data']['RelayState'])) diff --git a/demo-django/saml/settings.json b/demo-django/saml/settings.json index 391b91c1..3758746c 100644 --- a/demo-django/saml/settings.json +++ b/demo-django/saml/settings.json @@ -2,29 +2,29 @@ "strict": true, "debug": true, "sp": { - "entityId": "https:///metadata/", + "entityId": "http://pitbulk.no-ip.org:8000/metadata/", "assertionConsumerService": { - "url": "https:///?acs", + "url": "http://pitbulk.no-ip.org:8000/?acs", "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" }, "singleLogoutService": { - "url": "https:///?sls", + "url": "http://pitbulk.no-ip.org:8000/?sls", "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" }, "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", - "x509cert": "", - "privateKey": "" + "x509cert": "MIICbDCCAdWgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBTMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRgwFgYDVQQDDA9pZHAuZXhhbXBsZS5jb20wHhcNMTQwOTIzMTIyNDA4WhcNNDIwMjA4MTIyNDA4WjBTMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRgwFgYDVQQDDA9pZHAuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOWA+YHU7cvPOrBOfxCscsYTJB+kH3MaA9BFrSHFS+KcR6cw7oPSktIJxUgvDpQbtfNcOkE/tuOPBDoech7AXfvH6d7Bw7xtW8PPJ2mB5Hn/HGW2roYhxmfh3tR5SdwN6i4ERVF8eLkvwCHsNQyK2Ref0DAJvpBNZMHCpS24916/AgMBAAGjUDBOMB0GA1UdDgQWBBQ77/qVeiigfhYDITplCNtJKZTM8DAfBgNVHSMEGDAWgBQ77/qVeiigfhYDITplCNtJKZTM8DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAJO2j/1uO80E5C2PM6Fk9mzerrbkxl7AZ/mvlbOn+sNZE+VZ1AntYuG8ekbJpJtG1YfRfc7EA9mEtqvv4dhv7zBy4nK49OR+KpIBjItWB5kYvrqMLKBa32sMbgqqUqeF1ENXKjpvLSuPdfGJZA3dNa/+Dyb8GGqWe707zLyc5F8m", + "privateKey": "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOWA+YHU7cvPOrBOfxCscsYTJB+kH3MaA9BFrSHFS+KcR6cw7oPSktIJxUgvDpQbtfNcOkE/tuOPBDoech7AXfvH6d7Bw7xtW8PPJ2mB5Hn/HGW2roYhxmfh3tR5SdwN6i4ERVF8eLkvwCHsNQyK2Ref0DAJvpBNZMHCpS24916/AgMBAAECgYEA0wDXZPS9hKqMTNh+nnfONioXBjhA6fQ7GVtWKDxa3ofMoPyt7ejGL/Hnvcv13Vn02UAsFx1bKrCstDqVtYwrWrnmywXyH+o9paJnTmd+cRIjWU8mRvCrxzH5I/Bcvbp1qZoASuqZEaGwNjM6JpW2o3QTmHGMALcLUPfEvhApssECQQDy2e65E86HcFhi/Ta8TQ0odDCNbiWA0bI1Iu8B7z+NAy1D1+WnCd7w2u9U6CF/k2nFHCsvxEoeANM0z7h5T/XvAkEA8e4JqKmDrfdiakQT7nf9svU2jXZtxSbPiIRMafNikDvzZ1vJCZkvdmaWYL70GlDZIwc9ad67rHZ/n/fqX1d0MQJAbRpRsJ5gY+KqItbFt3UaWzlP8sowWR5cZJjsLb9RmsV5mYguKYw6t5R0f33GRu1wUFimYlBaR/5w5MIJi57LywJATO1a5uWX+G5MPewNxmsjIY91XEAHIYR4wzkGLz5z3dciS4BVCZdLD0QJlxPA/MkuckPwFET9uhYn+M7VGKHvUQJBANSDwsY+BdCGpi/WRV37HUfwLl07damaFbW3h08PQx8G8SuF7DpN+FPBcI6VhzrIWNRBxWprkgeGioKNfFWzSaM=" }, "idp": { - "entityId": "https://app.onelogin.com/saml/metadata/", + "entityId": "https://app.onelogin.com/saml/metadata/3dbd155e-be64-4a4d-8fab-e44788bce74f", "singleSignOnService": { - "url": "https://app.onelogin.com/trust/saml2/http-post/sso/", + "url": "https://sgarcia-us-preprod.onelogin.com/trust/saml2/http-redirect/sso/850162", "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" }, "singleLogoutService": { - "url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/", + "url": "https://sgarcia-us-preprod.onelogin.com/trust/saml2/http-redirect/slo/850162", "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" }, - "x509cert": "" + "x509cert": "MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJjaWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwWT25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUyMjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRwtnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xxVRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCBpIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaCFD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXMGI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65chjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIBvlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZWQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==" } -} \ No newline at end of file +} diff --git a/demo-flask/index.py b/demo-flask/index.py index 0f70b777..1f5404dc 100644 --- a/demo-flask/index.py +++ b/demo-flask/index.py @@ -47,32 +47,56 @@ def index(): if 'sso' in request.args: return redirect(auth.login()) + # If AuthNRequest ID need to be stored in order to later validate it, do instead + # sso_built_url = auth.login() + # request.session['AuthNRequestID'] = auth.get_last_request_id() + # return redirect(sso_built_url) elif 'sso2' in request.args: return_to = '%sattrs/' % request.host_url return redirect(auth.login(return_to)) elif 'slo' in request.args: - name_id = None - session_index = None + name_id = session_index = name_id_format = name_id_nq = name_id_spnq = None if 'samlNameId' in session: name_id = session['samlNameId'] if 'samlSessionIndex' in session: session_index = session['samlSessionIndex'] - - return redirect(auth.logout(name_id=name_id, session_index=session_index)) + if 'samlNameIdFormat' in session: + name_id_format = session['samlNameIdFormat'] + if 'samlNameIdNameQualifier' in session: + name_id_nq = session['samlNameIdNameQualifier'] + if 'samlNameIdSPNameQualifier' in session: + name_id_spnq = session['samlNameIdSPNameQualifier'] + + return redirect(auth.logout(name_id=name_id, session_index=session_index, nq=name_id_nq, name_id_format=name_id_format, spnq=name_id_spnq)) + # If LogoutRequest ID need to be stored in order to later validate it, do instead + # slo_built_url = auth.logout(name_id=name_id, session_index=session_index) + # session['LogoutRequestID'] = auth.get_last_request_id() + #return redirect(slo_built_url) elif 'acs' in request.args: - auth.process_response() + request_id = None + if 'AuthNRequestID' in session: + request_id = session['AuthNRequestID'] + + auth.process_response(request_id=request_id) errors = auth.get_errors() not_auth_warn = not auth.is_authenticated() if len(errors) == 0: + if 'AuthNRequestID' in session: + del session['AuthNRequestID'] session['samlUserdata'] = auth.get_attributes() - session['samlNameId'] = auth.get_nameid() + session['samlNameIdFormat'] = auth.get_nameid_format() + session['samlNameIdNameQualifier'] = auth.get_nameid_nq() + session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq() session['samlSessionIndex'] = auth.get_session_index() self_url = OneLogin_Saml2_Utils.get_self_url(req) if 'RelayState' in request.form and self_url != request.form['RelayState']: return redirect(auth.redirect_to(request.form['RelayState'])) elif 'sls' in request.args: + request_id = None + if 'LogoutRequestID' in session: + request_id = session['LogoutRequestID'] dscb = lambda: session.clear() - url = auth.process_slo(delete_session_cb=dscb) + url = auth.process_slo(request_id=request_id, delete_session_cb=dscb) errors = auth.get_errors() if len(errors) == 0: if url is not None: diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py index d9b42a32..0b12bbe7 100644 --- a/src/onelogin/saml2/auth.py +++ b/src/onelogin/saml2/auth.py @@ -53,6 +53,8 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None): self.__attributes = [] self.__nameid = None self.__nameid_format = None + self.__nameid_nq = None + self.__nameid_spnq = None self.__session_index = None self.__session_expiration = None self.__authenticated = False @@ -104,6 +106,8 @@ def process_response(self, request_id=None): self.__attributes = response.get_attributes() self.__nameid = response.get_nameid() self.__nameid_format = response.get_nameid_format() + self.__nameid_nq = response.get_nameid_nq() + self.__nameid_spnq = response.get_nameid_spnq() self.__session_index = response.get_session_index() self.__session_expiration = response.get_session_not_on_or_after() self.__last_message_id = response.get_id() @@ -245,6 +249,24 @@ def get_nameid_format(self): """ return self.__nameid_format + def get_nameid_nq(self): + """ + Returns the nameID NameQualifier of the Assertion. + + :returns: NameID NameQualifier + :rtype: string|None + """ + return self.__nameid_nq + + def get_nameid_spnq(self): + """ + Returns the nameID SP NameQualifier of the Assertion. + + :returns: NameID SP NameQualifier + :rtype: string|None + """ + return self.__nameid_spnq + def get_session_index(self): """ Returns the SessionIndex from the AuthnStatement. @@ -362,7 +384,7 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_ parameters['Signature'] = self.build_request_signature(saml_request, parameters['RelayState'], security['signatureAlgorithm']) return self.redirect_to(self.get_sso_url(), parameters) - def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name_id_format=None): + def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name_id_format=None, spnq=None): """ Initiates the SLO process. @@ -381,6 +403,9 @@ def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name :param name_id_format: The NameID Format that will be set in the LogoutRequest. :type: string + :param spnq: SP Name Qualifier + :type: string + :returns: Redirection url """ slo_url = self.get_slo_url() @@ -400,7 +425,8 @@ def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name name_id=name_id, session_index=session_index, nq=nq, - name_id_format=name_id_format + name_id_format=name_id_format, + spnq=spnq ) self.__last_request = logout_request.get_xml() self.__last_request_id = logout_request.id diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py index e419f585..1cb75efd 100644 --- a/src/onelogin/saml2/logout_request.py +++ b/src/onelogin/saml2/logout_request.py @@ -29,7 +29,7 @@ class OneLogin_Saml2_Logout_Request(object): """ - def __init__(self, settings, request=None, name_id=None, session_index=None, nq=None, name_id_format=None): + 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. @@ -50,6 +50,10 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq= :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 @@ -79,19 +83,23 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq= 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 - spNameQualifier = None - if name_id_format == OneLogin_Saml2_Constants.NAMEID_ENTITY: - name_id = idp_data['entityId'] + # 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 - elif nq is not None: - # We only gonna include SPNameQualifier if NameQualifier is provided - spNameQualifier = sp_data['entityId'] + 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, - spNameQualifier, + spnq, name_id_format, cert, False, diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py index 0ce801ea..cc5e8936 100644 --- a/src/onelogin/saml2/response.py +++ b/src/onelogin/saml2/response.py @@ -512,6 +512,32 @@ def get_nameid_format(self): nameid_format = nameid_data['Format'] return nameid_format + def get_nameid_nq(self): + """ + Gets the NameID NameQualifier provided by the SAML Response from the IdP + + :returns: NameID NameQualifier + :rtype: string|None + """ + nameid_nq = None + nameid_data = self.get_nameid_data() + if nameid_data and 'NameQualifier' in nameid_data.keys(): + nameid_nq = nameid_data['NameQualifier'] + return nameid_nq + + def get_nameid_spnq(self): + """ + Gets the NameID SP NameQualifier provided by the SAML response from the IdP. + + :returns: NameID SP NameQualifier + :rtype: string|None + """ + nameid_spnq = None + nameid_data = self.get_nameid_data() + if nameid_data and 'SPNameQualifier' in nameid_data.keys(): + nameid_spnq = nameid_data['SPNameQualifier'] + return nameid_spnq + def get_session_not_on_or_after(self): """ Gets the SessionNotOnOrAfter from the AuthnStatement diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py index 070a4887..3a57c438 100644 --- a/tests/src/OneLogin/saml2_tests/auth_test.py +++ b/tests/src/OneLogin/saml2_tests/auth_test.py @@ -220,6 +220,9 @@ def testProcessResponseValid(self): self.assertEqual(auth.get_attribute('mail'), attributes['mail']) session_index = auth.get_session_index() self.assertEqual('_6273d77b8cde0c333ec79d22a9fa0003b9fe2d75cb', session_index) + self.assertEqual("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", auth.get_nameid_format()) + self.assertIsNone(auth.get_nameid_nq()) + self.assertEqual("http://stuff.com/endpoints/metadata.php", auth.get_nameid_spnq()) def testRedirectTo(self): """ @@ -1002,6 +1005,70 @@ def testGetNameIdFormat(self): self.assertTrue(auth.is_authenticated()) self.assertEqual("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified", auth.get_nameid_format()) + def testGetNameIdNameQualifier(self): + """ + Tests the get_nameid_nq method of the OneLogin_Saml2_Auth + """ + settings = self.loadSettingsJSON() + message = self.file_contents(join(self.data_path, 'responses', 'valid_response_with_namequalifier.xml.base64')) + request_data = self.get_request() + request_data['post_data'] = { + 'SAMLResponse': message + } + auth = OneLogin_Saml2_Auth(request_data, old_settings=settings) + self.assertIsNone(auth.get_nameid_nq()) + auth.process_response() + self.assertTrue(auth.is_authenticated()) + self.assertEqual("https://test.example.com/saml/metadata", auth.get_nameid_nq()) + + def testGetNameIdNameQualifier2(self): + """ + Tests the get_nameid_nq method of the OneLogin_Saml2_Auth + """ + settings = self.loadSettingsJSON() + message = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64')) + request_data = self.get_request() + request_data['post_data'] = { + 'SAMLResponse': message + } + auth = OneLogin_Saml2_Auth(request_data, old_settings=settings) + self.assertIsNone(auth.get_nameid_nq()) + auth.process_response() + self.assertTrue(auth.is_authenticated()) + self.assertIsNone(auth.get_nameid_nq()) + + def testGetNameIdSPNameQualifier(self): + """ + Tests the get_nameid_spnq method of the OneLogin_Saml2_Auth + """ + settings = self.loadSettingsJSON() + message = self.file_contents(join(self.data_path, 'responses', 'valid_response_with_namequalifier.xml.base64')) + request_data = self.get_request() + request_data['post_data'] = { + 'SAMLResponse': message + } + auth = OneLogin_Saml2_Auth(request_data, old_settings=settings) + self.assertIsNone(auth.get_nameid_spnq()) + auth.process_response() + self.assertTrue(auth.is_authenticated()) + self.assertIsNone(auth.get_nameid_spnq()) + + def testGetNameIdSPNameQualifier2(self): + """ + Tests the get_nameid_spnq method of the OneLogin_Saml2_Auth + """ + settings = self.loadSettingsJSON() + message = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64')) + request_data = self.get_request() + request_data['post_data'] = { + 'SAMLResponse': message + } + auth = OneLogin_Saml2_Auth(request_data, old_settings=settings) + self.assertIsNone(auth.get_nameid_spnq()) + auth.process_response() + self.assertTrue(auth.is_authenticated()) + self.assertEqual("http://stuff.com/endpoints/metadata.php", auth.get_nameid_spnq()) + def testBuildRequestSignature(self): """ Tests the build_request_signature method of the OneLogin_Saml2_Auth diff --git a/tests/src/OneLogin/saml2_tests/logout_request_test.py b/tests/src/OneLogin/saml2_tests/logout_request_test.py index eb50c785..6fd75d3a 100644 --- a/tests/src/OneLogin/saml2_tests/logout_request_test.py +++ b/tests/src/OneLogin/saml2_tests/logout_request_test.py @@ -193,7 +193,6 @@ def testGetNameIdData(self): expected_name_id_data = { 'Format': 'urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress', 'NameQualifier': idp_data['entityId'], - 'SPNameQualifier': sp_data['entityId'], 'Value': 'ONELOGIN_9c86c4542ab9d6fce07f2f7fd335287b9b3cdf69' } diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py index bc172546..4794017b 100644 --- a/tests/src/OneLogin/saml2_tests/response_test.py +++ b/tests/src/OneLogin/saml2_tests/response_test.py @@ -285,6 +285,74 @@ def testReturnNameIdFormat(self): with self.assertRaisesRegexp(Exception, 'An empty NameID value found'): response_17.get_nameid_format() + def testReturnNameIdNameQualifier(self): + """ + Tests the get_nameid_nq method of the OneLogin_Saml2_Response + """ + json_settings = self.loadSettingsJSON() + json_settings['strict'] = False + settings = OneLogin_Saml2_Settings(json_settings) + xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64')) + response = OneLogin_Saml2_Response(settings, xml) + self.assertIsNone(response.get_nameid_nq()) + + xml_2 = self.file_contents(join(self.data_path, 'responses', 'response_encrypted_nameid.xml.base64')) + response_2 = OneLogin_Saml2_Response(settings, xml_2) + self.assertIsNone(response_2.get_nameid_nq()) + + xml_3 = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion.xml.base64')) + response_3 = OneLogin_Saml2_Response(settings, xml_3) + self.assertIsNone(response_3.get_nameid_nq()) + + xml_4 = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64')) + response_4 = OneLogin_Saml2_Response(settings, xml_4) + self.assertIsNone(response_4.get_nameid_nq()) + + xml_5 = self.file_contents(join(self.data_path, 'responses', 'valid_response_with_namequalifier.xml.base64')) + response_5 = OneLogin_Saml2_Response(settings, xml_5) + self.assertEqual('https://test.example.com/saml/metadata', response_5.get_nameid_nq()) + + json_settings['strict'] = True + settings = OneLogin_Saml2_Settings(json_settings) + xml_6 = self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_nameid.xml.base64')) + response_6 = OneLogin_Saml2_Response(settings, xml_6) + with self.assertRaisesRegexp(Exception, 'NameID not found in the assertion of the Response'): + response_6.get_nameid_nq() + + def testReturnNameIdNameSPQualifier(self): + """ + Tests the get_nameid_spnq method of the OneLogin_Saml2_Response + """ + json_settings = self.loadSettingsJSON() + json_settings['strict'] = False + settings = OneLogin_Saml2_Settings(json_settings) + xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64')) + response = OneLogin_Saml2_Response(settings, xml) + self.assertIsNone(response.get_nameid_spnq()) + + xml_2 = self.file_contents(join(self.data_path, 'responses', 'response_encrypted_nameid.xml.base64')) + response_2 = OneLogin_Saml2_Response(settings, xml_2) + self.assertEqual("http://stuff.com/endpoints/metadata.php", response_2.get_nameid_spnq()) + + xml_3 = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion.xml.base64')) + response_3 = OneLogin_Saml2_Response(settings, xml_3) + self.assertEqual("http://stuff.com/endpoints/metadata.php", response_3.get_nameid_spnq()) + + xml_4 = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64')) + response_4 = OneLogin_Saml2_Response(settings, xml_4) + self.assertEqual("http://stuff.com/endpoints/metadata.php", response_4.get_nameid_spnq()) + + xml_5 = self.file_contents(join(self.data_path, 'responses', 'valid_response_with_namequalifier.xml.base64')) + response_5 = OneLogin_Saml2_Response(settings, xml_5) + self.assertIsNone(response_5.get_nameid_spnq()) + + json_settings['strict'] = True + settings = OneLogin_Saml2_Settings(json_settings) + xml_6 = self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_nameid.xml.base64')) + response_6 = OneLogin_Saml2_Response(settings, xml_6) + with self.assertRaisesRegexp(Exception, 'NameID not found in the assertion of the Response'): + response_6.get_nameid_spnq() + def testGetNameIdData(self): """ Tests the get_nameid_data method of the OneLogin_Saml2_Response