Skip to content

Commit a73e11b

Browse files
author
Roland Hedberg
committed
Allow more direct modifications of nested items.
1 parent 9ef92af commit a73e11b

File tree

8 files changed

+197
-95
lines changed

8 files changed

+197
-95
lines changed

src/saml2/argtree.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,31 @@ def add_path(tdict, path):
6868
t[path[-2]] = path[-1]
6969

7070
return tdict
71+
72+
73+
def is_set(tdict, path):
74+
"""
75+
76+
:param tdict: a dictionary representing a argument tree
77+
:param path: a path list
78+
:return: True/False if the value is set
79+
"""
80+
t = tdict
81+
for step in path:
82+
try:
83+
t = t[step]
84+
except KeyError:
85+
return False
86+
87+
if t is not None:
88+
return True
89+
90+
return False
91+
92+
93+
def get_attr(tdict, path):
94+
t = tdict
95+
for step in path:
96+
t = t[step]
97+
98+
return t

src/saml2/client_base.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -558,19 +558,16 @@ def create_name_id_mapping_request(self, name_id_policy,
558558
# ======== response handling ===========
559559

560560
def parse_authn_request_response(self, xmlstr, binding, outstanding=None,
561-
outstanding_certs=None):
561+
outstanding_certs=None, conv_info=None):
562562
""" Deal with an AuthnResponse
563563
564564
:param xmlstr: The reply as a xml string
565565
:param binding: Which binding that was used for the transport
566566
:param outstanding: A dictionary with session IDs as keys and
567567
the original web request from the user before redirection
568568
as values.
569-
:param only_identity_in_encrypted_assertion: Must exist an assertion
570-
that is not encrypted that contains all
571-
other information like
572-
subject and
573-
authentication statement.
569+
:param outstanding_certs:
570+
:param conv_info: Information about the conversation.
574571
:return: An response.AuthnResponse or None
575572
"""
576573

@@ -592,6 +589,7 @@ def parse_authn_request_response(self, xmlstr, binding, outstanding=None,
592589
"attribute_converters": self.config.attribute_converters,
593590
"allow_unknown_attributes":
594591
self.config.allow_unknown_attributes,
592+
'conv_info': conv_info
595593
}
596594
try:
597595
resp = self._parse_response(xmlstr, AuthnResponse,

src/saml2/response.py

Lines changed: 92 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def for_me(conditions, myself):
220220

221221
def authn_response(conf, return_addrs, outstanding_queries=None, timeslack=0,
222222
asynchop=True, allow_unsolicited=False,
223-
want_assertions_signed=False):
223+
want_assertions_signed=False, conv_info=None):
224224
sec = security_context(conf)
225225
if not timeslack:
226226
try:
@@ -231,12 +231,13 @@ def authn_response(conf, return_addrs, outstanding_queries=None, timeslack=0,
231231
return AuthnResponse(sec, conf.attribute_converters, conf.entityid,
232232
return_addrs, outstanding_queries, timeslack,
233233
asynchop=asynchop, allow_unsolicited=allow_unsolicited,
234-
want_assertions_signed=want_assertions_signed)
234+
want_assertions_signed=want_assertions_signed,
235+
conv_info=conv_info)
235236

236237

237238
# comes in over SOAP so synchronous
238239
def attribute_response(conf, return_addrs, timeslack=0, asynchop=False,
239-
test=False):
240+
test=False, conv_info=None):
240241
sec = security_context(conf)
241242
if not timeslack:
242243
try:
@@ -246,14 +247,14 @@ def attribute_response(conf, return_addrs, timeslack=0, asynchop=False,
246247

247248
return AttributeResponse(sec, conf.attribute_converters, conf.entityid,
248249
return_addrs, timeslack, asynchop=asynchop,
249-
test=test)
250+
test=test, conv_info=conv_info)
250251

251252

252253
class StatusResponse(object):
253254
msgtype = "status_response"
254255

255256
def __init__(self, sec_context, return_addrs=None, timeslack=0,
256-
request_id=0, asynchop=True):
257+
request_id=0, asynchop=True, conv_info=None):
257258
self.sec = sec_context
258259
self.return_addrs = return_addrs
259260

@@ -272,6 +273,7 @@ def __init__(self, sec_context, return_addrs=None, timeslack=0,
272273
self.not_signed = False
273274
self.asynchop = asynchop
274275
self.do_not_verify = False
276+
self.conv_info = conv_info or {}
275277

276278
def _clear(self):
277279
self.xmlstr = ""
@@ -429,19 +431,19 @@ class LogoutResponse(StatusResponse):
429431
msgtype = "logout_response"
430432

431433
def __init__(self, sec_context, return_addrs=None, timeslack=0,
432-
asynchop=True):
434+
asynchop=True, conv_info=None):
433435
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
434-
asynchop=asynchop)
436+
asynchop=asynchop, conv_info=conv_info)
435437
self.signature_check = self.sec.correctly_signed_logout_response
436438

437439

438440
class NameIDMappingResponse(StatusResponse):
439441
msgtype = "name_id_mapping_response"
440442

441443
def __init__(self, sec_context, return_addrs=None, timeslack=0,
442-
request_id=0, asynchop=True):
444+
request_id=0, asynchop=True, conv_info=None):
443445
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
444-
request_id, asynchop)
446+
request_id, asynchop, conv_info=conv_info)
445447
self.signature_check = self.sec \
446448
.correctly_signed_name_id_mapping_response
447449

@@ -450,9 +452,9 @@ class ManageNameIDResponse(StatusResponse):
450452
msgtype = "manage_name_id_response"
451453

452454
def __init__(self, sec_context, return_addrs=None, timeslack=0,
453-
request_id=0, asynchop=True):
455+
request_id=0, asynchop=True, conv_info=None):
454456
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
455-
request_id, asynchop)
457+
request_id, asynchop, conv_info=conv_info)
456458
self.signature_check = self.sec.correctly_signed_manage_name_id_response
457459

458460

@@ -469,10 +471,10 @@ def __init__(self, sec_context, attribute_converters, entity_id,
469471
timeslack=0, asynchop=True, allow_unsolicited=False,
470472
test=False, allow_unknown_attributes=False,
471473
want_assertions_signed=False, want_response_signed=False,
472-
**kwargs):
474+
conv_info=None, **kwargs):
473475

474476
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
475-
asynchop=asynchop)
477+
asynchop=asynchop, conv_info=conv_info)
476478
self.entity_id = entity_id
477479
self.attribute_converters = attribute_converters
478480
if outstanding_queries:
@@ -721,6 +723,10 @@ def get_subject(self):
721723
assert self.assertion.subject
722724
subject = self.assertion.subject
723725
subjconf = []
726+
727+
if not self.verify_attesting_entity(subject.subject_confirmation):
728+
raise VerificationError("No valid attesting address")
729+
724730
for subject_confirmation in subject.subject_confirmation:
725731
_data = subject_confirmation.subject_confirmation_data
726732

@@ -736,6 +742,10 @@ def get_subject(self):
736742
raise ValueError("Unknown subject confirmation method: %s" % (
737743
subject_confirmation.method,))
738744

745+
_recip = _data.recipient
746+
if not _recip or not self.verify_recipient(_recip):
747+
raise VerificationError("No valid recipient")
748+
739749
subjconf.append(subject_confirmation)
740750

741751
if not subjconf:
@@ -933,7 +943,7 @@ def parse_assertion(self, keys=None):
933943
decr_text_old = None
934944
while (self.find_encrypt_data(
935945
resp) or self.find_encrypt_data_assertion_list(
936-
_enc_assertions)) and \
946+
_enc_assertions)) and \
937947
decr_text_old != decr_text:
938948
decr_text_old = decr_text
939949
decr_text = self.sec.decrypt_keys(decr_text, keys)
@@ -984,9 +994,11 @@ def parse_assertion(self, keys=None):
984994
return True
985995

986996
def verify(self, keys=None):
987-
""" Verify that the assertion is syntactically correct and
988-
the signature is correct if present.
989-
:param key_file: If not the default key file should be used this is it.
997+
""" Verify that the assertion is syntactically correct and the
998+
signature is correct if present.
999+
1000+
:param keys: If not the default key file should be used then use one
1001+
of these.
9901002
"""
9911003

9921004
try:
@@ -1069,21 +1081,54 @@ def __str__(self):
10691081
return "%s" % self.xmlstr.decode("utf-8")
10701082
return "%s" % self.xmlstr
10711083

1072-
def verify_attesting_entity(self, address):
1084+
def verify_recipient(self, recipient):
1085+
"""
1086+
Verify that I'm the recipient of the assertion
1087+
1088+
:param recipient: A URI specifying the entity or location to which an
1089+
attesting entity can present the assertion.
1090+
:return: True/False
10731091
"""
1074-
Assumes one assertion. At least one address specification has to be
1075-
correct.
1092+
if not self.conv_info:
1093+
return True
1094+
1095+
_info = self.conv_info
1096+
1097+
try:
1098+
if recipient == _info['entity_id']:
1099+
return True
1100+
except KeyError:
1101+
pass
10761102

1077-
:param address: IP address of attesting entity
1103+
try:
1104+
if recipient in self.return_addrs:
1105+
return True
1106+
except KeyError:
1107+
pass
1108+
1109+
return False
1110+
1111+
def verify_attesting_entity(self, subject_confirmation):
1112+
"""
1113+
At least one address specification has to be correct.
1114+
1115+
:param subject_confirmation: A SubbjectConfirmation instance
10781116
:return: True/False
10791117
"""
10801118

1119+
try:
1120+
address = self.conv_info['remote_addr']
1121+
except KeyError:
1122+
address = '0.0.0.0'
1123+
10811124
correct = 0
1082-
for subject_conf in self.assertion.subject.subject_confirmation:
1125+
for subject_conf in subject_confirmation:
10831126
if subject_conf.subject_confirmation_data is None:
10841127
correct += 1 # In reality undefined
10851128
elif subject_conf.subject_confirmation_data.address:
1086-
if subject_conf.subject_confirmation_data.address == address:
1129+
if address == '0.0.0.0': # accept anything
1130+
correct += 1
1131+
elif subject_conf.subject_confirmation_data.address == address:
10871132
correct += 1
10881133
else:
10891134
correct += 1
@@ -1098,10 +1143,12 @@ class AuthnQueryResponse(AuthnResponse):
10981143
msgtype = "authn_query_response"
10991144

11001145
def __init__(self, sec_context, attribute_converters, entity_id,
1101-
return_addrs=None, timeslack=0, asynchop=False, test=False):
1146+
return_addrs=None, timeslack=0, asynchop=False, test=False,
1147+
conv_info=None):
11021148
AuthnResponse.__init__(self, sec_context, attribute_converters,
11031149
entity_id, return_addrs, timeslack=timeslack,
1104-
asynchop=asynchop, test=test)
1150+
asynchop=asynchop, test=test,
1151+
conv_info=conv_info)
11051152
self.entity_id = entity_id
11061153
self.attribute_converters = attribute_converters
11071154
self.assertion = None
@@ -1115,10 +1162,12 @@ class AttributeResponse(AuthnResponse):
11151162
msgtype = "attribute_response"
11161163

11171164
def __init__(self, sec_context, attribute_converters, entity_id,
1118-
return_addrs=None, timeslack=0, asynchop=False, test=False):
1165+
return_addrs=None, timeslack=0, asynchop=False, test=False,
1166+
conv_info=None):
11191167
AuthnResponse.__init__(self, sec_context, attribute_converters,
11201168
entity_id, return_addrs, timeslack=timeslack,
1121-
asynchop=asynchop, test=test)
1169+
asynchop=asynchop, test=test,
1170+
conv_info=conv_info)
11221171
self.entity_id = entity_id
11231172
self.attribute_converters = attribute_converters
11241173
self.assertion = None
@@ -1131,10 +1180,11 @@ class AuthzResponse(AuthnResponse):
11311180
msgtype = "authz_decision_response"
11321181

11331182
def __init__(self, sec_context, attribute_converters, entity_id,
1134-
return_addrs=None, timeslack=0, asynchop=False):
1183+
return_addrs=None, timeslack=0, asynchop=False,
1184+
conv_info=None):
11351185
AuthnResponse.__init__(self, sec_context, attribute_converters,
11361186
entity_id, return_addrs, timeslack=timeslack,
1137-
asynchop=asynchop)
1187+
asynchop=asynchop, conv_info=conv_info)
11381188
self.entity_id = entity_id
11391189
self.attribute_converters = attribute_converters
11401190
self.assertion = None
@@ -1145,10 +1195,12 @@ class ArtifactResponse(AuthnResponse):
11451195
msgtype = "artifact_response"
11461196

11471197
def __init__(self, sec_context, attribute_converters, entity_id,
1148-
return_addrs=None, timeslack=0, asynchop=False, test=False):
1198+
return_addrs=None, timeslack=0, asynchop=False, test=False,
1199+
conv_info=None):
11491200
AuthnResponse.__init__(self, sec_context, attribute_converters,
11501201
entity_id, return_addrs, timeslack=timeslack,
1151-
asynchop=asynchop, test=test)
1202+
asynchop=asynchop, test=test,
1203+
conv_info=conv_info)
11521204
self.entity_id = entity_id
11531205
self.attribute_converters = attribute_converters
11541206
self.assertion = None
@@ -1158,7 +1210,7 @@ def __init__(self, sec_context, attribute_converters, entity_id,
11581210
def response_factory(xmlstr, conf, return_addrs=None, outstanding_queries=None,
11591211
timeslack=0, decode=True, request_id=0, origxml=None,
11601212
asynchop=True, allow_unsolicited=False,
1161-
want_assertions_signed=False):
1213+
want_assertions_signed=False, conv_info=None):
11621214
sec_context = security_context(conf)
11631215
if not timeslack:
11641216
try:
@@ -1171,23 +1223,23 @@ def response_factory(xmlstr, conf, return_addrs=None, outstanding_queries=None,
11711223
extension_schema = conf.extension_schema
11721224

11731225
response = StatusResponse(sec_context, return_addrs, timeslack, request_id,
1174-
asynchop)
1226+
asynchop, conv_info=conv_info)
11751227
try:
11761228
response.loads(xmlstr, decode, origxml)
11771229
if response.response.assertion or response.response.encrypted_assertion:
1178-
authnresp = AuthnResponse(sec_context, attribute_converters,
1179-
entity_id, return_addrs,
1180-
outstanding_queries, timeslack, asynchop,
1181-
allow_unsolicited,
1182-
extension_schema=extension_schema,
1183-
want_assertions_signed=want_assertions_signed)
1230+
authnresp = AuthnResponse(
1231+
sec_context, attribute_converters, entity_id, return_addrs,
1232+
outstanding_queries, timeslack, asynchop, allow_unsolicited,
1233+
extension_schema=extension_schema,
1234+
want_assertions_signed=want_assertions_signed,
1235+
conv_info=conv_info)
11841236
authnresp.update(response)
11851237
return authnresp
11861238
except TypeError:
11871239
response.signature_check = sec_context.correctly_signed_logout_response
11881240
response.loads(xmlstr, decode, origxml)
11891241
logoutresp = LogoutResponse(sec_context, return_addrs, timeslack,
1190-
asynchop=asynchop)
1242+
asynchop=asynchop, conv_info=conv_info)
11911243
logoutresp.update(response)
11921244
return logoutresp
11931245

0 commit comments

Comments
 (0)