Skip to content

Commit 0c22b0f

Browse files
Merge pull request #734 from Worteks/sign-logout-response
Add logout_responses_signed configuration option to sign logout responses
2 parents 06920df + 50b2963 commit 0c22b0f

File tree

6 files changed

+58
-2
lines changed

6 files changed

+58
-2
lines changed

docs/howto/config.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,24 @@ Example::
987987
}
988988
}
989989

990+
logout_responses_signed
991+
"""""""""""""""""""""""
992+
993+
Indicates if this entity will sign the Logout Responses while processing
994+
a Logout Request.
995+
996+
This can be overridden by application code when calling ``handle_logout_request``.
997+
998+
Valid values are True or False. Default value is False.
999+
1000+
Example::
1001+
1002+
"service": {
1003+
"sp": {
1004+
"logout_responses_signed": False,
1005+
}
1006+
}
1007+
9901008
subject_data
9911009
""""""""""""
9921010

src/saml2/client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ def do_attribute_query(self, entityid, subject_id,
487487
else:
488488
raise SAMLError("Unsupported binding")
489489

490-
def handle_logout_request(self, request, name_id, binding, sign=False,
490+
def handle_logout_request(self, request, name_id, binding, sign=None,
491491
sign_alg=None, relay_state=""):
492492
"""
493493
Deal with a LogoutRequest
@@ -534,6 +534,9 @@ def handle_logout_request(self, request, name_id, binding, sign=False,
534534
response_bindings = self.config.preferred_binding[
535535
"single_logout_service"]
536536

537+
if sign is None:
538+
sign = self.logout_responses_signed
539+
537540
response = self.create_logout_response(_req.message, response_bindings,
538541
status, sign, sign_alg=sign_alg)
539542
rinfo = self.response_args(_req.message, response_bindings)

src/saml2/client_base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ def __init__(self, config=None, identity_cache=None, state_cache=None,
162162

163163
attribute_defaults = {
164164
"logout_requests_signed": False,
165+
"logout_responses_signed": False,
165166
"allow_unsolicited": False,
166167
"authn_requests_signed": False,
167168
"want_assertions_signed": False,

src/saml2/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"name_id_policy_format",
9696
"name_id_format_allow_create",
9797
"logout_requests_signed",
98+
"logout_responses_signed",
9899
"requested_attribute_name_format",
99100
"hide_assertion_consumer_service",
100101
"force_authn",
@@ -201,6 +202,7 @@ def __init__(self, homedir="."):
201202
self.virtual_organization = None
202203
self.only_use_keys_in_metadata = True
203204
self.logout_requests_signed = None
205+
self.logout_responses_signed = None
204206
self.disable_ssl_certificate_validation = None
205207
self.context = ""
206208
self.attribute_converters = None

tests/server_conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"required_attributes": ["surName", "givenName", "mail"],
1515
"optional_attributes": ["title"],
1616
"idp": ["urn:mace:example.com:saml:roland:idp"],
17+
"logout_responses_signed": True,
18+
"logout_requests_signed": True,
1719
"requested_attributes": [
1820
{
1921
"name": "urn:oid:1.3.6.1.4.1.5923.1.1.1.2",

tests/test_51_client.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# -*- coding: utf-8 -*-
33

44
from base64 import encodebytes as b64encode
5+
from base64 import decodebytes as b64decode
56
import uuid
67
import six
78
from six.moves.urllib import parse
@@ -51,7 +52,6 @@
5152
"authn_auth": "http://www.example.com/login"
5253
}
5354

54-
5555
def generate_cert():
5656
sn = uuid.uuid4().urn
5757
cert_info = {
@@ -413,6 +413,36 @@ def test_sign_auth_request_0(self):
413413
except Exception: # missing certificate
414414
self.client.sec.verify_signature(ar_str, node_name=class_name(ar))
415415

416+
def test_logout_response(self):
417+
req_id, req = self.server.create_logout_request(
418+
"http://localhost:8088/slo", "urn:mace:example.com:saml:roland:sp",
419+
name_id=nid, reason="Tired", expire=in_a_while(minutes=15),
420+
session_indexes=["_foo"])
421+
422+
info = self.client.apply_binding(
423+
BINDING_HTTP_REDIRECT, req, destination="",
424+
relay_state="relay2")
425+
loc = info["headers"][0][1]
426+
qs = parse.parse_qs(loc[1:])
427+
samlreq = qs['SAMLRequest'][0]
428+
resphttp = self.client.handle_logout_request(samlreq, nid,
429+
BINDING_HTTP_REDIRECT)
430+
_dic = unpack_form(resphttp['data'], "SAMLResponse")
431+
xml = b64decode(_dic['SAMLResponse'].encode('UTF-8'))
432+
433+
# Signature found
434+
assert xml.decode('UTF-8').find(r"Signature") > 0
435+
436+
# Try again with logout_responses_signed=False
437+
self.client.logout_responses_signed = False
438+
resphttp = self.client.handle_logout_request(samlreq, nid,
439+
BINDING_HTTP_REDIRECT)
440+
_dic = unpack_form(resphttp['data'], "SAMLResponse")
441+
xml = b64decode(_dic['SAMLResponse'].encode('UTF-8'))
442+
443+
# Signature not found
444+
assert xml.decode('UTF-8').find(r"Signature") < 0
445+
416446
def test_create_logout_request(self):
417447
req_id, req = self.client.create_logout_request(
418448
"http://localhost:8088/slo", "urn:mace:example.com:saml:roland:idp",

0 commit comments

Comments
 (0)