Skip to content

Commit 9dd3ee9

Browse files
author
Roland Hedberg
committed
Added functionality needed by the saml2test tool.
1 parent 61afe88 commit 9dd3ee9

File tree

14 files changed

+470
-244
lines changed

14 files changed

+470
-244
lines changed

example/sp-wsgi/sp.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import re
1010
import sys
1111
import xml.dom.minidom
12+
from saml2.sigver import SignatureError
1213

1314
import six
1415
from six.moves.http_cookies import SimpleCookie
@@ -48,7 +49,7 @@
4849
logger = logging.getLogger("")
4950
hdlr = logging.FileHandler('spx.log')
5051
base_formatter = logging.Formatter(
51-
"%(asctime)s %(name)s:%(levelname)s %(message)s")
52+
"%(asctime)s %(name)s:%(levelname)s %(message)s")
5253

5354
hdlr.setFormatter(base_formatter)
5455
logger.addHandler(hdlr)
@@ -329,7 +330,8 @@ def __init__(self, name_id, data, saml_response):
329330

330331
@property
331332
def authn_statement(self):
332-
xml_doc = xml.dom.minidom.parseString(str(self.response.assertion.authn_statement[0]))
333+
xml_doc = xml.dom.minidom.parseString(
334+
str(self.response.assertion.authn_statement[0]))
333335
return xml_doc.toprettyxml()
334336

335337

@@ -355,7 +357,8 @@ def do(self, response, binding, relay_state="", mtype="response"):
355357

356358
try:
357359
self.response = self.sp.parse_authn_request_response(
358-
response, binding, self.outstanding_queries, self.cache.outstanding_certs)
360+
response, binding, self.outstanding_queries,
361+
self.cache.outstanding_certs)
359362
except UnknownPrincipal as excp:
360363
logger.error("UnknownPrincipal: %s", excp)
361364
resp = ServiceError("UnknownPrincipal: %s" % (excp,))
@@ -367,6 +370,9 @@ def do(self, response, binding, relay_state="", mtype="response"):
367370
except VerificationError as err:
368371
resp = ServiceError("Verification error: %s" % (err,))
369372
return resp(self.environ, self.start_response)
373+
except SignatureError as err:
374+
resp = ServiceError("Signature error: %s" % (err,))
375+
return resp(self.environ, self.start_response)
370376
except Exception as err:
371377
resp = ServiceError("Other error: %s" % (err,))
372378
return resp(self.environ, self.start_response)
@@ -384,7 +390,7 @@ def do(self, response, binding, relay_state="", mtype="response"):
384390
def verify_attributes(self, ava):
385391
logger.info("SP: %s", self.sp.config.entityid)
386392
rest = POLICY.get_entity_categories(
387-
self.sp.config.entityid, self.sp.metadata)
393+
self.sp.config.entityid, self.sp.metadata)
388394

389395
akeys = [k.lower() for k in ava.keys()]
390396

@@ -469,7 +475,7 @@ def _pick_idp(self, came_from):
469475
_rstate = rndstr()
470476
self.cache.relay_state[_rstate] = geturl(self.environ)
471477
_entityid = _cli.config.ecp_endpoint(
472-
self.environ["REMOTE_ADDR"])
478+
self.environ["REMOTE_ADDR"])
473479

474480
if not _entityid:
475481
return -1, ServiceError("No IdP to talk to")
@@ -521,7 +527,7 @@ def _pick_idp(self, came_from):
521527
elif self.discosrv:
522528
if query:
523529
idp_entity_id = _cli.parse_discovery_service_response(
524-
query=self.environ.get("QUERY_STRING"))
530+
query=self.environ.get("QUERY_STRING"))
525531
if not idp_entity_id:
526532
sid_ = sid()
527533
self.cache.outstanding_queries[sid_] = came_from
@@ -531,7 +537,7 @@ def _pick_idp(self, came_from):
531537
"sp")["discovery_response"][0][0]
532538
ret += "?sid=%s" % sid_
533539
loc = _cli.create_discovery_service_request(
534-
self.discosrv, eid, **{"return": ret})
540+
self.discosrv, eid, **{"return": ret})
535541
return -1, SeeOther(loc)
536542
elif len(idps) == 1:
537543
# idps is a dictionary
@@ -548,8 +554,8 @@ def redirect_to_auth(self, _cli, entity_id, came_from, sigalg=""):
548554
try:
549555
# Picks a binding to use for sending the Request to the IDP
550556
_binding, destination = _cli.pick_binding(
551-
"single_sign_on_service", self.bindings, "idpsso",
552-
entity_id=entity_id)
557+
"single_sign_on_service", self.bindings, "idpsso",
558+
entity_id=entity_id)
553559
logger.debug("binding: %s, destination: %s", _binding,
554560
destination)
555561
# Binding here is the response binding that is which binding the
@@ -568,7 +574,7 @@ def redirect_to_auth(self, _cli, entity_id, came_from, sigalg=""):
568574
"key": req_key_str
569575
}
570576
spcertenc = SPCertEnc(x509_data=ds.X509Data(
571-
x509_certificate=ds.X509Certificate(text=cert_str)))
577+
x509_certificate=ds.X509Certificate(text=cert_str)))
572578
extensions = Extensions(extension_elements=[
573579
element_to_extension_element(spcertenc)])
574580

@@ -589,7 +595,7 @@ def redirect_to_auth(self, _cli, entity_id, came_from, sigalg=""):
589595
except Exception as exc:
590596
logger.exception(exc)
591597
resp = ServiceError(
592-
"Failed to construct the AuthnRequest: %s" % exc)
598+
"Failed to construct the AuthnRequest: %s" % exc)
593599
return resp
594600

595601
# remember the request
@@ -782,7 +788,8 @@ def metadata(environ, start_response):
782788
if path[-1] != "/":
783789
path += "/"
784790
metadata = create_metadata_string(path + "sp_conf.py", None,
785-
_args.valid, _args.cert, _args.keyfile,
791+
_args.valid, _args.cert,
792+
_args.keyfile,
786793
_args.id, _args.name, _args.sign)
787794
start_response('200 OK', [('Content-Type', "text/xml")])
788795
return metadata
@@ -851,10 +858,12 @@ def application(environ, start_response):
851858
_parser.add_argument("config", help="SAML client config")
852859
_parser.add_argument('-p', dest='path', help='Path to configuration file.')
853860
_parser.add_argument('-v', dest='valid', default="4",
854-
help="How long, in days, the metadata is valid from the time of creation")
861+
help="How long, in days, the metadata is valid from "
862+
"the time of creation")
855863
_parser.add_argument('-c', dest='cert', help='certificate')
856864
_parser.add_argument('-i', dest='id',
857-
help="The ID of the entities descriptor in the metadata")
865+
help="The ID of the entities descriptor in the "
866+
"metadata")
858867
_parser.add_argument('-k', dest='keyfile',
859868
help="A file with a key to sign the metadata with")
860869
_parser.add_argument('-n', dest='name')

src/saml2/__init__.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
try:
2727
from xml.etree import cElementTree as ElementTree
28+
2829
if ElementTree.VERSION < '1.3.0':
2930
# cElementTree has no support for register_namespace
3031
# neither _namespace_map, thus we sacrify performance
@@ -40,17 +41,17 @@
4041
root_logger.level = logging.NOTSET
4142

4243
NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:assertion'
43-
#TEMPLATE = '{urn:oasis:names:tc:SAML:2.0:assertion}%s'
44-
#XSI_NAMESPACE = 'http://www.w3.org/2001/XMLSchema-instance'
44+
# TEMPLATE = '{urn:oasis:names:tc:SAML:2.0:assertion}%s'
45+
# XSI_NAMESPACE = 'http://www.w3.org/2001/XMLSchema-instance'
4546

4647
NAMEID_FORMAT_EMAILADDRESS = (
4748
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
4849

4950
# These are defined in saml2.saml
50-
#NAME_FORMAT_UNSPECIFIED = (
51+
# NAME_FORMAT_UNSPECIFIED = (
5152
# "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified")
52-
#NAME_FORMAT_URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
53-
#NAME_FORMAT_BASIC = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
53+
# NAME_FORMAT_URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
54+
# NAME_FORMAT_BASIC = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
5455

5556
DECISION_TYPE_PERMIT = "Permit"
5657
DECISION_TYPE_DENY = "Deny"
@@ -290,7 +291,6 @@ def _extension_element_from_element_tree(element_tree):
290291

291292

292293
class ExtensionContainer(object):
293-
294294
c_tag = ""
295295
c_namespace = ""
296296

@@ -300,6 +300,7 @@ def __init__(self, text=None, extension_elements=None,
300300
self.text = text
301301
self.extension_elements = extension_elements or []
302302
self.extension_attributes = extension_attributes or {}
303+
self.encrypted_assertion = None
303304

304305
# Three methods to create an object from an ElementTree
305306
def harvest_element_tree(self, tree):
@@ -403,7 +404,7 @@ class instance may be a value on a property in a defined class instance.
403404
"""
404405
cinst = None
405406

406-
#print("make_vals(%s, %s)" % (val, klass))
407+
# print("make_vals(%s, %s)" % (val, klass))
407408

408409
if isinstance(val, dict):
409410
cinst = klass().loadd(val, base64encode=base64encode)
@@ -571,8 +572,8 @@ def get_ns_map_attribute(self, attributes, uri_set):
571572

572573
def tag_get_uri(self, elem):
573574
if elem.tag[0] == "{":
574-
uri, tag = elem.tag[1:].split("}")
575-
return uri
575+
uri, tag = elem.tag[1:].split("}")
576+
return uri
576577
return None
577578

578579
def get_ns_map(self, elements, uri_set):
@@ -592,30 +593,41 @@ def get_prefix_map(self, elements):
592593
prefix_map["encas%d" % len(prefix_map)] = uri
593594
return prefix_map
594595

595-
def get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion(self, assertion_tag, advice_tag):
596-
for tmp_encrypted_assertion in self.assertion.advice.encrypted_assertion:
596+
def get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion(
597+
self, assertion_tag, advice_tag):
598+
for tmp_encrypted_assertion in \
599+
self.assertion.advice.encrypted_assertion:
597600
if tmp_encrypted_assertion.encrypted_data is None:
598-
prefix_map = self.get_prefix_map([tmp_encrypted_assertion._to_element_tree().find(assertion_tag)])
601+
prefix_map = self.get_prefix_map([
602+
tmp_encrypted_assertion._to_element_tree().find(
603+
assertion_tag)])
599604
tree = self._to_element_tree()
600-
encs = tree.find(assertion_tag).find(advice_tag).findall(tmp_encrypted_assertion._to_element_tree().tag)
605+
encs = tree.find(assertion_tag).find(advice_tag).findall(
606+
tmp_encrypted_assertion._to_element_tree().tag)
601607
for enc in encs:
602608
assertion = enc.find(assertion_tag)
603609
if assertion is not None:
604610
self.set_prefixes(assertion, prefix_map)
605611

606612
return ElementTree.tostring(tree, encoding="UTF-8").decode('utf-8')
607613

608-
def get_xml_string_with_self_contained_assertion_within_encrypted_assertion(self, assertion_tag):
609-
""" Makes a encrypted assertion only containing self contained namespaces.
614+
def get_xml_string_with_self_contained_assertion_within_encrypted_assertion(
615+
self, assertion_tag):
616+
""" Makes a encrypted assertion only containing self contained
617+
namespaces.
610618
611619
:param assertion_tag: Tag for the assertion to be transformed.
612620
:return: A new samlp.Resonse in string representation.
613621
"""
614-
prefix_map = self.get_prefix_map([self.encrypted_assertion._to_element_tree().find(assertion_tag)])
622+
prefix_map = self.get_prefix_map(
623+
[self.encrypted_assertion._to_element_tree().find(assertion_tag)])
615624

616625
tree = self._to_element_tree()
617626

618-
self.set_prefixes(tree.find(self.encrypted_assertion._to_element_tree().tag).find(assertion_tag), prefix_map)
627+
self.set_prefixes(
628+
tree.find(
629+
self.encrypted_assertion._to_element_tree().tag).find(
630+
assertion_tag), prefix_map)
619631

620632
return ElementTree.tostring(tree, encoding="UTF-8").decode('utf-8')
621633

@@ -648,6 +660,7 @@ def fixup(name):
648660
new_name = uri_map[uri] + ":" + tag
649661
memo[name] = new_name
650662
return new_name
663+
651664
# fix element name
652665
name = fixup(elem.tag)
653666
if name:
@@ -724,7 +737,7 @@ def children_with_values(self):
724737
childs.append(member)
725738
return childs
726739

727-
#noinspection PyUnusedLocal
740+
# noinspection PyUnusedLocal
728741
def set_text(self, val, base64encode=False):
729742
""" Sets the text property of this instance.
730743

src/saml2/assertion.py

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -654,24 +654,64 @@ def authn_statement(authn_class=None, authn_auth=None,
654654
return res
655655

656656

657+
def do_subject_confirmation(policy, sp_entity_id, key_info=None, **treeargs):
658+
"""
659+
660+
:param policy: Policy instance
661+
:param sp_entity_id: The entityid of the SP
662+
:param subject_confirmation_method: How was the subject confirmed
663+
:param address: The network address/location from which an attesting entity
664+
can present the assertion.
665+
:param key_info: Information of the key used to confirm the subject
666+
:param in_response_to: The ID of a SAML protocol message in response to
667+
which an attesting entity can present the assertion.
668+
:param recipient: A URI specifying the entity or location to which an
669+
attesting entity can present the assertion.
670+
:param not_before: A time instant before which the subject cannot be
671+
confirmed. The time value MUST be encoded in UTC.
672+
:return:
673+
"""
674+
675+
_sc = factory(saml.SubjectConfirmation, **treeargs)
676+
677+
_scd = _sc.subject_confirmation_data
678+
_scd.not_on_or_after = policy.not_on_or_after(sp_entity_id)
679+
680+
if _sc.method == saml.SCM_HOLDER_OF_KEY:
681+
_scd.add_extension_element(key_info)
682+
683+
return _sc
684+
685+
686+
def do_subject(policy, sp_entity_id, name_id, **farg):
687+
#
688+
specs = farg['subject_confirmation']
689+
690+
if isinstance(specs, list):
691+
res = [do_subject_confirmation(policy, sp_entity_id, **s) for s in specs]
692+
else:
693+
res = [do_subject_confirmation(policy, sp_entity_id, **specs)]
694+
695+
return factory(saml.Subject, name_id=name_id, subject_confirmation=res)
696+
697+
657698
class Assertion(dict):
658699
""" Handles assertions about subjects """
659700

660701
def __init__(self, dic=None):
661702
dict.__init__(self, dic)
662703
self.acs = []
663704

664-
def construct(self, sp_entity_id, in_response_to, consumer_url,
665-
name_id, attrconvs, policy, issuer, authn_class=None,
666-
authn_auth=None, authn_decl=None, encrypt=None,
667-
sec_context=None, authn_decl_ref=None, authn_instant="",
668-
subject_locality="", authn_statem=None, add_subject=True):
705+
def construct(self, sp_entity_id, attrconvs, policy, issuer, farg,
706+
authn_class=None, authn_auth=None, authn_decl=None,
707+
encrypt=None, sec_context=None, authn_decl_ref=None,
708+
authn_instant="", subject_locality="", authn_statem=None,
709+
name_id=None):
669710
""" Construct the Assertion
670711
671712
:param sp_entity_id: The entityid of the SP
672713
:param in_response_to: An identifier of the message, this message is
673714
a response to
674-
:param consumer_url: The intended consumer of the assertion
675715
:param name_id: An NameID instance
676716
:param attrconvs: AttributeConverters
677717
:param policy: The policy that should be adhered to when replying
@@ -721,29 +761,11 @@ def construct(self, sp_entity_id, in_response_to, consumer_url,
721761
else:
722762
_authn_statement = None
723763

724-
if not add_subject:
725-
_ass = assertion_factory(
726-
issuer=issuer,
727-
conditions=conds,
728-
subject=None
729-
)
730-
else:
731-
_ass = assertion_factory(
732-
issuer=issuer,
733-
conditions=conds,
734-
subject=factory(
735-
saml.Subject,
736-
name_id=name_id,
737-
subject_confirmation=[factory(
738-
saml.SubjectConfirmation,
739-
method=saml.SCM_BEARER,
740-
subject_confirmation_data=factory(
741-
saml.SubjectConfirmationData,
742-
in_response_to=in_response_to,
743-
recipient=consumer_url,
744-
not_on_or_after=policy.not_on_or_after(sp_entity_id)))]
745-
),
746-
)
764+
subject = do_subject(policy, sp_entity_id, name_id,
765+
**farg['subject'])
766+
767+
_ass = assertion_factory(issuer=issuer, conditions=conds,
768+
subject=subject)
747769

748770
if _authn_statement:
749771
_ass.authn_statement = [_authn_statement]

src/saml2/entity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ def _response(self, in_response_to, consumer_url=None, status=None,
604604
605605
:param in_response_to: The session identifier of the request
606606
:param consumer_url: The URL which should receive the response
607-
:param status: The status of the response
607+
:param status: An instance of samlp.Status
608608
:param issuer: The issuer of the response
609609
:param sign: Whether the response should be signed or not
610610
:param to_sign: If there are other parts to sign

0 commit comments

Comments
 (0)