Skip to content

Commit 0213e18

Browse files
author
Hans Hörberg
committed
Merge remote-tracking branch 'upstream/master'
2 parents 535acba + 26e25cd commit 0213e18

17 files changed

+70400
-53
lines changed

src/saml2/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,14 @@ def extension_elements_to_elements(extension_elements, schemas):
844844
according to the schemas.
845845
"""
846846
res = []
847+
848+
if isinstance(schemas, list):
849+
pass
850+
elif isinstance(schemas, dict):
851+
schemas = schemas.values()
852+
else:
853+
return res
854+
847855
for extension_element in extension_elements:
848856
for schema in schemas:
849857
inst = extension_element_to_element(extension_element,

src/saml2/client.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ def global_logout(self, name_id, reason="", expire=None, sign=None):
117117
entity_ids = self.users.issuers_of_info(name_id)
118118
return self.do_logout(name_id, entity_ids, reason, expire, sign)
119119

120-
def do_logout(self, name_id, entity_ids, reason, expire, sign=None):
120+
def do_logout(self, name_id, entity_ids, reason, expire, sign=None,
121+
expected_binding=None):
121122
"""
122123
123124
:param name_id: Identifier of the Subject (a NameID instance)
@@ -126,6 +127,8 @@ def do_logout(self, name_id, entity_ids, reason, expire, sign=None):
126127
:param reason: The reason for doing the logout
127128
:param expire: Try to logout before this time.
128129
:param sign: Whether to sign the request or not
130+
:param expected_binding: Specify the expected binding then not try it
131+
all
129132
:return:
130133
"""
131134
# check time
@@ -142,6 +145,8 @@ def do_logout(self, name_id, entity_ids, reason, expire, sign=None):
142145
# for all where I can use the SOAP binding, do those first
143146
for binding in [BINDING_SOAP, BINDING_HTTP_POST,
144147
BINDING_HTTP_REDIRECT]:
148+
if expected_binding and binding != expected_binding:
149+
continue
145150
try:
146151
srvs = self.metadata.single_logout_service(entity_id,
147152
binding,

src/saml2/config.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
__author__ = 'rolandh'
44

5+
import copy
56
import sys
67
import os
78
import re
@@ -48,7 +49,7 @@
4849

4950
COMMON_ARGS = [
5051
"entityid", "xmlsec_binary", "debug", "key_file", "cert_file",
51-
"secret", "accepted_time_diff", "name", "ca_certs",
52+
"encryption_type", "secret", "accepted_time_diff", "name", "ca_certs",
5253
"description", "valid_for", "verify_ssl_cert",
5354
"organization",
5455
"contact_person",
@@ -175,6 +176,7 @@ def __init__(self, homedir="."):
175176
self.debug = False
176177
self.key_file = None
177178
self.cert_file = None
179+
self.encryption_type = 'both'
178180
self.secret = None
179181
self.accepted_time_diff = None
180182
self.name = None
@@ -349,7 +351,7 @@ def load_file(self, config_file, metadata_construction=False):
349351

350352
mod = self._load(config_file)
351353
#return self.load(eval(open(config_file).read()))
352-
return self.load(mod.CONFIG, metadata_construction)
354+
return self.load(copy.deepcopy(mod.CONFIG), metadata_construction)
353355

354356
def load_metadata(self, metadata_conf):
355357
""" Loads metadata into an internal structure """

src/saml2/entity.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ def response_args(self, message, bindings=None, descr_type=""):
291291
def unravel(self, txt, binding, msgtype="response"):
292292
#logger.debug("unravel '%s'" % txt)
293293
if binding not in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST,
294-
BINDING_SOAP, BINDING_URI, None]:
294+
BINDING_SOAP, BINDING_URI, BINDING_HTTP_ARTIFACT,
295+
None]:
295296
raise ValueError("Don't know how to handle '%s'" % binding)
296297
else:
297298
try:
@@ -302,6 +303,8 @@ def unravel(self, txt, binding, msgtype="response"):
302303
elif binding == BINDING_SOAP:
303304
func = getattr(soap, "parse_soap_enveloped_saml_%s" % msgtype)
304305
xmlstr = func(txt)
306+
elif binding == BINDING_HTTP_ARTIFACT:
307+
xmlstr = base64.b64decode(txt)
305308
else:
306309
xmlstr = txt
307310
except Exception:

src/saml2/ident.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ def get_nameid(self, userid, nformat, sp_name_qualifier, name_qualifier):
120120

121121
_id = "%s@%s" % (_id, self.domain)
122122

123+
if nformat == NAMEID_FORMAT_PERSISTENT:
124+
_id = userid
125+
123126
nameid = NameID(format=nformat, sp_name_qualifier=sp_name_qualifier,
124127
name_qualifier=name_qualifier, text=_id)
125128

@@ -281,7 +284,7 @@ def handle_name_id_mapping_request(self, name_id, name_id_policy):
281284
# else create and return a new one
282285
return self.construct_nameid(_id, name_id_policy=name_id_policy)
283286

284-
def handle_manage_name_id_request(self, name_id, new_id="",
287+
def handle_manage_name_id_request(self, name_id, new_id=None,
285288
new_encrypted_id="", terminate=""):
286289
"""
287290
Requests from the SP is about the SPProvidedID attribute.

src/saml2/mdstore.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,13 @@ def repack_cert(cert):
103103

104104

105105
class MetaData(object):
106-
def __init__(self, onts, attrc, metadata=""):
106+
def __init__(self, onts, attrc, metadata="", node_name=None, **kwargs):
107107
self.onts = onts
108108
self.attrc = attrc
109109
self.entity = {}
110110
self.metadata = metadata
111111
self.security = None
112+
self.node_name = node_name
112113

113114
def items(self):
114115
return self.entity.items()
@@ -371,8 +372,8 @@ class MetaDataFile(MetaData):
371372
Handles Metadata file on the same machine. The format of the file is
372373
the SAML Metadata format.
373374
"""
374-
def __init__(self, onts, attrc, filename, cert=None):
375-
MetaData.__init__(self, onts, attrc)
375+
def __init__(self, onts, attrc, filename, cert=None, **kwargs):
376+
MetaData.__init__(self, onts, attrc, **kwargs)
376377
self.filename = filename
377378
self.cert = cert
378379

@@ -382,8 +383,9 @@ def get_metadata_content(self):
382383
def load(self):
383384
_txt = self.get_metadata_content()
384385
if self.cert:
385-
node_name = "%s:%s" % (md.EntitiesDescriptor.c_namespace,
386-
md.EntitiesDescriptor.c_tag)
386+
node_name = self.node_name \
387+
or "%s:%s" % (md.EntitiesDescriptor.c_namespace,
388+
md.EntitiesDescriptor.c_tag)
387389

388390
if self.security.verify_signature(_txt,
389391
node_name=node_name,
@@ -400,8 +402,8 @@ class MetaDataLoader(MetaDataFile):
400402
Handles Metadata file loaded by a passed in function.
401403
The format of the file is the SAML Metadata format.
402404
"""
403-
def __init__(self, onts, attrc, loader_callable, cert=None):
404-
MetaData.__init__(self, onts, attrc)
405+
def __init__(self, onts, attrc, loader_callable, cert=None, **kwargs):
406+
MetaData.__init__(self, onts, attrc, **kwargs)
405407
self.metadata_provider_callable = self.get_metadata_loader(loader_callable)
406408
self.cert = cert
407409

@@ -444,7 +446,7 @@ class MetaDataExtern(MetaData):
444446
Accessible but HTTP GET.
445447
"""
446448

447-
def __init__(self, onts, attrc, url, security, cert, http):
449+
def __init__(self, onts, attrc, url, security, cert, http, **kwargs):
448450
"""
449451
:params onts:
450452
:params attrc:
@@ -453,7 +455,7 @@ def __init__(self, onts, attrc, url, security, cert, http):
453455
:params cert:
454456
:params http:
455457
"""
456-
MetaData.__init__(self, onts, attrc)
458+
MetaData.__init__(self, onts, attrc, **kwargs)
457459
self.url = url
458460
self.security = security
459461
self.cert = cert
@@ -466,8 +468,9 @@ def load(self):
466468
"""
467469
response = self.http.send(self.url)
468470
if response.status_code == 200:
469-
node_name = "%s:%s" % (md.EntitiesDescriptor.c_namespace,
470-
md.EntitiesDescriptor.c_tag)
471+
node_name = self.node_name \
472+
or "%s:%s" % (md.EntitiesDescriptor.c_namespace,
473+
md.EntitiesDescriptor.c_tag)
471474

472475
_txt = response.text.encode("utf-8")
473476
if self.cert:
@@ -480,7 +483,7 @@ def load(self):
480483
self.parse(_txt)
481484
return True
482485
else:
483-
logger.info("Response status: %s" % response.status)
486+
logger.info("Response status: %s" % response.status_code)
484487
return False
485488

486489

@@ -489,8 +492,8 @@ class MetaDataMD(MetaData):
489492
Handles locally stored metadata, the file format is the text representation
490493
of the Python representation of the metadata.
491494
"""
492-
def __init__(self, onts, attrc, filename):
493-
MetaData.__init__(self, onts, attrc)
495+
def __init__(self, onts, attrc, filename, **kwargs):
496+
MetaData.__init__(self, onts, attrc, **kwargs)
494497
self.filename = filename
495498

496499
def load(self):
@@ -523,12 +526,13 @@ def load(self, typ, *args, **kwargs):
523526
elif typ == "inline":
524527
self.ii += 1
525528
key = self.ii
526-
md = MetaData(self.onts, self.attrc, args[0])
529+
md = MetaData(self.onts, self.attrc, args[0], **kwargs)
527530
elif typ == "remote":
528531
key = kwargs["url"]
529532
md = MetaDataExtern(self.onts, self.attrc,
530533
kwargs["url"], self.security,
531-
kwargs["cert"], self.http)
534+
kwargs["cert"], self.http,
535+
node_name=kwargs.get('node_name'))
532536
elif typ == "mdfile":
533537
key = args[0]
534538
md = MetaDataMD(self.onts, self.attrc, args[0])
@@ -804,7 +808,7 @@ def _providers(self, descriptor):
804808
res = []
805809
for md in self.metadata.values():
806810
for ent_id, ent_desc in md.items():
807-
if "spsso_descriptor" in ent_desc:
811+
if descriptor in ent_desc:
808812
res.append(ent_id)
809813
return res
810814

src/saml2/metadata.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def do_key_descriptor(cert, use="both"):
197197
)
198198
]
199199
elif use in ["signing", "encryption"]:
200-
md.KeyDescriptor(
200+
return md.KeyDescriptor(
201201
key_info=ds.KeyInfo(
202202
x509_data=ds.X509Data(
203203
x509_certificate=ds.X509Certificate(text=cert)
@@ -429,7 +429,8 @@ def do_spsso_descriptor(conf, cert=None):
429429
spsso.extensions.add_extension_element(val)
430430

431431
if cert:
432-
spsso.key_descriptor = do_key_descriptor(cert, "both")
432+
encryption_type = conf.encryption_type
433+
spsso.key_descriptor = do_key_descriptor(cert, encryption_type)
433434

434435
for key in ["want_assertions_signed", "authn_requests_signed"]:
435436
try:

src/saml2/mongo_store.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ def store(self, ident, name_id):
153153
self.mdb.store(ident, name_id=to_dict(name_id, ONTS.values(), True))
154154

155155
def find_nameid(self, userid, nformat=None, sp_name_qualifier=None,
156-
name_qualifier=None, sp_provided_id=None):
156+
name_qualifier=None, sp_provided_id=None, **kwargs):
157+
# reset passed for compatibility kwargs for next usage
157158
kwargs = {}
158159
if nformat:
159160
kwargs["name_format"] = nformat

src/saml2/response.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -762,19 +762,28 @@ def _encrypted_assertion(self, xmlstr):
762762
return self._assertion(assertion)
763763

764764
def parse_assertion(self):
765-
try:
766-
assert len(self.response.assertion) == 1 or \
767-
len(self.response.encrypted_assertion) == 1
768-
except AssertionError:
769-
raise Exception("No assertion part")
765+
if self.context == "AuthnQuery":
766+
# can contain one or more assertions
767+
pass
768+
else: # This is a saml2int limitation
769+
try:
770+
assert len(self.response.assertion) == 1 or \
771+
len(self.response.encrypted_assertion) == 1
772+
except AssertionError:
773+
raise Exception("No assertion part")
770774

771775
if self.response.assertion:
772776
logger.debug("***Unencrypted response***")
773-
return self._assertion(self.response.assertion[0])
777+
for assertion in self.response.assertion:
778+
if not self._assertion(assertion):
779+
return False
780+
return True
774781
else:
775782
logger.debug("***Encrypted response***")
776-
return self._encrypted_assertion(
777-
self.response.encrypted_assertion[0])
783+
for assertion in self.response.encrypted_assertion:
784+
if not self._encrypted_assertion(assertion):
785+
return False
786+
return True
778787

779788
def verify(self):
780789
""" Verify that the assertion is syntactically correct and
@@ -883,7 +892,7 @@ def __init__(self, sec_context, attribute_converters, entity_id,
883892
self.entity_id = entity_id
884893
self.attribute_converters = attribute_converters
885894
self.assertion = None
886-
self.context = "AuthnQueryResponse"
895+
self.context = "AuthnQuery"
887896

888897
def condition_ok(self, lax=False): # Should I care about conditions ?
889898
return True

src/saml2/server.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ def init_config(self, stype="idp"):
151151
raise Exception("Couldn't open identity database: %s" %
152152
(dbspec,))
153153

154+
_domain = self.config.getattr("domain", "idp")
155+
if _domain:
156+
self.ident.domain = _domain
157+
154158
self.ident.name_qualifier = self.config.entityid
155159

156160
dbspec = self.config.getattr("edu_person_targeted_id", "idp")
@@ -465,7 +469,14 @@ def create_authn_response(self, identity, in_response_to, destination,
465469
if not snq:
466470
snq = sp_entity_id
467471

468-
_nids = self.ident.find_nameid(userid, sp_name_qualifier=snq)
472+
kwa = {"sp_name_qualifier": snq}
473+
474+
try:
475+
kwa["format"] = name_id_policy.format
476+
except AttributeError:
477+
pass
478+
479+
_nids = self.ident.find_nameid(userid, **kwa)
469480
# either none or one
470481
if _nids:
471482
name_id = _nids[0]

0 commit comments

Comments
 (0)