Hello community, here is the log from the commit of package python-python3-saml for openSUSE:Factory checked in at 2019-07-22 17:20:06 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-python3-saml (Old) and /work/SRC/openSUSE:Factory/.python-python3-saml.new.4126 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-python3-saml" Mon Jul 22 17:20:06 2019 rev:2 rq:717586 version:1.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-python3-saml/python-python3-saml.changes 2019-05-06 13:21:33.800559810 +0200 +++ /work/SRC/openSUSE:Factory/.python-python3-saml.new.4126/python-python3-saml.changes 2019-07-22 17:20:07.573898804 +0200 @@ -1,0 +2,13 @@ +Mon Jul 22 13:05:59 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 1.7.0: + * Adjusted acs endpoint to extract NameQualifier and SPNameQualifier from + SAMLResponse. + * Adjusted single logout service to provide NameQualifier and SPNameQualifier + to logout method. + * Add getNameIdNameQualifier to Auth and SamlResponse. + * Extend logout method from Auth and LogoutRequest constructor to support. + * Added get_in_response_to method to Response and LogoutResponse classes + * Update defusexml dependency + +------------------------------------------------------------------- Old: ---- python3-saml-1.6.0.tar.gz New: ---- python3-saml-1.7.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-python3-saml.spec ++++++ --- /var/tmp/diff_new_pack.GZjvOr/_old 2019-07-22 17:20:08.893898444 +0200 +++ /var/tmp/diff_new_pack.GZjvOr/_new 2019-07-22 17:20:08.941898430 +0200 @@ -18,13 +18,13 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-python3-saml -Version: 1.6.0 +Version: 1.7.0 Release: 0 Summary: Python SAML support License: MIT Group: Development/Languages/Python URL: https://github.com/onelogin/python3-saml -Source: https://github.com/onelogin/python3-saml/archive/v%{version}.tar.gz#/python3-saml-%{version}.tar.gz +Source: https://github.com/onelogin/python3-saml/archive/v.%{version}.tar.gz#/python3-saml-%{version}.tar.gz Patch0: bug-testDecryptElement.patch BuildRequires: %{python_module defusedxml >= 0.5.0} BuildRequires: %{python_module freezegun >= 0.3.11} @@ -50,7 +50,7 @@ defined by the OASIS Security Services Technical Committee. %prep -%setup -q -n python3-saml-%{version} +%setup -q -n python3-saml-v.%{version} %patch0 -p1 sed -i 's/==/>=/;/dependency_links/d' setup.py ++++++ python3-saml-1.6.0.tar.gz -> python3-saml-1.7.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/README.md new/python3-saml-v.1.7.0/README.md --- old/python3-saml-1.6.0/README.md 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/README.md 2019-07-02 20:09:38.000000000 +0200 @@ -10,7 +10,7 @@ Forget those complicated libraries and use the open source library provided and supported by OneLogin Inc. -This version supports Python3. There is a separate version that only support Python2: [python-saml](https://pypi.python.org/pypi/python-saml) +This version supports Python3. There is a separate version that only support Python2: [python-saml](https://github.com/onelogin/python-saml) #### Warning #### @@ -100,7 +100,7 @@ The toolkit is hosted on GitHub. You can download it from: - * Lastest release: https://github.com/onelogin/python3-saml/releases/latest + * Latest release: https://github.com/onelogin/python3-saml/releases/latest * Master repo: https://github.com/onelogin/python3-saml/tree/master Copy the core of the library ``(src/onelogin/saml2 folder)`` and merge the ``setup.py`` inside the Python application. (Each application has its structure so take your time to locate the Python SAML toolkit in the best place). @@ -794,17 +794,17 @@ 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 no ``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: @@ -830,7 +830,12 @@ 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 @@ -839,6 +844,11 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/changelog.md new/python3-saml-v.1.7.0/changelog.md --- old/python3-saml-1.6.0/changelog.md 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/changelog.md 2019-07-02 20:09:38.000000000 +0200 @@ -1,4 +1,9 @@ # python3-saml changelog +### 1.7.0 (Jul 02, 2019) +* Adjusted acs endpoint to extract NameQualifier and SPNameQualifier from SAMLResponse. Adjusted single logout service to provide NameQualifier and SPNameQualifier to logout method. Add getNameIdNameQualifier to Auth and SamlResponse. Extend logout method from Auth and LogoutRequest constructor to support SPNameQualifier parameter. Align LogoutRequest constructor with SAML specs +* Added get_in_response_to method to Response and LogoutResponse classes +* Update defusexml dependency + ### 1.6.0 (Apr 10, 2019) * Add support for Subjects on AuthNRequests by the new name_id_value_req parameter * [#127](https://github.com/onelogin/python3-saml/pull/127) Fix for SLO when XML specifies encoding diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/demo-django/demo/views.py new/python3-saml-v.1.7.0/demo-django/demo/views.py --- old/python3-saml-1.6.0/demo-django/demo/views.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/demo-django/demo/views.py 2019-07-02 20:09:38.000000000 +0200 @@ -41,26 +41,48 @@ if 'sso' in req['get_data']: return HttpResponseRedirect(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 HttpResponseRedirect(sso_built_url) elif 'sso2' in req['get_data']: 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) + # request.session['LogoutRequestID'] = auth.get_last_request_id() + # return HttpResponseRedirect(slo_built_url) elif 'acs' in req['get_data']: - auth.process_response() + request_id = None + if 'AuthNRequestID' in request.session: + request_id = request.session['AuthNRequestID'] + + auth.process_response(request_id=request_id) errors = auth.get_errors() not_auth_warn = not auth.is_authenticated() if not errors: + if 'AuthNRequestID' in request.session: + 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'])) @@ -68,8 +90,11 @@ if auth.get_settings().is_debug_active(): error_reason = auth.get_last_error_reason() elif 'sls' in req['get_data']: + request_id = None + if 'LogoutRequestID' in request.session: + request_id = request.session['LogoutRequestID'] dscb = lambda: request.session.flush() - 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/demo-flask/index.py new/python3-saml-v.1.7.0/demo-flask/index.py --- old/python3-saml-1.6.0/demo-flask/index.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/demo-flask/index.py 2019-07-02 20:09:38.000000000 +0200 @@ -11,7 +11,7 @@ app = Flask(__name__) app.config['SECRET_KEY'] = 'onelogindemopytoolkit' -app.config['SAML_PATH'] = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'saml') +app.config['SAML_PATH'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'saml') def init_saml_auth(req): @@ -46,32 +46,52 @@ 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'] + 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)) + 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)) 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: - session['samlUserdata'] = auth.get_attributes() + if 'AuthNRequestID' in session: + del session['AuthNRequestID'] 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/setup.py new/python3-saml-v.1.7.0/setup.py --- old/python3-saml-1.6.0/setup.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/setup.py 2019-07-02 20:09:38.000000000 +0200 @@ -9,7 +9,7 @@ setup( name='python3-saml', - version='1.6.0', + version='1.7.0', description='Onelogin Python Toolkit. Add SAML support to your Python software using this library', classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -38,7 +38,7 @@ install_requires=[ 'isodate>=0.5.0', 'xmlsec>=0.6.0', - 'defusedxml==0.5.0' + 'defusedxml>=0.5.0' ], dependency_links=['http://github.com/mehcode/python-xmlsec/tarball/master'], extras_require={ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/src/onelogin/saml2/auth.py new/python3-saml-v.1.7.0/src/onelogin/saml2/auth.py --- old/python3-saml-1.6.0/src/onelogin/saml2/auth.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/src/onelogin/saml2/auth.py 2019-07-02 20:09:38.000000000 +0200 @@ -55,6 +55,8 @@ self.__attributes = dict() 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 @@ -107,6 +109,8 @@ 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 @@ """ 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. @@ -366,7 +388,7 @@ self.add_request_signature(parameters, 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. @@ -385,6 +407,9 @@ :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() @@ -405,7 +430,8 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/src/onelogin/saml2/logout_request.py new/python3-saml-v.1.7.0/src/onelogin/saml2/logout_request.py --- old/python3-saml-1.6.0/src/onelogin/saml2/logout_request.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/src/onelogin/saml2/logout_request.py 2019-07-02 20:09:38.000000000 +0200 @@ -25,7 +25,7 @@ """ - 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. @@ -46,6 +46,9 @@ :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 @@ -75,19 +78,23 @@ 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 - sp_name_qualifier = 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 - sp_name_qualifier = 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, - sp_name_qualifier, + spnq, name_id_format, cert, False, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/src/onelogin/saml2/logout_response.py new/python3-saml-v.1.7.0/src/onelogin/saml2/logout_response.py --- old/python3-saml-1.6.0/src/onelogin/saml2/logout_response.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/src/onelogin/saml2/logout_response.py 2019-07-02 20:09:38.000000000 +0200 @@ -95,7 +95,7 @@ security = self.__settings.get_security_data() # Check if the InResponseTo of the Logout Response matches the ID of the Logout Request (requestId) if provided - in_response_to = self.document.get('InResponseTo', None) + in_response_to = self.get_in_response_to() if request_id is not None and in_response_to and in_response_to != request_id: raise OneLogin_Saml2_ValidationError( 'The InResponseTo of the Logout Response: %s, does not match the ID of the Logout request sent by the SP: %s' % (in_response_to, request_id), @@ -175,6 +175,14 @@ self.__logout_response = logout_response + def get_in_response_to(self): + """ + Gets the ID of the LogoutRequest which this response is in response to + :returns: ID of LogoutRequest this LogoutResponse is in response to or None if it is not present + :rtype: str + """ + return self.document.get('InResponseTo') + def get_response(self, deflate=True): """ Returns a Logout Response object. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/src/onelogin/saml2/response.py new/python3-saml-v.1.7.0/src/onelogin/saml2/response.py --- old/python3-saml-1.6.0/src/onelogin/saml2/response.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/src/onelogin/saml2/response.py 2019-07-02 20:09:38.000000000 +0200 @@ -122,7 +122,7 @@ 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) + in_response_to = self.get_in_response_to() if in_response_to is not None and request_id is not None: if in_response_to != request_id: raise OneLogin_Saml2_ValidationError( @@ -387,6 +387,14 @@ authn_context_nodes = self.__query_assertion('/saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef') return [OneLogin_Saml2_XML.element_text(node) for node in authn_context_nodes] + def get_in_response_to(self): + """ + Gets the ID of the request which this response is in response to + :returns: ID of AuthNRequest this Response is in response to or None if it is not present + :rtype: str + """ + return self.document.get('InResponseTo') + def get_issuers(self): """ Gets the issuers (from message and from assertion) @@ -498,6 +506,32 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/tests/data/metadata/metadata_settings1.xml new/python3-saml-v.1.7.0/tests/data/metadata/metadata_settings1.xml --- old/python3-saml-1.6.0/tests/data/metadata/metadata_settings1.xml 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/tests/data/metadata/metadata_settings1.xml 2019-07-02 20:09:38.000000000 +0200 @@ -1,6 +1,6 @@ <?xml version="1.0"?> <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" - validUntil="2020-03-05T18:19:11Z" + validUntil="2037-03-05T18:19:11Z" cacheDuration="PT1594475551S" entityID="http://stuff.com/endpoints/metadata.php"> <md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/tests/data/responses/valid_response_with_namequalifier.xml.base64 new/python3-saml-v.1.7.0/tests/data/responses/valid_response_with_namequalifier.xml.base64 --- old/python3-saml-1.6.0/tests/data/responses/valid_response_with_namequalifier.xml.base64 1970-01-01 01:00:00.000000000 +0100 +++ new/python3-saml-v.1.7.0/tests/data/responses/valid_response_with_namequalifier.xml.base64 2019-07-02 20:09:38.000000000 +0200 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDhmZWI5YWNkLTFlODYtYWMxMi05MDIzLTEzYjg0NDc5YjI1YiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciPjxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDhmZWI5YWNkLTFlODYtYWMxMi05MDIzLTEzYjg0NDc5YjI1YiI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+NVRWZURYbGQ3YzhURmtybVlDeFpuL2ZHRTRzPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5hZlFaVUE2REpHa0hLNjVMMENBaTJBSDJkOWNwbExuekNPTHBCYm9hUmVmaWdtVC92L0tJZGcyYXpWRzY2Ykk1aFA1NTBNR0c2ZVVzaWJ1N2N3ZytFbG9tejVBalE3dzlGZG8waHdWWWhib3JaSkN2TUxLUzBEWkFzc01XZnZ3RGNUNmhra3UreXFlS2RhZ1BBOTYwQ25YcUMxeHpjMk43WS82dlBCU081bVU9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDQxN2ZiOTc2LTk0NGEtNDNiZi05ZTUyLWZiOWM1OTYxNzYxZiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIj48c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZng0MTdmYjk3Ni05NDRhLTQzYmYtOWU1Mi1mYjljNTk2MTc2MWYiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmxSbTJ3UW13ZGhmZVZuMDFaS1Ewb05CN1JqQT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+aktwQUNmMWkxR0FMSWQ5Y0liQlFsTkJQMVhpZDhhYXFKOUxyTkFIZ1lpR2VIc0NscldVUkZJREprOGI0T3RmdHdXTGZKeXBXbXgwWm15M2hpTTJyVHBIbDBLMGVqSFNsOS9Ed0pabkNEQW1CS1lhZ0ZFR0xxWXYwaXI0Y2lYaForTkdXSDY1czhBRlVibjU2SytaS3lpMFkwMWc4TmVqaS92OTNlZFZ6ZTZnPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDZ1RDQ0Flb0NDUUNiT2xyV0RkWDdGVEFOQmdrcWhraUc5dzBCQVFVRkFEQ0JoREVMTUFrR0ExVUVCaE1DVGs4eEdEQVdCZ05WQkFnVEQwRnVaSEpsWVhNZ1UyOXNZbVZ5WnpFTU1Bb0dBMVVFQnhNRFJtOXZNUkF3RGdZRFZRUUtFd2RWVGtsT1JWUlVNUmd3RmdZRFZRUURFdzltWldsa1pTNWxjbXhoYm1jdWJtOHhJVEFmQmdrcWhraUc5dzBCQ1FFV0VtRnVaSEpsWVhOQWRXNXBibVYwZEM1dWJ6QWVGdzB3TnpBMk1UVXhNakF4TXpWYUZ3MHdOekE0TVRReE1qQXhNelZhTUlHRU1Rc3dDUVlEVlFRR0V3Sk9UekVZTUJZR0ExVUVDQk1QUVc1a2NtVmhjeUJUYjJ4aVpYSm5NUXd3Q2dZRFZRUUhFd05HYjI4eEVEQU9CZ05WQkFvVEIxVk9TVTVGVkZReEdEQVdCZ05WQkFNVEQyWmxhV1JsTG1WeWJHRnVaeTV1YnpFaE1COEdDU3FHU0liM0RRRUpBUllTWVc1a2NtVmhjMEIxYm1sdVpYUjBMbTV2TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FEaXZiaFI3UDUxNngvUzNCcUt4dXBRZTBMT05vbGl1cGlCT2VzQ08zU0hiRHJsMytxOUliZm5mbUUwNHJOdU1jUHNJeEIxNjFUZERwSWVzTENuN2M4YVBISVNLT3RQbEFlVFpTbmI4UUF1N2FSalpxMytQYnJQNXVXM1RjZkNHUHRLVHl0SE9nZS9PbEpibzA3OGRWaFhRMTRkMUVEd1hKVzFyUlh1VXQ0QzhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUNEVmZwODZIT2JxWStlOEJVb1dROStWTVF4MUFTRG9oQmp3T3NnMld5a1VxUlhGK2RMZmNVSDlkV1I2M0N0WklLRkRiU3ROb21QblF6N25iSytvbnlnd0JzcFZFYm5IdVVpaFpxM1pVZG11bVFxQ3c0VXZzLzFVdnEzb3JPby9XSlZoVHl2TGdGVksyUWFyUTQvNjdPWmZIZDdSK1BPQlhob3BoU012MVpPbzwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vdGVzdC5leGFtcGxlLmNvbS9zYW1sL21ldGFkYXRhIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+NDkyODgyNjE1YWNmMzFjODA5NmI2MjcyNDVkNzZhZTUzMDM2YzA5MDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjA1NC0wOC0yM1QwNjo1NzowMVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMi0xOVQwMTozNjozMVoiIE5vdE9uT3JBZnRlcj0iMjA1NC0wOC0yM1QwNjo1NzowMVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wMi0xOVQwMTozNzowMVoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwNTQtMDItMTlUMDk6Mzc6MDFaIiBTZXNzaW9uSW5kZXg9Il82MjczZDc3YjhjZGUwYzMzM2VjNzlkMjJhOWZhMDAwM2I5ZmUyZDc1Y2IiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c21hcnRpbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluQHlhY28uZXM8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iY24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPlNpeHRvMzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJzbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+TWFydGluMjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dXNlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5hZG1pbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/tests/src/OneLogin/saml2_tests/auth_test.py new/python3-saml-v.1.7.0/tests/src/OneLogin/saml2_tests/auth_test.py --- old/python3-saml-1.6.0/tests/src/OneLogin/saml2_tests/auth_test.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/tests/src/OneLogin/saml2_tests/auth_test.py 2019-07-02 20:09:38.000000000 +0200 @@ -227,6 +227,9 @@ 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): """ @@ -993,6 +996,70 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/tests/src/OneLogin/saml2_tests/logout_request_test.py new/python3-saml-v.1.7.0/tests/src/OneLogin/saml2_tests/logout_request_test.py --- old/python3-saml-1.6.0/tests/src/OneLogin/saml2_tests/logout_request_test.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/tests/src/OneLogin/saml2_tests/logout_request_test.py 2019-07-02 20:09:38.000000000 +0200 @@ -215,7 +215,6 @@ expected_name_id_data = { 'Format': 'urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress', 'NameQualifier': idp_data['entityId'], - 'SPNameQualifier': 'http://stuff.com/endpoints/metadata.php', 'Value': 'ONELOGIN_9c86c4542ab9d6fce07f2f7fd335287b9b3cdf69' } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-saml-1.6.0/tests/src/OneLogin/saml2_tests/response_test.py new/python3-saml-v.1.7.0/tests/src/OneLogin/saml2_tests/response_test.py --- old/python3-saml-1.6.0/tests/src/OneLogin/saml2_tests/response_test.py 2019-04-10 00:27:34.000000000 +0200 +++ new/python3-saml-v.1.7.0/tests/src/OneLogin/saml2_tests/response_test.py 2019-07-02 20:09:38.000000000 +0200 @@ -75,8 +75,8 @@ xml = self.file_contents(join(self.data_path, 'responses', 'signed_message_response.xml.base64')) response = OneLogin_Saml2_Response(settings, xml) - prety_xml = self.file_contents(join(self.data_path, 'responses', 'pretty_signed_message_response.xml')) - self.assertEqual(etree.tostring(response.get_xml_document(), encoding='unicode', pretty_print=True), prety_xml) + pretty_xml = self.file_contents(join(self.data_path, 'responses', 'pretty_signed_message_response.xml')) + self.assertEqual(etree.tostring(response.get_xml_document(), encoding='unicode', pretty_print=True), pretty_xml) xml_2 = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion.xml.base64')) response_2 = OneLogin_Saml2_Response(settings, xml_2) @@ -293,6 +293,74 @@ with self.assertRaisesRegex(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.assertRaisesRegex(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.assertRaisesRegex(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 @@ -407,7 +475,7 @@ settings = OneLogin_Saml2_Settings(json_settings) response_13 = OneLogin_Saml2_Response(settings, xml_6) nameid_data_13 = response_13.get_nameid_data() - nameid_data_13 = self.assertEqual(expected_nameid_data_5, nameid_data_13) + self.assertEqual(expected_nameid_data_5, nameid_data_13) json_settings['strict'] = False json_settings['security']['wantNameId'] = False @@ -685,6 +753,22 @@ response_3 = OneLogin_Saml2_Response(settings, xml_3) self.assertEqual(2696012228, response_3.get_session_not_on_or_after()) + def testGetInResponseTo(self): + """ + Tests the retrieval of the InResponseTo attribute + """ + + settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) + + # Response without an InResponseTo element should return None + xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64')) + response = OneLogin_Saml2_Response(settings, xml) + self.assertIsNone(response.get_in_response_to()) + + 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('ONELOGIN_be60b8caf8e9d19b7a3551b244f116c947ff247d', response_3.get_in_response_to()) + def testIsInvalidXML(self): """ Tests the is_valid method of the OneLogin_Saml2_Response