Merge branch 'master' of github.com:rohe/pysaml2

This commit is contained in:
Roland Hedberg
2016-09-22 08:54:29 +03:00
15 changed files with 134 additions and 77 deletions

2
.gitignore vendored
View File

@@ -31,6 +31,8 @@ tmp*
*.tmpl
*.iml
_build/
.cache
*.swp
example/idp3/htdocs/login.mako

View File

@@ -270,6 +270,19 @@ idp/aa
Directives that are specific to an IdP or AA service instance
sign_assertion
""""""""""""""
Specifies if the IdP should sign the assertion in an authentication response
or not. Can be True or False. Default is False.
sign_response
"""""""""""""
Specifies if the IdP should sign the authentication response or not. Can be
True or False. Default is False.
policy
""""""
@@ -419,7 +432,7 @@ Indicates if this SP wants the IdP to send the assertions signed. This
sets the WantAssertionsSigned attribute of the SPSSODescriptor node
of the metadata so the IdP will know this SP preference.
Valid values are True or False. Default value is True.
Valid values are True or False. Default value is False.
Example::

View File

@@ -42,7 +42,7 @@ def get_algorithm_support(xmlsec):
pof.wait()
if not p_err:
p = p_out.split('\n')
p = p_out.splitlines()
algs = [x.strip('"') for x in p[1].split(',')]
digest = []
signing = []

View File

@@ -615,7 +615,7 @@ def _authn_context_decl_ref(decl_ref, authn_auth=None):
def authn_statement(authn_class=None, authn_auth=None,
authn_decl=None, authn_decl_ref=None, authn_instant="",
subject_locality=""):
subject_locality="", session_not_on_or_after=None):
"""
Construct the AuthnStatement
:param authn_class: Authentication Context Class reference
@@ -639,6 +639,7 @@ def authn_statement(authn_class=None, authn_auth=None,
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid(),
session_not_on_or_after=session_not_on_or_after,
authn_context=_authn_context_class_ref(
authn_class, authn_auth))
elif authn_decl:
@@ -646,19 +647,22 @@ def authn_statement(authn_class=None, authn_auth=None,
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid(),
session_not_on_or_after=session_not_on_or_after,
authn_context=_authn_context_decl(authn_decl, authn_auth))
elif authn_decl_ref:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid(),
session_not_on_or_after=session_not_on_or_after,
authn_context=_authn_context_decl_ref(authn_decl_ref,
authn_auth))
else:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid())
session_index=sid(),
session_not_on_or_after=session_not_on_or_after)
if subject_locality:
res.subject_locality = saml.SubjectLocality(text=subject_locality)
@@ -719,7 +723,7 @@ class Assertion(dict):
authn_class=None, authn_auth=None, authn_decl=None,
encrypt=None, sec_context=None, authn_decl_ref=None,
authn_instant="", subject_locality="", authn_statem=None,
name_id=None):
name_id=None, session_not_on_or_after=None):
""" Construct the Assertion
:param sp_entity_id: The entityid of the SP
@@ -770,7 +774,8 @@ class Assertion(dict):
_authn_statement = authn_statement(authn_class, authn_auth,
authn_decl, authn_decl_ref,
authn_instant,
subject_locality)
subject_locality,
session_not_on_or_after=session_not_on_or_after)
else:
_authn_statement = None

View File

@@ -61,7 +61,7 @@ MAP = {
DEF+'eduPersonScopedAffiliation': 'eduPersonScopedAffiliation',
DEF+'eduPersonTargetedID': 'eduPersonTargetedID',
DEF+'eduPersonAssurance': 'eduPersonAssurance',
DEF+'eduPersonUniqueID': 'eduPersonUniqueID',
DEF+'eduPersonUniqueId': 'eduPersonUniqueId',
DEF+'eduPersonOrcid': 'eduPersonOrcid',
DEF+'email': 'email',
DEF+'emailAddress': 'emailAddress',
@@ -228,7 +228,7 @@ MAP = {
'eduPersonScopedAffiliation': DEF+'eduPersonScopedAffiliation',
'eduPersonTargetedID': DEF+'eduPersonTargetedID',
'eduPersonAssurance': DEF+'eduPersonAssurance',
'eduPersonUniqueID': DEF+'eduPersonUniqueID',
'eduPersonUniqueId': DEF+'eduPersonUniqueId',
'eduPersonOrcid': DEF+'eduPersonOrcid',
'email': DEF+'email',
'emailAddress': DEF+'emailAddress',

View File

@@ -19,19 +19,19 @@ MAP = {
EDUCOURSE_OID+'1': 'eduCourseOffering',
EDUCOURSE_OID+'2': 'eduCourseMember',
EDUPERSON_OID+'1': 'eduPersonAffiliation',
EDUPERSON_OID+'2': 'eduPersonEntitlement',
EDUPERSON_OID+'3': 'eduPersonNickname',
EDUPERSON_OID+'4': 'eduPersonOrgDN',
EDUPERSON_OID+'5': 'eduPersonOrgUnitDN',
EDUPERSON_OID+'6': 'eduPersonPrimaryAffiliation',
EDUPERSON_OID+'7': 'eduPersonPrimaryOrgUnitDN',
EDUPERSON_OID+'8': 'eduPersonPrincipalName',
EDUPERSON_OID+'9': 'eduPersonPrincipalName',
EDUPERSON_OID+'10': 'eduPersonScopedAffiliation',
EDUPERSON_OID+'11': 'eduPersonTargetedID',
EDUPERSON_OID+'12': 'eduPersonAssurance',
EDUPERSON_OID+'13': 'eduPersonUniqueID',
EDUPERSON_OID+'14': 'eduPersonOrcid',
EDUPERSON_OID+'2': 'eduPersonNickname',
EDUPERSON_OID+'3': 'eduPersonOrgDN',
EDUPERSON_OID+'4': 'eduPersonOrgUnitDN',
EDUPERSON_OID+'5': 'eduPersonPrimaryAffiliation',
EDUPERSON_OID+'6': 'eduPersonPrincipalName',
EDUPERSON_OID+'7': 'eduPersonEntitlement',
EDUPERSON_OID+'8': 'eduPersonPrimaryOrgUnitDN',
EDUPERSON_OID+'9': 'eduPersonScopedAffiliation',
EDUPERSON_OID+'10': 'eduPersonTargetedID',
EDUPERSON_OID+'11': 'eduPersonAssurance',
EDUPERSON_OID+'12': 'eduPersonPrincipalNamePrior',
EDUPERSON_OID+'13': 'eduPersonUniqueId',
EDUPERSON_OID+'16': 'eduPersonOrcid',
LDAPGVAT_OID+'1': 'PVP-GID',
LDAPGVAT_OID+'149': 'PVP-BPK',
LDAPGVAT_OID+'153': 'PVP-OU-OKZ',
@@ -179,19 +179,19 @@ MAP = {
'eduCourseMember': EDUCOURSE_OID+'2',
'eduCourseOffering': EDUCOURSE_OID+'1',
'eduPersonAffiliation': EDUPERSON_OID+'1',
'eduPersonEntitlement': EDUPERSON_OID+'2',
'eduPersonNickname': EDUPERSON_OID+'3',
'eduPersonOrgDN': EDUPERSON_OID+'4',
'eduPersonOrgUnitDN': EDUPERSON_OID+'5',
'eduPersonPrimaryAffiliation': EDUPERSON_OID+'6',
'eduPersonPrimaryOrgUnitDN': EDUPERSON_OID+'7',
'eduPersonPrincipalName': EDUPERSON_OID+'8',
'eduPersonPrincipalNamePrior': EDUPERSON_OID+'9',
'eduPersonScopedAffiliation': EDUPERSON_OID+'10',
'eduPersonTargetedID': EDUPERSON_OID+'11',
'eduPersonAssurance': EDUPERSON_OID+'12',
'eduPersonUniqueID': EDUPERSON_OID+'13',
'eduPersonOrcid': EDUPERSON_OID+'14',
'eduPersonEntitlement': EDUPERSON_OID+'7',
'eduPersonNickname': EDUPERSON_OID+'2',
'eduPersonOrgDN': EDUPERSON_OID+'3',
'eduPersonOrgUnitDN': EDUPERSON_OID+'4',
'eduPersonPrimaryAffiliation': EDUPERSON_OID+'5',
'eduPersonPrimaryOrgUnitDN': EDUPERSON_OID+'8',
'eduPersonPrincipalName': EDUPERSON_OID+'6',
'eduPersonPrincipalNamePrior': EDUPERSON_OID+'12',
'eduPersonScopedAffiliation': EDUPERSON_OID+'9',
'eduPersonTargetedID': EDUPERSON_OID+'10',
'eduPersonAssurance': EDUPERSON_OID+'11',
'eduPersonUniqueId': EDUPERSON_OID+'13',
'eduPersonOrcid': EDUPERSON_OID+'16',
'email': PKCS_9+'1',
'employeeNumber': NETSCAPE_LDAP+'3',
'employeeType': NETSCAPE_LDAP+'4',

View File

@@ -11,19 +11,19 @@ MAP = {
"identifier": "urn:mace:shibboleth:1.0:attributeNamespace:uri",
'fro': {
EDUPERSON_OID+'1': 'eduPersonAffiliation',
EDUPERSON_OID+'2': 'eduPersonEntitlement',
EDUPERSON_OID+'3': 'eduPersonNickname',
EDUPERSON_OID+'4': 'eduPersonOrgDN',
EDUPERSON_OID+'5': 'eduPersonOrgUnitDN',
EDUPERSON_OID+'6': 'eduPersonPrimaryAffiliation',
EDUPERSON_OID+'7': 'eduPersonPrimaryOrgUnitDN',
EDUPERSON_OID+'8': 'eduPersonPrincipalName',
EDUPERSON_OID+'9': 'eduPersonPrincipalNamePrior',
EDUPERSON_OID+'10': 'eduPersonScopedAffiliation',
EDUPERSON_OID+'11': 'eduPersonTargetedID',
EDUPERSON_OID+'12': 'eduPersonAssurance',
EDUPERSON_OID+'13': 'eduPersonUniqueID',
EDUPERSON_OID+'14': 'eduPersonOrcid',
EDUPERSON_OID+'2': 'eduPersonNickname',
EDUPERSON_OID+'3': 'eduPersonOrgDN',
EDUPERSON_OID+'4': 'eduPersonOrgUnitDN',
EDUPERSON_OID+'5': 'eduPersonPrimaryAffiliation',
EDUPERSON_OID+'6': 'eduPersonPrincipalName',
EDUPERSON_OID+'7': 'eduPersonEntitlement',
EDUPERSON_OID+'8': 'eduPersonPrimaryOrgUnitDN',
EDUPERSON_OID+'9': 'eduPersonScopedAffiliation',
EDUPERSON_OID+'10': 'eduPersonTargetedID',
EDUPERSON_OID+'11': 'eduPersonAssurance',
EDUPERSON_OID+'12': 'eduPersonPrincipalNamePrior',
EDUPERSON_OID+'13': 'eduPersonUniqueId',
EDUPERSON_OID+'16': 'eduPersonOrcid',
NETSCAPE_LDAP+'1': 'carLicense',
NETSCAPE_LDAP+'2': 'departmentNumber',
NETSCAPE_LDAP+'3': 'employeeNumber',
@@ -114,19 +114,19 @@ MAP = {
'dnQualifier': X500ATTR+'46',
'domainComponent': UCL_DIR_PILOT+'25',
'eduPersonAffiliation': EDUPERSON_OID+'1',
'eduPersonEntitlement': EDUPERSON_OID+'2',
'eduPersonNickname': EDUPERSON_OID+'3',
'eduPersonOrgDN': EDUPERSON_OID+'4',
'eduPersonOrgUnitDN': EDUPERSON_OID+'5',
'eduPersonPrimaryAffiliation': EDUPERSON_OID+'6',
'eduPersonPrimaryOrgUnitDN': EDUPERSON_OID+'7',
'eduPersonPrincipalName': EDUPERSON_OID+'8',
'eduPersonPrincipalNamePrior': EDUPERSON_OID+'9',
'eduPersonScopedAffiliation': EDUPERSON_OID+'10',
'eduPersonTargetedID': EDUPERSON_OID+'11',
'eduPersonAssurance': EDUPERSON_OID+'12',
'eduPersonUniqueID': EDUPERSON_OID+'13',
'eduPersonOrcid': EDUPERSON_OID+'14',
'eduPersonEntitlement': EDUPERSON_OID+'7',
'eduPersonNickname': EDUPERSON_OID+'2',
'eduPersonOrgDN': EDUPERSON_OID+'3',
'eduPersonOrgUnitDN': EDUPERSON_OID+'4',
'eduPersonPrimaryAffiliation': EDUPERSON_OID+'5',
'eduPersonPrimaryOrgUnitDN': EDUPERSON_OID+'8',
'eduPersonPrincipalName': EDUPERSON_OID+'6',
'eduPersonPrincipalNamePrior': EDUPERSON_OID+'12',
'eduPersonScopedAffiliation': EDUPERSON_OID+'9',
'eduPersonTargetedID': EDUPERSON_OID+'10',
'eduPersonAssurance': EDUPERSON_OID+'11',
'eduPersonUniqueId': EDUPERSON_OID+'13',
'eduPersonOrcid': EDUPERSON_OID+'16',
'email': PKCS_9+'1',
'emailAddress': PKCS_9+'1',
'employeeNumber': NETSCAPE_LDAP+'3',

View File

@@ -902,6 +902,9 @@ class MetadataStore(MetaData):
elif typ == "loader":
key = args[1]
_md = MetaDataLoader(self.attrc, args[1], **_args)
elif typ == "mdq":
key = args[1]
_md = MetaDataMDX(args[1])
else:
raise SAMLError("Unknown metadata type '%s'" % typ)
_md.load()
@@ -992,10 +995,7 @@ class MetadataStore(MetaData):
try:
srvs = _md[entity_id][typ]
except KeyError:
return None
if not srvs:
return srvs
continue
res = []
for srv in srvs:
@@ -1005,6 +1005,8 @@ class MetadataStore(MetaData):
res.append(elem)
return res
return None
def ext_service(self, entity_id, typ, service, binding=None):
known_entity = False
for key, _md in self.metadata.items():

View File

@@ -326,7 +326,8 @@ class Server(Entity):
def setup_assertion(self, authn, sp_entity_id, in_response_to, consumer_url,
name_id, policy, _issuer, authn_statement, identity,
best_effort, sign_response, farg=None, **kwargs):
best_effort, sign_response, farg=None,
session_not_on_or_after=None, **kwargs):
"""
Construct and return the Assertion
@@ -370,17 +371,20 @@ class Server(Entity):
assertion = ast.construct(
sp_entity_id, self.config.attribute_converters, policy,
issuer=_issuer, farg=farg['assertion'], name_id=name_id,
session_not_on_or_after=session_not_on_or_after,
**authn_args)
elif authn_statement: # Got a complete AuthnStatement
assertion = ast.construct(
sp_entity_id, self.config.attribute_converters, policy,
issuer=_issuer, authn_statem=authn_statement,
farg=farg['assertion'], name_id=name_id, **kwargs)
farg=farg['assertion'], name_id=name_id,
**kwargs)
else:
assertion = ast.construct(
sp_entity_id, self.config.attribute_converters, policy,
issuer=_issuer, farg=farg['assertion'], name_id=name_id,
session_not_on_or_after=session_not_on_or_after,
**kwargs)
return assertion
@@ -394,7 +398,7 @@ class Server(Entity):
encrypt_assertion_self_contained=False,
encrypted_advice_attributes=False,
pefim=False, sign_alg=None, digest_alg=None,
farg=None):
farg=None, session_not_on_or_after=None):
""" Create a response. A layer of indirection.
:param in_response_to: The session identifier of the request
@@ -455,7 +459,7 @@ class Server(Entity):
assertion = self.setup_assertion(
authn, sp_entity_id, in_response_to, consumer_url, name_id,
policy, _issuer, authn_statement, [], True, sign_response,
farg=farg)
farg=farg, session_not_on_or_after=session_not_on_or_after)
assertion.advice = saml.Advice()
# assertion.advice.assertion_id_ref.append(saml.AssertionIDRef())
@@ -465,7 +469,8 @@ class Server(Entity):
assertion = self.setup_assertion(
authn, sp_entity_id, in_response_to, consumer_url, name_id,
policy, _issuer, authn_statement, identity, True,
sign_response, farg=farg)
sign_response, farg=farg,
session_not_on_or_after=session_not_on_or_after)
to_sign = []
if not encrypt_assertion:
@@ -681,6 +686,7 @@ class Server(Entity):
encrypt_assertion_self_contained=True,
encrypted_advice_attributes=False, pefim=False,
sign_alg=None, digest_alg=None,
session_not_on_or_after=None,
**kwargs):
""" Constructs an AuthenticationResponse
@@ -741,11 +747,13 @@ class Server(Entity):
return self._authn_response(
in_response_to, destination, sp_entity_id, identity,
authn=_authn, issuer=issuer, pefim=pefim,
sign_alg=sign_alg, digest_alg=digest_alg, **args)
sign_alg=sign_alg, digest_alg=digest_alg,
session_not_on_or_after=session_not_on_or_after, **args)
return self._authn_response(
in_response_to, destination, sp_entity_id, identity,
authn=_authn, issuer=issuer, pefim=pefim, sign_alg=sign_alg,
digest_alg=digest_alg, **args)
digest_alg=digest_alg,
session_not_on_or_after=session_not_on_or_after, **args)
except MissingValue as exc:
return self.create_error_response(in_response_to, destination,
@@ -756,13 +764,15 @@ class Server(Entity):
name_id_policy=None, userid=None,
name_id=None, authn=None, authn_decl=None,
issuer=None, sign_response=False,
sign_assertion=False, **kwargs):
sign_assertion=False,
session_not_on_or_after=None, **kwargs):
return self.create_authn_response(identity, in_response_to, destination,
sp_entity_id, name_id_policy, userid,
name_id, authn, issuer,
sign_response, sign_assertion,
authn_decl=authn_decl)
authn_decl=authn_decl,
session_not_on_or_after=session_not_on_or_after)
# noinspection PyUnusedLocal
def create_assertion_id_request_response(self, assertion_id, sign=False,

View File

@@ -586,7 +586,7 @@ def parse_xmlsec_output(output):
:param output: The output from Popen
:return: A boolean; True if the command was a success otherwise False
"""
for line in output.split("\n"):
for line in output.splitlines():
if line == "OK":
return True
elif line == "FAIL":

View File

@@ -91,7 +91,7 @@ def validate_on_or_after(not_on_or_after, slack):
nooa = calendar.timegm(time_util.str_to_time(not_on_or_after))
if now > nooa + slack:
raise ResponseLifetimeExceed(
"Can't use it, it's too old %d > %d".format(now - slack, nooa))
"Can't use it, it's too old %d > %d" % (now - slack, nooa))
return nooa
else:
return False

View File

@@ -86,7 +86,7 @@ def test_filter_on_attributes_without_friendly_name():
"eduPersonAffiliation": "test",
"extra": "foo"}
eptid = to_dict(
Attribute(name="urn:oid:1.3.6.1.4.1.5923.1.1.1.11",
Attribute(name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10",
name_format=NAME_FORMAT_URI), ONTS)
ep_affiliation = to_dict(
Attribute(name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1",

View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import datetime
import re
from collections import OrderedDict
from future.backports.urllib.parse import quote_plus
@@ -455,5 +456,16 @@ def test_metadata_extension_algsupport():
mdf = mds.metadata[full_path("uu.xml")]
assert mds
def test_extension():
mds = MetadataStore(ATTRCONV, None)
# use ordered dict to force expected entity to be last
metadata = OrderedDict()
metadata["1"] = {"entity1": {}}
metadata["2"] = {"entity2": {"idpsso_descriptor": [{"extensions": {"extension_elements": [{"__class__": "test"}]}}]}}
mds.metadata = metadata
assert mds.extension("entity2", "idpsso_descriptor", "test")
if __name__ == "__main__":
test_metadata_extension_algsupport()

View File

@@ -540,6 +540,19 @@ def test_sha256_signing():
assert s
def test_xmlsec_output_line_parsing():
output1 = "prefix\nOK\npostfix"
assert sigver.parse_xmlsec_output(output1)
output2 = "prefix\nFAIL\npostfix"
raises(sigver.XmlsecError, sigver.parse_xmlsec_output, output2)
output3 = "prefix\r\nOK\r\npostfix"
assert sigver.parse_xmlsec_output(output3)
output4 = "prefix\r\nFAIL\r\npostfix"
raises(sigver.XmlsecError, sigver.parse_xmlsec_output, output4)
if __name__ == "__main__":
# t = TestSecurity()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python
from saml2.sigver import _get_xmlsec_cryptobackend
from saml2.sigver import SecurityContext
from saml2.httpbase import HTTPBase