Skip to content

Commit 134f5d7

Browse files
Merge pull request #528 from c00kiemon5ter/fix-logout-return-addrs
Retrieve SLO endpoint by the appropriate service type This is triggered when the self.config object has not been created through the SPConfig class, or the IdPConfig class, or the config_factory() function, but directly through the Config class. This results in the config object not having a context as it empty by default. When that happens and endpoint() is called without being passed a context parameter, then getattr() will fail to return the correct attribute. The solution is to pass the context parameter when calling the endpoint() method. This is achieved by passing the entity_type attribute. Moreover, _parse_response() used to call endpoint() only for BINDING_HTTP_REDIRECT and BINDING_HTTP_POST. Thus, return_addrs would be left empty for BINDING_SOAP. The second part of the solution is adding BINDING_SOAP to the bindings that will be checked to fill in return_addrs.
2 parents 58a3bfd + efb2945 commit 134f5d7

File tree

2 files changed

+95
-98
lines changed

2 files changed

+95
-98
lines changed

src/saml2/entity.py

Lines changed: 54 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,8 +1101,6 @@ def _parse_response(self, xmlstr, response_cls, service, binding,
11011101
otherwise the response.
11021102
"""
11031103

1104-
response = None
1105-
11061104
if self.config.accepted_time_diff:
11071105
kwargs["timeslack"] = self.config.accepted_time_diff
11081106

@@ -1112,68 +1110,66 @@ def _parse_response(self, xmlstr, response_cls, service, binding,
11121110
else:
11131111
kwargs["asynchop"] = True
11141112

1115-
if xmlstr:
1116-
if "return_addrs" not in kwargs:
1117-
if binding in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]:
1118-
try:
1119-
# expected return address
1120-
kwargs["return_addrs"] = self.config.endpoint(
1121-
service, binding=binding)
1122-
except Exception:
1123-
logger.info("Not supposed to handle this!")
1124-
return None
1113+
response = None
1114+
if not xmlstr:
1115+
return response
11251116

1126-
try:
1127-
response = response_cls(self.sec, **kwargs)
1128-
except Exception as exc:
1129-
logger.info("%s", exc)
1130-
raise
1117+
if "return_addrs" not in kwargs:
1118+
bindings = {
1119+
BINDING_SOAP,
1120+
BINDING_HTTP_REDIRECT,
1121+
BINDING_HTTP_POST,
1122+
}
1123+
if binding in bindings:
1124+
# expected return address
1125+
kwargs["return_addrs"] = self.config.endpoint(
1126+
service,
1127+
binding=binding,
1128+
context=self.entity_type)
11311129

1132-
xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype)
1133-
origxml = xmlstr
1134-
if not xmlstr: # Not a valid reponse
1135-
return None
1130+
try:
1131+
response = response_cls(self.sec, **kwargs)
1132+
except Exception as exc:
1133+
logger.info("%s", exc)
1134+
raise
1135+
1136+
xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype)
1137+
origxml = xmlstr
1138+
if not xmlstr: # Not a valid reponse
1139+
return None
11361140

1137-
try:
1138-
response = response.loads(xmlstr, False, origxml=origxml)
1139-
except SigverError as err:
1140-
logger.error("Signature Error: %s", err)
1141-
raise
1142-
except UnsolicitedResponse:
1143-
logger.error("Unsolicited response")
1144-
raise
1145-
except Exception as err:
1146-
if "not well-formed" in "%s" % err:
1147-
logger.error("Not well-formed XML")
1148-
raise
1149-
1150-
logger.debug("XMLSTR: %s", xmlstr)
1141+
try:
1142+
response = response.loads(xmlstr, False, origxml=origxml)
1143+
except SigverError as err:
1144+
logger.error("Signature Error: %s", err)
1145+
raise
1146+
except UnsolicitedResponse:
1147+
logger.error("Unsolicited response")
1148+
raise
1149+
except Exception as err:
1150+
if "not well-formed" in "%s" % err:
1151+
logger.error("Not well-formed XML")
1152+
raise
1153+
1154+
logger.debug("XMLSTR: %s", xmlstr)
1155+
1156+
if not response:
1157+
return response
11511158

1152-
if response:
1159+
keys = None
1160+
if outstanding_certs:
1161+
try:
1162+
cert = outstanding_certs[response.in_response_to]
1163+
except KeyError:
11531164
keys = None
1154-
if outstanding_certs:
1155-
try:
1156-
cert = outstanding_certs[response.in_response_to]
1157-
except KeyError:
1158-
keys = None
1159-
else:
1160-
if not isinstance(cert, list):
1161-
cert = [cert]
1162-
keys = []
1163-
for _cert in cert:
1164-
keys.append(_cert["key"])
1165-
only_identity_in_encrypted_assertion = False
1166-
if "only_identity_in_encrypted_assertion" in kwargs:
1167-
only_identity_in_encrypted_assertion = kwargs[
1168-
"only_identity_in_encrypted_assertion"]
1169-
1170-
response = response.verify(keys)
1171-
1172-
if not response:
1173-
return None
1174-
1175-
# logger.debug(response)
1165+
else:
1166+
if not isinstance(cert, list):
1167+
cert = [cert]
1168+
keys = []
1169+
for _cert in cert:
1170+
keys.append(_cert["key"])
11761171

1172+
response = response.verify(keys)
11771173
return response
11781174

11791175
# ------------------------------------------------------------------------

tests/test_51_client.py

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
from saml2.authn_context import INTERNETPROTOCOLPASSWORD
2929
from saml2.client import Saml2Client
30-
from saml2.config import SPConfig
3130
from saml2.pack import parse_soap_enveloped_saml
3231
from saml2.response import LogoutResponse
3332
from saml2.saml import NAMEID_FORMAT_PERSISTENT, EncryptedAssertion, Advice
@@ -122,18 +121,6 @@ def _leq(l1, l2):
122121
return set(l1) == set(l2)
123122

124123

125-
# def test_parse_3():
126-
# xml_response = open(XML_RESPONSE_FILE3).read()
127-
# response = samlp.response_from_string(xml_response)
128-
# client = Saml2Client({})
129-
# (ava, name_id, real_uri) = \
130-
# client.do_response(response, "xenosmilus.umdc.umu.se")
131-
# print(40*"=")
132-
# print(ava)
133-
# print(40*",")
134-
# print(name_id)
135-
# assert False
136-
137124
REQ1 = {"1.2.14": """<?xml version='1.0' encoding='UTF-8'?>
138125
<ns0:AttributeQuery Destination="https://idp.example.com/idp/" ID="id1"
139126
IssueInstant="%s" Version="2.0" xmlns:ns0="urn:oasis:names:tc:SAML:2
@@ -193,7 +180,6 @@ def test_create_attribute_query1(self):
193180

194181
attrq = samlp.attribute_query_from_string(reqstr)
195182

196-
print(attrq.keyswv())
197183
assert _leq(attrq.keyswv(), ['destination', 'subject', 'issue_instant',
198184
'version', 'id', 'issuer'])
199185

@@ -222,7 +208,6 @@ def test_create_attribute_query2(self):
222208
format=saml.NAMEID_FORMAT_PERSISTENT,
223209
message_id="id1")
224210

225-
print(req.to_string())
226211
assert req.destination == "https://idp.example.com/idp/"
227212
assert req.id == "id1"
228213
assert req.version == "2.0"
@@ -272,7 +257,6 @@ def test_create_auth_request_0(self):
272257
"http://www.example.com/sso", message_id="id1")[1]
273258

274259
ar = samlp.authn_request_from_string(ar_str)
275-
print(ar)
276260
assert ar.assertion_consumer_service_url == ("http://lingon.catalogix"
277261
".se:8087/")
278262
assert ar.destination == "http://www.example.com/sso"
@@ -317,7 +301,6 @@ def test_create_auth_request_nameid_policy_allow_create(self):
317301
"http://www.example.com/sso", message_id="id1")[1]
318302

319303
ar = samlp.authn_request_from_string(ar_str)
320-
print(ar)
321304
assert ar.assertion_consumer_service_url == ("http://lingon.catalogix"
322305
".se:8087/")
323306
assert ar.destination == "http://www.example.com/sso"
@@ -340,7 +323,6 @@ def test_create_auth_request_vo(self):
340323
message_id="666")[1]
341324

342325
ar = samlp.authn_request_from_string(ar_str)
343-
print(ar)
344326
assert ar.id == "666"
345327
assert ar.assertion_consumer_service_url == "http://lingon.catalogix" \
346328
".se:8087/"
@@ -355,8 +337,6 @@ def test_create_auth_request_vo(self):
355337
assert nid_policy.sp_name_qualifier == "urn:mace:example.com:it:tek"
356338

357339
def test_sign_auth_request_0(self):
358-
# print(self.client.config)
359-
360340
req_id, areq = self.client.create_authn_request(
361341
"http://www.example.com/sso", sign=True, message_id="id1")
362342

@@ -367,11 +347,9 @@ def test_sign_auth_request_0(self):
367347
assert ar.signature
368348
assert ar.signature.signature_value
369349
signed_info = ar.signature.signed_info
370-
# print(signed_info)
371350
assert len(signed_info.reference) == 1
372351
assert signed_info.reference[0].uri == "#id1"
373352
assert signed_info.reference[0].digest_value
374-
print("------------------------------------------------")
375353
try:
376354
assert self.client.sec.correctly_signed_authn_request(
377355
ar_str, self.client.config.xmlsec_binary,
@@ -424,7 +402,6 @@ def test_response_1(self):
424402
assert authn_response.response.assertion[0].issuer.text == IDP
425403
session_info = authn_response.session_info()
426404

427-
print(session_info)
428405
assert session_info["ava"] == {'mail': ['[email protected]'],
429406
'givenName': ['Derek'],
430407
'sn': ['Jeter'],
@@ -438,7 +415,6 @@ def test_response_1(self):
438415
# One person in the cache
439416
assert len(self.client.users.subjects()) == 1
440417
subject_id = self.client.users.subjects()[0]
441-
print("||||", self.client.users.get_info_from(subject_id, IDP))
442418
# The information I have about the subject comes from one source
443419
assert self.client.users.issuers_of_info(subject_id) == [IDP]
444420

@@ -468,7 +444,6 @@ def test_response_1(self):
468444
issuers = [self.client.users.issuers_of_info(s) for s in
469445
self.client.users.subjects()]
470446
# The information I have about the subjects comes from the same source
471-
print(issuers)
472447
assert issuers == [[IDP], [IDP]]
473448

474449
def test_response_2(self):
@@ -791,14 +766,10 @@ def verify_authn_response(self, idp, authn_response, _client, ava_verify):
791766

792767
def test_init_values(self):
793768
entityid = self.client.config.entityid
794-
print(entityid)
795769
assert entityid == "urn:mace:example.com:saml:roland:sp"
796-
print(self.client.metadata.with_descriptor("idpsso"))
797770
location = self.client._sso_location()
798-
print(location)
799771
assert location == 'http://localhost:8088/sso'
800772
my_name = self.client._my_name()
801-
print(my_name)
802773
assert my_name == "urn:mace:example.com:saml:roland:sp"
803774

804775
def test_sign_then_encrypt_assertion(self):
@@ -865,7 +836,6 @@ def test_sign_then_encrypt_assertion(self):
865836

866837
seresp.assertion = resp_ass
867838
seresp.encrypted_assertion = None
868-
# print(_sresp)
869839

870840
assert seresp.assertion
871841

@@ -1354,7 +1324,6 @@ def test_signed_redirect(self):
13541324

13551325
res = self.server.parse_authn_request(qs["SAMLRequest"][0],
13561326
BINDING_HTTP_REDIRECT)
1357-
print(res)
13581327

13591328
def test_do_logout_signed_redirect(self):
13601329
conf = config.SPConfig()
@@ -1395,7 +1364,6 @@ def test_do_logout_signed_redirect(self):
13951364

13961365
res = self.server.parse_logout_request(qs["SAMLRequest"][0],
13971366
BINDING_HTTP_REDIRECT)
1398-
print(res)
13991367

14001368
def test_do_logout_post(self):
14011369
# information about the user from an IdP
@@ -1466,7 +1434,7 @@ class TestClientWithDummy():
14661434
def setup_class(self):
14671435
self.server = FakeIDP("idp_all_conf")
14681436

1469-
conf = SPConfig()
1437+
conf = config.SPConfig()
14701438
conf.load_file("servera_conf")
14711439
self.client = Saml2Client(conf)
14721440

@@ -1536,12 +1504,13 @@ def test_logout_1(self):
15361504
entity_ids = self.client.users.issuers_of_info(nid)
15371505
assert entity_ids == ["urn:mace:example.com:saml:roland:idp"]
15381506
resp = self.client.global_logout(nid, "Tired", in_a_while(minutes=5))
1539-
print(resp)
15401507
assert resp
15411508
assert len(resp) == 1
15421509
assert list(resp.keys()) == entity_ids
15431510
response = resp[entity_ids[0]]
15441511
assert isinstance(response, LogoutResponse)
1512+
assert response.return_addrs
1513+
assert len(response.return_addrs) == 1
15451514

15461515
def test_post_sso(self):
15471516
binding = BINDING_HTTP_POST
@@ -1566,7 +1535,6 @@ def test_post_sso(self):
15661535
'application/x-www-form-urlencoded')]
15671536

15681537
response = self.client.send(**http_args)
1569-
print(response.text)
15701538
_dic = unpack_form(response.text, "SAMLResponse")
15711539
# Explicitly allow unsigned responses for this test
15721540
self.client.want_response_signed = False
@@ -1603,7 +1571,6 @@ def test_negotiated_post_sso(self):
16031571
'application/x-www-form-urlencoded')]
16041572

16051573
response = self.client.send(**http_args)
1606-
print(response.text)
16071574
_dic = unpack_form(response.text, "SAMLResponse")
16081575
resp = self.client.parse_authn_request_response(_dic["SAMLResponse"],
16091576
BINDING_HTTP_POST,
@@ -1613,6 +1580,44 @@ def test_negotiated_post_sso(self):
16131580
'http://www.example.com/login'
16141581
assert ac.authn_context_class_ref.text == INTERNETPROTOCOLPASSWORD
16151582

1583+
1584+
class TestClientNoConfigContext():
1585+
def setup_class(self):
1586+
self.server = FakeIDP("idp_all_conf")
1587+
1588+
conf = config.Config() # not SPConfig
1589+
conf.load_file("servera_conf")
1590+
self.client = Saml2Client(conf)
1591+
1592+
self.client.send = self.server.receive
1593+
1594+
def test_logout_1(self):
1595+
""" one IdP/AA logout from"""
1596+
1597+
# information about the user from an IdP
1598+
session_info = {
1599+
"name_id": nid,
1600+
"issuer": "urn:mace:example.com:saml:roland:idp",
1601+
"not_on_or_after": in_a_while(minutes=15),
1602+
"ava": {
1603+
"givenName": "Anders",
1604+
"sn": "Andersson",
1605+
1606+
}
1607+
}
1608+
self.client.users.add_information_about_person(session_info)
1609+
entity_ids = self.client.users.issuers_of_info(nid)
1610+
assert entity_ids == ["urn:mace:example.com:saml:roland:idp"]
1611+
resp = self.client.global_logout(nid, "Tired", in_a_while(minutes=5))
1612+
assert resp
1613+
assert len(resp) == 1
1614+
assert list(resp.keys()) == entity_ids
1615+
response = resp[entity_ids[0]]
1616+
assert isinstance(response, LogoutResponse)
1617+
assert response.return_addrs
1618+
assert len(response.return_addrs) == 1
1619+
1620+
16161621
def test_parse_soap_enveloped_saml_xxe():
16171622
xml = """<?xml version="1.0"?>
16181623
<!DOCTYPE lolz [
@@ -1625,10 +1630,6 @@ def test_parse_soap_enveloped_saml_xxe():
16251630
with raises(EntitiesForbidden):
16261631
parse_soap_enveloped_saml(xml, None)
16271632

1628-
# if __name__ == "__main__":
1629-
# tc = TestClient()
1630-
# tc.setup_class()
1631-
# tc.test_response()
16321633

16331634
if __name__ == "__main__":
16341635
tc = TestClient()

0 commit comments

Comments
 (0)