From fb1b8e52535cc9bbfe5d009b601b0e9a7b1d0259 Mon Sep 17 00:00:00 2001 From: Maxime Besson Date: Sun, 4 Oct 2020 09:18:18 +0200 Subject: Fixes #733: add setting to sign logout responses --- src/saml2/client.py | 5 ++++- src/saml2/client_base.py | 1 + src/saml2/config.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/saml2/client.py b/src/saml2/client.py index e283420a..599a78b2 100644 --- a/src/saml2/client.py +++ b/src/saml2/client.py @@ -487,7 +487,7 @@ class Saml2Client(Base): else: raise SAMLError("Unsupported binding") - def handle_logout_request(self, request, name_id, binding, sign=False, + def handle_logout_request(self, request, name_id, binding, sign=None, sign_alg=None, relay_state=""): """ Deal with a LogoutRequest @@ -534,6 +534,9 @@ class Saml2Client(Base): response_bindings = self.config.preferred_binding[ "single_logout_service"] + if sign is None: + sign = self.logout_responses_signed + response = self.create_logout_response(_req.message, response_bindings, status, sign, sign_alg=sign_alg) rinfo = self.response_args(_req.message, response_bindings) diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py index 871f3f2c..59a3a995 100644 --- a/src/saml2/client_base.py +++ b/src/saml2/client_base.py @@ -162,6 +162,7 @@ class Base(Entity): attribute_defaults = { "logout_requests_signed": False, + "logout_responses_signed": False, "allow_unsolicited": False, "authn_requests_signed": False, "want_assertions_signed": False, diff --git a/src/saml2/config.py b/src/saml2/config.py index eb00c7cf..2f6e13b0 100644 --- a/src/saml2/config.py +++ b/src/saml2/config.py @@ -95,6 +95,7 @@ SP_ARGS = [ "name_id_policy_format", "name_id_format_allow_create", "logout_requests_signed", + "logout_responses_signed", "requested_attribute_name_format", "hide_assertion_consumer_service", "force_authn", @@ -201,6 +202,7 @@ class Config(object): self.virtual_organization = None self.only_use_keys_in_metadata = True self.logout_requests_signed = None + self.logout_responses_signed = None self.disable_ssl_certificate_validation = None self.context = "" self.attribute_converters = None -- cgit v1.2.1 From 60476ae2516e89aa6d52b6300c535173883edd81 Mon Sep 17 00:00:00 2001 From: Maxime Besson Date: Sun, 4 Oct 2020 09:19:42 +0200 Subject: Document logout_responses_signed --- docs/howto/config.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/howto/config.rst b/docs/howto/config.rst index 060f4f68..e1c2025f 100644 --- a/docs/howto/config.rst +++ b/docs/howto/config.rst @@ -987,6 +987,24 @@ Example:: } } +logout_responses_signed +""""""""""""""""""""""" + +Indicates if this entity will sign the Logout Responses while processing +a Logout Request. + +This can be overridden by application code when calling ``handle_logout_request``. + +Valid values are True or False. Default value is False. + +Example:: + + "service": { + "sp": { + "logout_responses_signed": False, + } + } + subject_data """""""""""" -- cgit v1.2.1 From 50b2963d136d1940f9e26bf5d7b1c76cc9df02a2 Mon Sep 17 00:00:00 2001 From: Maxime Besson Date: Sun, 4 Oct 2020 09:20:24 +0200 Subject: Unit test for logout_responses_signed --- tests/server_conf.py | 2 ++ tests/test_51_client.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/server_conf.py b/tests/server_conf.py index 2b87b942..f1dc33d6 100644 --- a/tests/server_conf.py +++ b/tests/server_conf.py @@ -14,6 +14,8 @@ CONFIG = { "required_attributes": ["surName", "givenName", "mail"], "optional_attributes": ["title"], "idp": ["urn:mace:example.com:saml:roland:idp"], + "logout_responses_signed": True, + "logout_requests_signed": True, "requested_attributes": [ { "name": "urn:oid:1.3.6.1.4.1.5923.1.1.1.2", diff --git a/tests/test_51_client.py b/tests/test_51_client.py index 2b71146c..c1d52531 100644 --- a/tests/test_51_client.py +++ b/tests/test_51_client.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from base64 import encodebytes as b64encode +from base64 import decodebytes as b64decode import uuid import six from six.moves.urllib import parse @@ -51,7 +52,6 @@ AUTHN = { "authn_auth": "http://www.example.com/login" } - def generate_cert(): sn = uuid.uuid4().urn cert_info = { @@ -413,6 +413,36 @@ class TestClient: except Exception: # missing certificate self.client.sec.verify_signature(ar_str, node_name=class_name(ar)) + def test_logout_response(self): + req_id, req = self.server.create_logout_request( + "http://localhost:8088/slo", "urn:mace:example.com:saml:roland:sp", + name_id=nid, reason="Tired", expire=in_a_while(minutes=15), + session_indexes=["_foo"]) + + info = self.client.apply_binding( + BINDING_HTTP_REDIRECT, req, destination="", + relay_state="relay2") + loc = info["headers"][0][1] + qs = parse.parse_qs(loc[1:]) + samlreq = qs['SAMLRequest'][0] + resphttp = self.client.handle_logout_request(samlreq, nid, + BINDING_HTTP_REDIRECT) + _dic = unpack_form(resphttp['data'], "SAMLResponse") + xml = b64decode(_dic['SAMLResponse'].encode('UTF-8')) + + # Signature found + assert xml.decode('UTF-8').find(r"Signature") > 0 + + # Try again with logout_responses_signed=False + self.client.logout_responses_signed = False + resphttp = self.client.handle_logout_request(samlreq, nid, + BINDING_HTTP_REDIRECT) + _dic = unpack_form(resphttp['data'], "SAMLResponse") + xml = b64decode(_dic['SAMLResponse'].encode('UTF-8')) + + # Signature not found + assert xml.decode('UTF-8').find(r"Signature") < 0 + def test_create_logout_request(self): req_id, req = self.client.create_logout_request( "http://localhost:8088/slo", "urn:mace:example.com:saml:roland:idp", -- cgit v1.2.1