All tests works now

This commit is contained in:
Roland Hedberg
2012-11-14 13:36:08 +01:00
parent cc8e91e84d
commit 74cf8659e1
25 changed files with 838 additions and 837 deletions

View File

@@ -18,7 +18,7 @@ Sure you can send a AuthenticationRequest to an IdentityProvider or a
AttributeQuery to an AttributeAuthority but in order to get what they AttributeQuery to an AttributeAuthority but in order to get what they
return you have to sit behind a Web server. Well that is not really true since return you have to sit behind a Web server. Well that is not really true since
the AttributeQuery would be over SOAP and you would get the result over the the AttributeQuery would be over SOAP and you would get the result over the
conenction you have to the AttributeAuthority. connection you have to the AttributeAuthority.
But anyway, you may get my point. This is middleware stuff ! But anyway, you may get my point. This is middleware stuff !

View File

@@ -79,13 +79,15 @@ def filter_on_attributes(ava, required=None, optional=None):
for attr in required: for attr in required:
if attr.friendly_name in ava: if attr.friendly_name in ava:
values = [av.text for av in attr.attribute_value] values = [av.text for av in attr.attribute_value]
res[attr.friendly_name] = _filter_values(ava[attr.friendly_name], values, True) res[attr.friendly_name] = _filter_values(ava[attr.friendly_name],
values, True)
elif attr.name in ava: elif attr.name in ava:
values = [av.text for av in attr.attribute_value] values = [av.text for av in attr.attribute_value]
res[attr.name] = _filter_values(ava[attr.name], values, True) res[attr.name] = _filter_values(ava[attr.name], values, True)
else: else:
_name = attr.friendly_name or attr.name
print >> sys.stderr, ava.keys() print >> sys.stderr, ava.keys()
raise MissingValue("Required attribute missing: '%s'" % (attr.friendly_name,)) raise MissingValue("Required attribute missing: '%s'" % (_name,))
if optional is None: if optional is None:
optional = [] optional = []
@@ -94,9 +96,11 @@ def filter_on_attributes(ava, required=None, optional=None):
if attr.friendly_name in ava: if attr.friendly_name in ava:
values = [av.text for av in attr.attribute_value] values = [av.text for av in attr.attribute_value]
try: try:
res[attr.friendly_name].extend(_filter_values(ava[attr.friendly_name], values)) res[attr.friendly_name].extend(_filter_values(ava[attr.friendly_name],
values))
except KeyError: except KeyError:
res[attr.friendly_name] = _filter_values(ava[attr.friendly_name], values) res[attr.friendly_name] = _filter_values(ava[attr.friendly_name],
values)
elif attr.name in ava: elif attr.name in ava:
values = [av.text for av in attr.attribute_value] values = [av.text for av in attr.attribute_value]
try: try:
@@ -379,8 +383,7 @@ class Policy(object):
If the requirements can't be met an exception is raised. If the requirements can't be met an exception is raised.
""" """
if metadata: if metadata:
(required, optional) = metadata.attribute_consumer(sp_entity_id) (required, optional) = metadata.attribute_requirement(sp_entity_id)
#(required, optional) = metadata.wants(sp_entity_id)
else: else:
required = optional = None required = optional = None

View File

@@ -20,7 +20,7 @@ to conclude its tasks.
""" """
import saml2 import saml2
from saml2.saml import AssertionIDRef from saml2.saml import AssertionIDRef, NAMEID_FORMAT_PERSISTENT
try: try:
from urlparse import parse_qs from urlparse import parse_qs
@@ -51,6 +51,7 @@ class Saml2Client(Base):
def do_authenticate(self, entityid=None, relay_state="", def do_authenticate(self, entityid=None, relay_state="",
binding=saml2.BINDING_HTTP_REDIRECT, vorg="", binding=saml2.BINDING_HTTP_REDIRECT, vorg="",
nameid_format=NAMEID_FORMAT_PERSISTENT,
scoping=None, consent=None, extensions=None, sign=None): scoping=None, consent=None, extensions=None, sign=None):
""" Makes an authentication request. """ Makes an authentication request.
@@ -68,11 +69,10 @@ class Saml2Client(Base):
location = self._sso_location(entityid, binding) location = self._sso_location(entityid, binding)
session_id, _req_str = "%s" % self.create_authn_request(location, vorg, req = self.create_authn_request(location, vorg, scoping, binding,
scoping, nameid_format, consent, extensions,
consent,
extensions,
sign) sign)
_req_str = "%s" % req
logger.info("AuthNReq: %s" % _req_str) logger.info("AuthNReq: %s" % _req_str)
@@ -90,7 +90,7 @@ class Saml2Client(Base):
else: else:
raise Exception("Unknown binding type: %s" % binding) raise Exception("Unknown binding type: %s" % binding)
return session_id, response return response
def global_logout(self, subject_id, reason="", expire=None, sign=None, def global_logout(self, subject_id, reason="", expire=None, sign=None,
return_to="/"): return_to="/"):
@@ -146,8 +146,9 @@ class Saml2Client(Base):
destination = destinations[0] destination = destinations[0]
logger.info("destination to provider: %s" % destination) logger.info("destination to provider: %s" % destination)
request = self.create_logout_request(subject_id, destination, request = self.create_logout_request(subject_id,
entity_id, reason, expire) destination, entity_id,
reason, expire)
to_sign = [] to_sign = []
#if sign and binding != BINDING_HTTP_REDIRECT: #if sign and binding != BINDING_HTTP_REDIRECT:
@@ -311,8 +312,13 @@ class Saml2Client(Base):
def _soap_query_response(self, destination, query_type, **kwargs): def _soap_query_response(self, destination, query_type, **kwargs):
_create_func = getattr(self, "create_%s" % query_type) _create_func = getattr(self, "create_%s" % query_type)
_response_func = getattr(self, "%s_response" % query_type) _response_func = getattr(self, "%s_response" % query_type)
try:
response_args = kwargs["response_args"]
del kwargs["response_args"]
except KeyError:
response_args = None
id, query = _create_func(destination, **kwargs) query = _create_func(destination, **kwargs)
response = send_using_soap(query, destination, response = send_using_soap(query, destination,
self.config.key_file, self.config.key_file,
@@ -321,8 +327,8 @@ class Saml2Client(Base):
if response: if response:
logger.info("Verifying response") logger.info("Verifying response")
if "response_args" in kwargs: if response_args:
response = _response_func(response, **kwargs["response_args"]) response = _response_func(response, **response_args)
else: else:
response = _response_func(response) response = _response_func(response)
@@ -387,7 +393,7 @@ class Saml2Client(Base):
consent=consent, extensions=extensions, consent=consent, extensions=extensions,
sign=sign) sign=sign)
def do_attribute_query(self, subject_id, entityid, def do_attribute_query(self, entityid, subject_id,
attribute=None, sp_name_qualifier=None, attribute=None, sp_name_qualifier=None,
name_qualifier=None, nameid_format=None, name_qualifier=None, nameid_format=None,
real_id=None, consent=None, extensions=None, real_id=None, consent=None, extensions=None,
@@ -396,8 +402,8 @@ class Saml2Client(Base):
by default done over SOAP. Other bindings could be used but not by default done over SOAP. Other bindings could be used but not
supported right now. supported right now.
:param subject_id: The identifier of the subject
:param entityid: To whom the query should be sent :param entityid: To whom the query should be sent
:param subject_id: The identifier of the subject
:param attribute: A dictionary of attributes and values that is asked for :param attribute: A dictionary of attributes and values that is asked for
:param sp_name_qualifier: The unique identifier of the :param sp_name_qualifier: The unique identifier of the
service provider or affiliation of providers for whom the service provider or affiliation of providers for whom the

View File

@@ -18,8 +18,7 @@
"""Contains classes and functions that a SAML2.0 Service Provider (SP) may use """Contains classes and functions that a SAML2.0 Service Provider (SP) may use
to conclude its tasks. to conclude its tasks.
""" """
from random import random from saml2.saml import AssertionIDRef, NAMEID_FORMAT_TRANSIENT
from saml2.saml import AssertionIDRef
from saml2.samlp import AuthnQuery, LogoutRequest, AssertionIDRequest from saml2.samlp import AuthnQuery, LogoutRequest, AssertionIDRequest
from saml2.samlp import AttributeQuery from saml2.samlp import AttributeQuery
from saml2.samlp import AuthzDecisionQuery from saml2.samlp import AuthzDecisionQuery
@@ -35,7 +34,7 @@ except ImportError:
from cgi import parse_qs from cgi import parse_qs
from saml2.time_util import instant from saml2.time_util import instant
from saml2.s_utils import signature from saml2.s_utils import signature, rndstr
from saml2.s_utils import sid from saml2.s_utils import sid
from saml2.s_utils import do_attributes from saml2.s_utils import do_attributes
from saml2.s_utils import decode_base64_and_inflate from saml2.s_utils import decode_base64_and_inflate
@@ -123,23 +122,17 @@ class Base(object):
else: else:
self.vorg = None self.vorg = None
if "allow_unsolicited" in self.config: for foo in ["allow_unsolicited", "authn_requests_signed",
self.allow_unsolicited = self.config.allow_unsolicited "logout_requests_signed"]:
if self.config.getattr("sp", foo) == 'true':
setattr(self, foo, True)
else: else:
self.allow_unsolicited = False setattr(self, foo, False)
if getattr(self.config, 'authn_requests_signed', 'false') == 'true':
self.authn_requests_signed_default = True
else:
self.authn_requests_signed_default = False
if getattr(self.config, 'logout_requests_signed', 'false') == 'true':
self.logout_requests_signed_default = True
else:
self.logout_requests_signed_default = False
# extra randomness # extra randomness
self.seed = random() self.seed = rndstr(32)
self.logout_requests_signed_default = True
self.allow_unsolicited = self.config.getattr("allow_unsolicited", "sp")
# #
# Private methods # Private methods
@@ -223,7 +216,7 @@ class Base(object):
return True return True
def service_url(self, binding=BINDING_HTTP_POST): def service_url(self, binding=BINDING_HTTP_POST):
_res = self.config.endpoint("assertion_consumer_service", binding) _res = self.config.endpoint("assertion_consumer_service", binding, "sp")
if _res: if _res:
return _res[0] return _res[0]
else: else:
@@ -266,23 +259,23 @@ class Base(object):
logger.info("REQUEST: %s" % req) logger.info("REQUEST: %s" % req)
return id, signed_instance_factory(req, self.sec, to_sign) return signed_instance_factory(req, self.sec, to_sign)
def create_authn_request(self, destination, id=0, vorg="", scoping=None, def create_authn_request(self, destination, vorg="", scoping=None,
binding=saml2.BINDING_HTTP_POST, binding=saml2.BINDING_HTTP_POST,
nameid_format=saml.NAMEID_FORMAT_TRANSIENT, nameid_format=NAMEID_FORMAT_TRANSIENT,
service_url_binding=None, service_url_binding=None,
consent=None, extensions=None, sign=None): id=0, consent=None, extensions=None, sign=None):
""" Creates an authentication request. """ Creates an authentication request.
:param destination: Where the request should be sent. :param destination: Where the request should be sent.
:param id: The identifier for this request
:param vorg: The virtual organization the service belongs to. :param vorg: The virtual organization the service belongs to.
:param scoping: The scope of the request :param scoping: The scope of the request
:param binding: The protocol to use for the Response !! :param binding: The protocol to use for the Response !!
:param nameid_format: Format of the NameID :param nameid_format: Format of the NameID
:param service_url_binding: Where the reply should be sent dependent :param service_url_binding: Where the reply should be sent dependent
on reply binding. on reply binding.
:param id: The identifier for this request
:param consent: Whether the principal have given her consent :param consent: Whether the principal have given her consent
:param extensions: Possible extensions :param extensions: Possible extensions
:param sign: Whether the request should be signed or not. :param sign: Whether the request should be signed or not.
@@ -301,11 +294,12 @@ class Base(object):
my_name = self._my_name() my_name = self._my_name()
# Profile stuff, should be configurable # Profile stuff, should be configurable
if nameid_format == saml.NAMEID_FORMAT_TRANSIENT: if nameid_format is None or nameid_format == NAMEID_FORMAT_TRANSIENT:
name_id_policy = samlp.NameIDPolicy(allow_create="true", name_id_policy = samlp.NameIDPolicy(allow_create="true",
format=nameid_format) format=NAMEID_FORMAT_TRANSIENT)
else: else:
name_id_policy = samlp.NameIDPolicy(format=nameid_format) name_id_policy = samlp.NameIDPolicy(allow_create="false",
format=nameid_format)
if vorg: if vorg:
try: try:
@@ -317,20 +311,20 @@ class Base(object):
return self._message(AuthnRequest, destination, id, consent, return self._message(AuthnRequest, destination, id, consent,
extensions, sign, extensions, sign,
assertion_consumer_service_url=service_url, assertion_consumer_service_url=service_url,
protocol_binding= binding, protocol_binding=binding,
name_id_policy = name_id_policy, name_id_policy=name_id_policy,
provider_name = my_name, provider_name=my_name,
scoping = scoping) scoping=scoping)
def create_attribute_query(self, destination, id, subject_id, def create_attribute_query(self, destination, subject_id,
attribute=None, sp_name_qualifier=None, attribute=None, sp_name_qualifier=None,
name_qualifier=None, nameid_format=None, name_qualifier=None, nameid_format=None,
consent=None, extensions=None, sign=False): id=0, consent=None, extensions=None, sign=False,
**kwargs):
""" Constructs an AttributeQuery """ Constructs an AttributeQuery
:param destination: To whom the query should be sent :param destination: To whom the query should be sent
:param id: The identifier of the session
:param subject_id: The identifier of the subject :param subject_id: The identifier of the subject
:param attribute: A dictionary of attributes and values that is :param attribute: A dictionary of attributes and values that is
asked for. The key are one of 4 variants: asked for. The key are one of 4 variants:
@@ -344,6 +338,7 @@ class Base(object):
:param name_qualifier: The unique identifier of the identity :param name_qualifier: The unique identifier of the identity
provider that generated the identifier. provider that generated the identifier.
:param nameid_format: The format of the name ID :param nameid_format: The format of the name ID
:param id: The identifier of the session
:param consent: Whether the principal have given her consent :param consent: Whether the principal have given her consent
:param extensions: Possible extensions :param extensions: Possible extensions
:param sign: Whether the query should be signed or not. :param sign: Whether the query should be signed or not.
@@ -365,13 +360,12 @@ class Base(object):
attribute=attribute) attribute=attribute)
def create_logout_request(self, destination, id, subject_id, def create_logout_request(self, destination, subject_id, issuer_entity_id,
issuer_entity_id, reason=None, expire=None, reason=None, expire=None,
consent=None, extensions=None, sign=False): id=0, consent=None, extensions=None, sign=False):
""" Constructs a LogoutRequest """ Constructs a LogoutRequest
:param destination: Destination of the request :param destination: Destination of the request
:param id: Request identifier
:param subject_id: The identifier of the subject :param subject_id: The identifier of the subject
:param issuer_entity_id: The entity ID of the IdP the request is :param issuer_entity_id: The entity ID of the IdP the request is
target at. target at.
@@ -379,6 +373,7 @@ class Base(object):
form of a URI reference. form of a URI reference.
:param expire: The time at which the request expires, :param expire: The time at which the request expires,
after which the recipient may discard the message. after which the recipient may discard the message.
:param id: Request identifier
:param consent: Whether the principal have given her consent :param consent: Whether the principal have given her consent
:param extensions: Possible extensions :param extensions: Possible extensions
:param sign: Whether the query should be signed or not. :param sign: Whether the query should be signed or not.
@@ -419,21 +414,21 @@ class Base(object):
# AssertionIDRequest, SubjectQuery, # AssertionIDRequest, SubjectQuery,
# AuthnQuery, AttributeQuery, or AuthzDecisionQuery # AuthnQuery, AttributeQuery, or AuthzDecisionQuery
def create_authz_decision_query(self, destination, action, id=0, def create_authz_decision_query(self, destination, action,
evidence=None, resource=None, subject=None, evidence=None, resource=None, subject=None,
sign=None, consent=None, id=0, consent=None, extensions=None,
extensions=None): sign=None):
""" Creates an authz decision query. """ Creates an authz decision query.
:param destination: The IdP endpoint :param destination: The IdP endpoint
:param action: The action you want to perform (has to be at least one) :param action: The action you want to perform (has to be at least one)
:param id: Message identifier
:param evidence: Why you should be able to perform the action :param evidence: Why you should be able to perform the action
:param resource: The resource you want to perform the action on :param resource: The resource you want to perform the action on
:param subject: Who wants to do the thing :param subject: Who wants to do the thing
:param sign: Whether the request should be signed or not. :param id: Message identifier
:param consent: If the principal gave her consent to this request :param consent: If the principal gave her consent to this request
:param extensions: Possible request extensions :param extensions: Possible request extensions
:param sign: Whether the request should be signed or not.
:return: AuthzDecisionQuery instance :return: AuthzDecisionQuery instance
""" """
@@ -443,17 +438,20 @@ class Base(object):
def create_authz_decision_query_using_assertion(self, destination, assertion, def create_authz_decision_query_using_assertion(self, destination, assertion,
action=None, resource=None, action=None, resource=None,
subject=None, subject=None, id=0,
binding=BINDING_HTTP_REDIRECT, consent=None,
extensions=None,
sign=False): sign=False):
""" Makes an authz decision query. """ Makes an authz decision query.
:param destination: The IdP endpoint to send the request to :param destination: The IdP endpoint to send the request to
:param assertion: :param assertion: An Assertion instance
:param action: :param action: The action you want to perform (has to be at least one)
:param resource: :param resource: The resource you want to perform the action on
:param subject: :param subject: Who wants to do the thing
:param binding: Which binding to use for sending the request :param id: Message identifier
:param consent: If the principal gave her consent to this request
:param extensions: Possible request extensions
:param sign: Whether the request should be signed or not. :param sign: Whether the request should be signed or not.
:return: AuthzDecisionQuery instance :return: AuthzDecisionQuery instance
""" """
@@ -469,24 +467,46 @@ class Base(object):
return self.create_authz_decision_query(destination, return self.create_authz_decision_query(destination,
_action, _action,
saml.Evidence(assertion=assertion), saml.Evidence(assertion=assertion),
resource, subject, binding, resource, subject,
sign) id=id,
consent=consent,
extensions=extensions,
sign=sign)
def create_assertion_id_request(self, assertion_id_refs, destination=None, def create_assertion_id_request(self, assertion_id_refs, destination=None,
id=0, consent=None, extensions=None, id=0, consent=None, extensions=None,
sign=False): sign=False):
"""
:param assertion_id_refs:
:param destination: The IdP endpoint to send the request to
:param id: Message identifier
:param consent: If the principal gave her consent to this request
:param extensions: Possible request extensions
:param sign: Whether the request should be signed or not.
:return: AssertionIDRequest instance
"""
id_refs = [AssertionIDRef(text=s) for s in assertion_id_refs] id_refs = [AssertionIDRef(text=s) for s in assertion_id_refs]
return self._message(AssertionIDRequest, destination, id, consent, return self._message(AssertionIDRequest, destination, id, consent,
extensions, sign, assertion_id_refs=id_refs ) extensions, sign, assertion_id_refs=id_refs )
def create_authn_query(self, subject, destination=None, id=0, def create_authn_query(self, subject, destination=None,
authn_context=None, session_index="", authn_context=None, session_index="",
consent=None, sign=False, id=0, consent=None, extensions=None, sign=False):
extensions=None): """
:param subject:
:param destination: The IdP endpoint to send the request to
:param authn_context:
:param session_index:
:param id: Message identifier
:param consent: If the principal gave her consent to this request
:param extensions: Possible request extensions
:param sign: Whether the request should be signed or not.
:return:
"""
return self._message(AuthnQuery, destination, id, consent, extensions, return self._message(AuthnQuery, destination, id, consent, extensions,
sign, subject=subject, session_index=session_index, sign, subject=subject, session_index=session_index,
requested_auth_context=authn_context) requested_auth_context=authn_context)

View File

@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
COMMON_ARGS = ["entityid", "xmlsec_binary", "debug", "key_file", "cert_file", COMMON_ARGS = ["entityid", "xmlsec_binary", "debug", "key_file", "cert_file",
"secret", "accepted_time_diff", "name", "ca_certs", "secret", "accepted_time_diff", "name", "ca_certs",
"description", "description", "valid_for",
"organization", "organization",
"contact_person", "contact_person",
"name_form", "name_form",
@@ -97,48 +97,61 @@ class Config(object):
def_context = "" def_context = ""
def __init__(self): def __init__(self):
self._attr = {"": {}, "sp": {}, "idp": {}, "aa": {}, "pdp": {}} self.entityid = None
self.xmlsec_binary= None
self.debug=False
self.key_file=None
self.cert_file=None
self.secret=None
self.accepted_time_diff=None
self.name=None
self.ca_certs=None
self.description=None
self.valid_for=None
self.organization=None
self.contact_person=None
self.name_form=None
self.virtual_organization=None
self.logger=None
self.only_use_keys_in_metadata=None
self.logout_requests_signed=None
self.disable_ssl_certificate_validation=None
self.context = "" self.context = ""
self.attribute_converters=None
self.metadata=None
self.policy=None
self.serves = []
def serves(self): # def copy_into(self, typ=""):
return [t for t in ["sp", "idp", "aa", "pdp"] if self._attr[t]] # if typ == "sp":
# copy = SPConfig()
def copy_into(self, typ=""): # elif typ in ["idp", "aa"]:
if typ == "sp": # copy = IdPConfig()
copy = SPConfig() # else:
elif typ in ["idp", "aa"]: # copy = Config()
copy = IdPConfig() # copy.context = typ
else: # copy._attr = self._attr.copy()
copy = Config() # return copy
copy.context = typ
copy._attr = self._attr.copy()
return copy
def __getattribute__(self, item):
if item == "context":
return object.__getattribute__(self, item)
_context = self.context
if item in ALL:
try:
return self._attr[_context][item]
except KeyError:
if _context:
try:
return self._attr[""][item]
except KeyError:
pass
return None
else:
return object.__getattribute__(self, item)
def setattr(self, context, attr, val): def setattr(self, context, attr, val):
self._attr[context][attr] = val if context == "":
setattr(self, attr, val)
else:
setattr(self, "_%s_%s" % (context,attr), val)
def getattr(self, attr, context=None):
if context is None:
context = self.context
if context == "":
return getattr(self, attr, None)
else:
return getattr(self, "_%s_%s" % (context,attr), None)
def load_special(self, cnf, typ, metadata_construction=False): def load_special(self, cnf, typ, metadata_construction=False):
for arg in SPEC[typ]: for arg in SPEC[typ]:
try: try:
self._attr[typ][arg] = cnf[arg] self.setattr(typ, arg, cnf[arg])
except KeyError: except KeyError:
pass pass
@@ -147,9 +160,8 @@ class Config(object):
self.context = self.def_context self.context = self.def_context
def load_complex(self, cnf, typ="", metadata_construction=False): def load_complex(self, cnf, typ="", metadata_construction=False):
_attr_typ = self._attr[typ]
try: try:
_attr_typ["policy"] = Policy(cnf["policy"]) self.setattr(typ, "policy", Policy(cnf["policy"]))
except KeyError: except KeyError:
pass pass
@@ -162,16 +174,20 @@ class Config(object):
if not acs: if not acs:
raise Exception(("No attribute converters, ", raise Exception(("No attribute converters, ",
"something is wrong!!")) "something is wrong!!"))
try:
_attr_typ["attribute_converters"].extend(acs) _acs = self.getattr("attribute_converters", typ)
except KeyError: if _acs:
_attr_typ["attribute_converters"] = acs _acs.extend(acs)
else:
self.setattr(typ, "attribute_converters", acs)
except KeyError: except KeyError:
pass pass
if not metadata_construction: if not metadata_construction:
try: try:
_attr_typ["metadata"] = self.load_metadata(cnf["metadata"]) self.setattr(typ, "metadata",
self.load_metadata(cnf["metadata"]))
except KeyError: except KeyError:
pass pass
@@ -185,7 +201,7 @@ class Config(object):
""" """
for arg in COMMON_ARGS: for arg in COMMON_ARGS:
try: try:
self._attr[""][arg] = cnf[arg] setattr(self, arg, cnf[arg])
except KeyError: except KeyError:
pass pass
@@ -194,19 +210,19 @@ class Config(object):
try: try:
self.load_special(cnf["service"][typ], typ, self.load_special(cnf["service"][typ], typ,
metadata_construction=metadata_construction) metadata_construction=metadata_construction)
self.serves.append(typ)
except KeyError: except KeyError:
pass pass
if not metadata_construction: if not metadata_construction:
if "xmlsec_binary" not in self._attr[""]: if not self.xmlsec_binary:
self._attr[""]["xmlsec_binary"] = get_xmlsec_binary() self.xmlsec_binary = get_xmlsec_binary()
# verify that xmlsec is where it's supposed to be # verify that xmlsec is where it's supposed to be
if not os.path.exists(self._attr[""]["xmlsec_binary"]): if not os.path.exists(self.xmlsec_binary):
#if not os.access(, os.F_OK): #if not os.access(, os.F_OK):
raise Exception("xmlsec binary not in '%s' !" % ( raise Exception("xmlsec binary not in '%s' !" % (
self._attr[""]["xmlsec_binary"])) self.xmlsec_binary))
self.load_complex(cnf, metadata_construction=metadata_construction) self.load_complex(cnf, metadata_construction=metadata_construction)
self.context = self.def_context self.context = self.def_context
@@ -258,7 +274,7 @@ class Config(object):
metad.import_external_metadata(spec["url"], cert) metad.import_external_metadata(spec["url"], cert)
return metad return metad
def endpoint(self, service, binding=None): def endpoint(self, service, binding=None, context=None):
""" Goes through the list of endpoint specifications for the """ Goes through the list of endpoint specifications for the
given type of service and returnes the first endpoint that matches given type of service and returnes the first endpoint that matches
the given binding. If no binding is given any endpoint for that the given binding. If no binding is given any endpoint for that
@@ -270,7 +286,9 @@ class Config(object):
""" """
spec = [] spec = []
unspec = [] unspec = []
for endpspec in self.endpoints[service]: endps = self.getattr("endpoints")
if endps and service in endps:
for endpspec in endps[service]:
try: try:
endp, bind = endpspec endp, bind = endpspec
if binding is None or bind == binding: if binding is None or bind == binding:
@@ -327,9 +345,8 @@ class Config(object):
if root_logger.level != logging.NOTSET: # Someone got there before me if root_logger.level != logging.NOTSET: # Someone got there before me
return root_logger return root_logger
try: _logconf = self.logger
_logconf = self._attr[""]["logger"] if _logconf is None:
except KeyError:
return root_logger return root_logger
try: try:
@@ -341,20 +358,6 @@ class Config(object):
root_logger.info("Logging started") root_logger.info("Logging started")
return root_logger return root_logger
def keys(self):
keys = []
for dir in ["", "sp", "idp", "aa"]:
keys.extend(self._attr[dir].keys())
return list(set(keys))
def __contains__(self, item):
for dir in ["", "sp", "idp", "aa"]:
if item in self._attr[dir]:
return True
return False
class SPConfig(Config): class SPConfig(Config):
def_context = "sp" def_context = "sp"
@@ -393,16 +396,20 @@ class SPConfig(Config):
""" """
res = [] res = []
if self.aa is None or entity_id in self.aa: aa_eid = self.getattr("entity_id")
if aa_eid:
if entity_id in aa_eid:
for aad in self.metadata.attribute_authority(entity_id): for aad in self.metadata.attribute_authority(entity_id):
for attrserv in aad.attribute_service: for attrserv in aad.attribute_service:
if attrserv.binding == binding: if attrserv.binding == binding:
res.append(attrserv) res.append(attrserv)
else:
return self.metadata.attribute_authority()
return res return res
def idps(self, langpref=None): def idps(self, langpref=None):
""" Returns a dictionary of usefull IdPs, the keys being the """ Returns a dictionary of useful IdPs, the keys being the
entity ID of the service and the names of the services as values entity ID of the service and the names of the services as values
:param langpref: The preferred languages of the name, the first match :param langpref: The preferred languages of the name, the first match
@@ -412,11 +419,13 @@ class SPConfig(Config):
if langpref is None: if langpref is None:
langpref = ["en"] langpref = ["en"]
if self.idp: eidp = self.getattr("entity_id")
if eidp:
return dict([(e, nd[0]) for (e, return dict([(e, nd[0]) for (e,
nd) in self.metadata.idps(langpref).items() if e in self.idp]) nd) in self.metadata.idps(langpref).items() if e in eidp])
else: else:
return self.metadata.idps() return dict([(e, nd[0]) for (e,
nd) in self.metadata.idps(langpref).items()])
def vo_conf(self, vo_name): def vo_conf(self, vo_name):
try: try:
@@ -431,8 +440,9 @@ class SPConfig(Config):
:param ipaddress: The IP address of the user client :param ipaddress: The IP address of the user client
:return: IdP entity ID or None :return: IdP entity ID or None
""" """
if "ecp" in self._attr["sp"]: _ecp = self.getattr("ecp")
for key, eid in self._attr["sp"]["ecp"].items(): if _ecp:
for key, eid in _ecp.items():
if re.match(key, ipaddress): if re.match(key, ipaddress):
return eid return eid

View File

@@ -32,7 +32,6 @@ from saml2.profile import ecp
from saml2.server import Server from saml2.server import Server
from saml2.schema import soapenv from saml2.schema import soapenv
from saml2.s_utils import sid
from saml2.response import authn_response from saml2.response import authn_response
@@ -115,8 +114,8 @@ def ecp_auth_request(cls, entityid=None, relay_state="", sign=False):
logger.info("entityid: %s, binding: %s" % (entityid, BINDING_SOAP)) logger.info("entityid: %s, binding: %s" % (entityid, BINDING_SOAP))
location = cls._sso_location(entityid, binding=BINDING_SOAP) location = cls._sso_location(entityid, binding=BINDING_SOAP)
session_id = sid() authn_req = cls.create_authn_request(location,
authn_req = cls.authn(location, session_id, binding=BINDING_PAOS, binding=BINDING_PAOS,
service_url_binding=BINDING_PAOS) service_url_binding=BINDING_PAOS)
body = soapenv.Body() body = soapenv.Body()
@@ -128,7 +127,7 @@ def ecp_auth_request(cls, entityid=None, relay_state="", sign=False):
soap_envelope = soapenv.Envelope(header=header, body=body) soap_envelope = soapenv.Envelope(header=header, body=body)
return session_id, "%s" % soap_envelope return authn_req.id, "%s" % soap_envelope
def handle_ecp_authn_response(cls, soap_message, outstanding=None): def handle_ecp_authn_response(cls, soap_message, outstanding=None):

View File

@@ -51,7 +51,6 @@ from saml2.saml import NAME_FORMAT_URI
from saml2.time_util import in_a_while from saml2.time_util import in_a_while
from saml2.time_util import valid from saml2.time_util import valid
from saml2.attribute_converter import from_local_name from saml2.attribute_converter import from_local_name
from saml2.attribute_converter import ava_fro
from saml2.sigver import pre_signature_part from saml2.sigver import pre_signature_part
from saml2.sigver import make_temp, cert_from_key_info, verify_signature from saml2.sigver import make_temp, cert_from_key_info, verify_signature
from saml2.sigver import pem_format from saml2.sigver import pem_format
@@ -104,7 +103,6 @@ class MetaData(object):
self.http = httplib2.Http(ca_certs=ca_certs, self.http = httplib2.Http(ca_certs=ca_certs,
disable_ssl_certificate_validation=disable_ssl_certificate_validation) disable_ssl_certificate_validation=disable_ssl_certificate_validation)
self._import = {} self._import = {}
self._wants = {}
self._keys = {} self._keys = {}
self._extension_modules = metadata_extension_modules() self._extension_modules = metadata_extension_modules()
self.post_load_process = post_load_process self.post_load_process = post_load_process
@@ -152,7 +150,7 @@ class MetaData(object):
except KeyError: except KeyError:
self._loc_key[ident] = {use: certs} self._loc_key[ident] = {use: certs}
def _vo_metadata(self, entity_descr, entity, tag): def _affiliation(self, entity_descr, entity, tag):
""" """
Pick out the Affiliation descriptors from an entity Pick out the Affiliation descriptors from an entity
descriptor and store the information in a way which is easily descriptor and store the information in a way which is easily
@@ -170,60 +168,25 @@ class MetaData(object):
if members: if members:
entity[tag] = members entity[tag] = members
def _sp_metadata(self, entity_descr, entity, tag): afd._certs = self._certs(afd.key_descriptor, "pem")
self._add_certs(entity_descr.entity_id, afd._certs)
def _spsso(self, dp, entity_descr):
""" """
Pick out the SP SSO descriptors from an entity
descriptor and store the information in a way which is easily
accessible.
:param entity_descr: A EntityDescriptor instance :param dp:
:param entity_descr
:return:
""" """
try: dp = self._role(dp, entity_descr)
ssd = entity_descr.spsso_descriptor
except AttributeError:
return
ssds = [] if dp._certs:
required = [] for acs in dp.assertion_consumer_service:
optional = [] self._add_certs(acs.location, dp._certs)
#print "..... %s ..... " % entity_descriptor.entity_id
for tssd in ssd:
# Only want to talk to SAML 2.0 entities
if samlp.NAMESPACE not in \
tssd.protocol_support_enumeration.split(" "):
#print "<<<", idp.protocol_support_enumeration
continue
ssds.append(tssd) return dp
certs = self._certs(tssd.key_descriptor, "pem")
self._add_certs(entity_descr.entity_id, certs)
self._extensions(tssd) def _idpsso(self, dp, entity_descr):
for acs in tssd.attribute_consuming_service:
for attr in acs.requested_attribute:
#print "==", attr
if attr.is_required == "true":
required.append(attr)
else:
optional.append(attr)
for acs in tssd.assertion_consumer_service:
self._add_certs(acs.location, certs)
if required or optional:
#print "REQ",required
#print "OPT",optional
self._wants[entity_descr.entity_id] = (ava_fro(self.attrconv,
required),
ava_fro(self.attrconv,
optional))
if ssds:
entity[tag] = ssds
def _idp_metadata(self, entity_descr, entity, tag):
""" """
Pick out the IdP SSO descriptors from an entity Pick out the IdP SSO descriptors from an entity
descriptor and store the information in a way which is easily descriptor and store the information in a way which is easily
@@ -231,32 +194,17 @@ class MetaData(object):
:param entity_descr: A EntityDescriptor instance :param entity_descr: A EntityDescriptor instance
""" """
try:
isd = entity_descr.idpsso_descriptor
except AttributeError:
return
idps = [] dp = self._role(dp, entity_descr)
for tidp in isd: if dp._certs:
if samlp.NAMESPACE not in \ for sso in dp.single_sign_on_service:
tidp.protocol_support_enumeration.split(" "): self._add_certs(sso.location, dp._certs)
#print "<<<", idp.protocol_support_enumeration
continue
idps.append(tidp) self._extensions(dp)
certs = self._certs(tidp.key_descriptor, "pem") return dp
self._add_certs(entity_descr.entity_id, certs) def _attribute_authority(self, dp, entity_descr):
for sso in tidp.single_sign_on_service:
self._add_certs(sso.location, certs)
self._extensions(tidp)
if idps:
entity[tag] = idps
def _aad_metadata(self, entity_descr, entity, tag):
""" """
Pick out the attribute authority descriptors from an entity Pick out the attribute authority descriptors from an entity
descriptor and store the information in a way which is easily descriptor and store the information in a way which is easily
@@ -264,105 +212,107 @@ class MetaData(object):
:param entity_descr: A EntityDescriptor instance :param entity_descr: A EntityDescriptor instance
""" """
try:
attr_auth_descr = entity_descr.attribute_authority_descriptor
except AttributeError:
#print "No Attribute AD: %s" % entity_descr.entity_id
return
aads = []
for taad in attr_auth_descr:
# Remove everyone that doesn't talk SAML 2.0
#print "supported protocols", taad.protocol_support_enumeration
if samlp.NAMESPACE not in \
taad.protocol_support_enumeration.split(" "):
continue
# remove the bindings I can't handle # remove the bindings I can't handle
aserv = [] aserv = []
for attr_serv in taad.attribute_service: for attr_serv in dp.attribute_service:
#print "binding", attr_serv.binding #print "binding", attr_serv.binding
if attr_serv.binding == BINDING_SOAP: if attr_serv.binding == BINDING_SOAP:
aserv.append(attr_serv) aserv.append(attr_serv)
if not aserv: if not aserv:
continue return None
taad.attribute_service = aserv dp.attribute_service = aserv
self._extensions(taad) dp = self._role(dp, entity_descr)
# gather all the certs and place them in temporary files if dp._certs:
certs = self._certs(taad.key_descriptor, "pem") for attr_serv in dp.attribute_service:
self._add_certs(entity_descr.entity_id, certs) self._add_certs(attr_serv.location, dp._certs)
for sso in taad.attribute_service: return dp
self._add_certs(sso.location, certs)
aads.append(taad) def _pdp(self, dp, entity_descr):
if aads:
entity[tag] = aads
def _pdp_metadata(self, entity_descr, entity, tag):
"""
Pick out the PDP descriptors from an entity
descriptor and store the information in a way which is easily
accessible.
*authz_service=None,
assertion_id_request_service=None,
name_id_format=None,
signature=None,
extensions=None,
key_descriptor=None,
organization=None,
contact_person=None,
id=None,
valid_until=None,
cache_duration=None,
*protocol_support_enumeration=None,
error_url=None,
:param entity_descr: A EntityDescriptor instance
"""
try:
pdp_descr = entity_descr.pdp_descriptor
except AttributeError:
#print "No Attribute AD: %s" % entity_descr.entity_id
return
pdps = []
for pdp in pdp_descr:
# Remove everyone that doesn't talk SAML 2.0
#print "supported protocols", taad.protocol_support_enumeration
if samlp.NAMESPACE not in \
pdp.protocol_support_enumeration.split(" "):
continue
# remove the bindings I can't handle
aserv = [] aserv = []
for authz_serv in pdp.authz_service: for authz_serv in dp.authz_service:
#print "binding", attr_serv.binding #print "binding", attr_serv.binding
if authz_serv.binding == BINDING_SOAP: if authz_serv.binding == BINDING_SOAP:
aserv.append(authz_serv) aserv.append(authz_serv)
if not aserv: if not aserv:
continue return None
pdp.authz_service = aserv dp.authz_service = aserv
self._extensions(pdp) dp = self._role(dp, entity_descr)
if dp._certs:
for aus in dp.authz_service:
self._add_certs(aus.location, dp._certs)
return dp
def _authn_authority(self, dp, entity_descr):
"""
AuthnAuthorityDescriptor
:return:
"""
return self._role(dp, entity_descr)
def _role(self, dp, entity_descr):
"""
RoleDescriptor
:return:
"""
self._extensions(dp)
# gather all the certs and place them in temporary files # gather all the certs and place them in temporary files
certs = self._certs(pdp.key_descriptor, "pem") dp._certs = self._certs(dp.key_descriptor, "pem")
self._add_certs(entity_descr.entity_id, certs) self._add_certs(entity_descr.entity_id, dp._certs)
for aus in pdp.authz_service: return dp
self._add_certs(aus.location, certs)
pdps.append(pdp) def _roledescriptor(self, entity_descr, entity, tag, descriptor, func):
"""
Pick out a specific descriptor from an entity
descriptor and store the information in a way which is easily
accessible.
:param entity_descr: A EntityDescriptor instance
:param entity: The whole entity
:param tag: which tag to store the information under
:param descriptor: The descriptor type
:param func: A processing function specific for the descriptor type
"""
try:
_descr = getattr(entity_descr, descriptor)
except AttributeError:
#print "No Attribute AD: %s" % entity_descr.entity_id
return
dps = []
if isinstance(_descr, list):
for dp in _descr:
# Remove everyone that doesn't talk SAML 2.0
if samlp.NAMESPACE not in \
dp.protocol_support_enumeration.split(" "):
continue
dp = func(dp, entity_descr)
if dp:
dps.append(dp)
elif _descr:
dp = _descr
# Remove everyone that doesn't talk SAML 2.0
if samlp.NAMESPACE in dp.protocol_support_enumeration.split(" "):
dp = func(dp, entity_descr)
if dp:
dps.append(dp)
if dps:
entity[tag] = dps
if pdps:
entity[tag] = pdps
def clear_from_source(self, source): def clear_from_source(self, source):
""" Remove all the metadata references I have gotten from this source """ Remove all the metadata references I have gotten from this source
@@ -420,12 +370,15 @@ class MetaData(object):
elif entity_descr.valid_until: elif entity_descr.valid_until:
entity["valid_until"] = entity_descr.valid_until entity["valid_until"] = entity_descr.valid_until
self._idp_metadata(entity_descr, entity, "idp_sso") # go through the different types of descriptors
self._sp_metadata(entity_descr, entity, "sp_sso") for descr in ["idpsso", "attribute_authority", "authn_authority",
self._aad_metadata(entity_descr, entity, "pdp", "role", "spsso"]:
"attribute_authority") func = getattr(self, "_%s" % descr)
self._vo_metadata(entity_descr, entity, "affiliation") self._roledescriptor(entity_descr, entity, descr,
self._pdp_metadata(entity_descr, entity, "pdp") "%s_descriptor" % descr, func)
self._affiliation(entity_descr, entity, "affiliation")
try: try:
entity["organization"] = entity_descr.organization entity["organization"] = entity_descr.organization
except AttributeError: except AttributeError:
@@ -492,7 +445,7 @@ class MetaData(object):
@keep_updated @keep_updated
def idp_services(self, entity_id, typ, binding=None): def idp_services(self, entity_id, typ, binding=None):
""" depreceated """ """ depreceated """
idps = self.entity[entity_id]["idp_sso"] idps = self.entity[entity_id]["idpsso"]
loc = {} loc = {}
for idp in idps: # None or one for idp in idps: # None or one
@@ -504,7 +457,7 @@ class MetaData(object):
@keep_updated @keep_updated
def sp_services(self, entity_id, typ, binding=None): def sp_services(self, entity_id, typ, binding=None):
""" deprecated """ """ deprecated """
sps = self.entity[entity_id]["sp_sso"] sps = self.entity[entity_id]["spsso"]
loc = {} loc = {}
for sep in sps: # None or one for sep in sps: # None or one
@@ -527,7 +480,7 @@ class MetaData(object):
loc = [] loc = []
try: try:
idps = self.entity[entity_id]["idp_sso"] idps = self.entity[entity_id]["idpsso"]
except KeyError: except KeyError:
return loc return loc
@@ -554,7 +507,7 @@ class MetaData(object):
loc = [] loc = []
try: try:
idps = self.entity[entity_id]["idp_sso"] idps = self.entity[entity_id]["idpsso"]
except KeyError: except KeyError:
return loc return loc
@@ -590,7 +543,7 @@ class MetaData(object):
loc = [] loc = []
try: try:
sss = self.entity[entity_id]["%s_sso" % typ] sss = self.entity[entity_id]["%ssso" % typ]
except KeyError: except KeyError:
return loc return loc
@@ -663,7 +616,7 @@ class MetaData(object):
@keep_updated @keep_updated
def consumer_url(self, entity_id, binding=BINDING_HTTP_POST, _log=None): def consumer_url(self, entity_id, binding=BINDING_HTTP_POST, _log=None):
try: try:
ssos = self.entity[entity_id]["sp_sso"] ssos = self.entity[entity_id]["spsso"]
except KeyError: except KeyError:
raise raise
@@ -683,7 +636,7 @@ class MetaData(object):
@keep_updated @keep_updated
def assertion_consumer_services(self, entity_id, binding=BINDING_HTTP_POST): def assertion_consumer_services(self, entity_id, binding=BINDING_HTTP_POST):
try: try:
ssos = self.entity[entity_id]["sp_sso"] ssos = self.entity[entity_id]["spsso"]
except KeyError: except KeyError:
raise raise
@@ -728,31 +681,39 @@ class MetaData(object):
return name return name
@keep_updated def req_opt(self, acs):
def wants(self, entity_id): req = []
try: opt = []
return self._wants[entity_id]
except KeyError:
return [], []
@keep_updated
def attribute_consumer(self, entity_id):
try:
ssos = self.entity[entity_id]["sp_sso"]
except KeyError:
return [], []
required = []
optional = []
# What if there is more than one ? Can't be ?
for acs in ssos[0].attribute_consuming_service:
for attr in acs.requested_attribute: for attr in acs.requested_attribute:
if attr.is_required == "true": if attr.is_required == "true":
required.append(attr) req.append(attr)
else: else:
optional.append(attr) opt.append(attr)
return required, optional return req, opt
@keep_updated
#def attribute_consumer(self, entity_id, index=None):
def attribute_requirement(self, entity_id, index=None):
try:
ssos = self.entity[entity_id]["spsso"]
except KeyError:
return {}, {}
acss = ssos[0].attribute_consuming_service
if acss is None or acss == []:
return {}, {}
elif len(acss) == 1:
return self.req_opt(acss[0])
else:
if index is None:
for acs in acss:
if acs.default:
return self.req_opt(acs)
# if I get here NO default was found, pick the first ?
return self.req_opt(acss[0])
else:
return self.req_opt(acss[index])
def _orgname(self, org, langs=None): def _orgname(self, org, langs=None):
if not org: if not org:
@@ -792,20 +753,20 @@ class MetaData(object):
langs = ["en"] langs = ["en"]
for entity_id, edict in self.entity.items(): for entity_id, edict in self.entity.items():
if "idp_sso" in edict: if "idpsso" in edict:
#idp_aa_check self._valid(entity_id) #idp_aa_check self._valid(entity_id)
name = None name = None
if "organization" in edict: if "organization" in edict:
name = self._orgname(edict["organization"], langs) name = self._orgname(edict["organization"], langs)
if not name: if not name:
name = self._location(edict["idp_sso"])[0] name = self._location(edict["idpsso"])[0]
idps[entity_id] = (name, edict["idp_sso"]) idps[entity_id] = (name, edict["idpsso"])
return idps return idps
#noinspection PyUnusedLocal #noinspection PyUnusedLocal
@keep_updated @keep_updated
def ui_info(self, entity_id, service="idp_sso"): def ui_info(self, entity_id, service="idpsso"):
inst = self.entity[entity_id][service] inst = self.entity[entity_id][service]
def export_discojuice_json(self, lang=None): def export_discojuice_json(self, lang=None):
@@ -826,7 +787,7 @@ class MetaData(object):
result = [] result = []
for entity_id, entity in self.entity.items(): for entity_id, entity in self.entity.items():
try: try:
for _sso in entity['idp_sso']: for _sso in entity['idpsso']:
rdict = {'entityID': entity_id, rdict = {'entityID': entity_id,
'title': self._orgname(entity['organization'], lang)} 'title': self._orgname(entity['organization'], lang)}
@@ -996,12 +957,7 @@ def do_requested_attribute(attributes, acs, is_required="false"):
lista.append(md.RequestedAttribute(**args)) lista.append(md.RequestedAttribute(**args))
return lista return lista
def do_uiinfo(conf): def do_uiinfo(_uiinfo):
try:
_uiinfo = conf.ui_info
except AttributeError:
return None
uii = mdui.UIInfo() uii = mdui.UIInfo()
for attr in ['display_name', 'description', "information_url", for attr in ['display_name', 'description', "information_url",
'privacy_statement_url']: 'privacy_statement_url']:
@@ -1157,12 +1113,13 @@ DEFAULT = {
"want_authn_requests_signed": "false", "want_authn_requests_signed": "false",
} }
def do_sp_sso_descriptor(conf, cert=None): def do_spsso_descriptor(conf, cert=None):
spsso = md.SPSSODescriptor() spsso = md.SPSSODescriptor()
spsso.protocol_support_enumeration = samlp.NAMESPACE spsso.protocol_support_enumeration = samlp.NAMESPACE
if conf.endpoints: endps = conf.getattr("endpoints", "sp")
for (endpoint, instlist) in do_endpoints(conf.endpoints, if endps:
for (endpoint, instlist) in do_endpoints(endps,
ENDPOINTS["sp"]).items(): ENDPOINTS["sp"]).items():
setattr(spsso, endpoint, instlist) setattr(spsso, endpoint, instlist)
@@ -1171,7 +1128,7 @@ def do_sp_sso_descriptor(conf, cert=None):
for key in ["want_assertions_signed", "authn_requests_signed"]: for key in ["want_assertions_signed", "authn_requests_signed"]:
try: try:
val = getattr(conf, key) val = conf.getattr(key, "sp")
if val is None: if val is None:
setattr(spsso, key, DEFAULT[key]) #default ?! setattr(spsso, key, DEFAULT[key]) #default ?!
else: else:
@@ -1181,16 +1138,15 @@ def do_sp_sso_descriptor(conf, cert=None):
setattr(spsso, key, DEFAULTS[key]) setattr(spsso, key, DEFAULTS[key])
requested_attributes = [] requested_attributes = []
if conf.required_attributes: acs = conf.attribute_converters
requested_attributes.extend(do_requested_attribute( req = conf.getattr("required_attributes", "sp")
conf.required_attributes, if req:
conf.attribute_converters, requested_attributes.extend(do_requested_attribute(req, acs,
is_required="true")) is_required="true"))
if conf.optional_attributes: opt=conf.getattr("optional_attributes", "sp")
requested_attributes.extend(do_requested_attribute( if opt:
conf.optional_attributes, requested_attributes.extend(do_requested_attribute(opt, acs))
conf.attribute_converters))
if requested_attributes: if requested_attributes:
spsso.attribute_consuming_service = [md.AttributeConsumingService( spsso.attribute_consuming_service = [md.AttributeConsumingService(
@@ -1211,43 +1167,47 @@ def do_sp_sso_descriptor(conf, cert=None):
except KeyError: except KeyError:
pass pass
if conf.discovery_response: dresp = conf.getattr("discovery_response", "sp")
if dresp:
if spsso.extensions is None: if spsso.extensions is None:
spsso.extensions = md.Extensions() spsso.extensions = md.Extensions()
spsso.extensions.add_extension_element(do_idpdisc(conf.discovery_response)) spsso.extensions.add_extension_element(do_idpdisc(dresp))
return spsso return spsso
def do_idp_sso_descriptor(conf, cert=None): def do_idpsso_descriptor(conf, cert=None):
idpsso = md.IDPSSODescriptor() idpsso = md.IDPSSODescriptor()
idpsso.protocol_support_enumeration = samlp.NAMESPACE idpsso.protocol_support_enumeration = samlp.NAMESPACE
if conf.endpoints: endps = conf.getattr("endpoints", "idp")
for (endpoint, instlist) in do_endpoints(conf.endpoints, if endps:
for (endpoint, instlist) in do_endpoints(endps,
ENDPOINTS["idp"]).items(): ENDPOINTS["idp"]).items():
setattr(idpsso, endpoint, instlist) setattr(idpsso, endpoint, instlist)
if conf.scope: scopes = conf.getattr("scope", "idp")
if scopes:
if idpsso.extensions is None: if idpsso.extensions is None:
idpsso.extensions = md.Extensions() idpsso.extensions = md.Extensions()
for scope in conf.scope: for scope in scopes:
mdscope = shibmd.Scope() mdscope = shibmd.Scope()
mdscope.text = scope mdscope.text = scope
# unless scope contains '*'/'+'/'?' assume non regexp ? # unless scope contains '*'/'+'/'?' assume non regexp ?
mdscope.regexp = "false" mdscope.regexp = "false"
idpsso.extensions.add_extension_element(mdscope) idpsso.extensions.add_extension_element(mdscope)
if conf.ui_info: ui_info = conf.getattr("ui_info", "idp")
if ui_info:
if idpsso.extensions is None: if idpsso.extensions is None:
idpsso.extensions = md.Extensions() idpsso.extensions = md.Extensions()
idpsso.extensions.add_extension_element(do_uiinfo(conf)) idpsso.extensions.add_extension_element(do_uiinfo(ui_info))
if cert: if cert:
idpsso.key_descriptor = do_key_descriptor(cert) idpsso.key_descriptor = do_key_descriptor(cert)
for key in ["want_authn_requests_signed"]: for key in ["want_authn_requests_signed"]:
try: try:
val = getattr(conf,key) val = conf.getattr(key, "idp")
if val is None: if val is None:
setattr(idpsso, key, DEFAULT["want_authn_requests_signed"]) setattr(idpsso, key, DEFAULT["want_authn_requests_signed"])
else: else:
@@ -1294,41 +1254,31 @@ def do_pdp_descriptor(conf, cert):
return pdp return pdp
def entity_descriptor(confd, valid_for): def entity_descriptor(confd):
mycert = "".join(open(confd.cert_file).readlines()[1:-1]) mycert = "".join(open(confd.cert_file).readlines()[1:-1])
# if "attribute_map_dir" in confd:
# attrconverters = ac_factory(confd.attribute_map_dir)
# else:
# attrconverters = [AttributeConverter()]
#if "attribute_maps" in confd:
# (forward,backward) = parse_attribute_map(confd["attribute_maps"])
#else:
# backward = {}
entd = md.EntityDescriptor() entd = md.EntityDescriptor()
entd.entity_id = confd.entityid entd.entity_id = confd.entityid
if valid_for: if confd.valid_for:
entd.valid_until = in_a_while(hours=valid_for) entd.valid_until = in_a_while(hours=int(confd.valid_for))
if confd.organization is not None: if confd.organization is not None:
entd.organization = do_organization_info(confd.organization) entd.organization = do_organization_info(confd.organization)
if confd.contact_person is not None: if confd.contact_person is not None:
entd.contact_person = do_contact_person_info(confd.contact_person) entd.contact_person = do_contact_person_info(confd.contact_person)
serves = confd.serves() serves = confd.serves
if not serves: if not serves:
raise Exception( raise Exception(
'No service type ("sp","idp","aa") provided in the configuration') 'No service type ("sp","idp","aa") provided in the configuration')
if "sp" in serves: if "sp" in serves:
confd.context = "sp" confd.context = "sp"
entd.spsso_descriptor = do_sp_sso_descriptor(confd, mycert) entd.spsso_descriptor = do_spsso_descriptor(confd, mycert)
if "idp" in serves: if "idp" in serves:
confd.context = "idp" confd.context = "idp"
entd.idpsso_descriptor = do_idp_sso_descriptor(confd, mycert) entd.idpsso_descriptor = do_idpsso_descriptor(confd, mycert)
if "aa" in serves: if "aa" in serves:
confd.context = "aa" confd.context = "aa"
entd.attribute_authority_descriptor = do_aa_descriptor(confd, mycert) entd.attribute_authority_descriptor = do_aa_descriptor(confd, mycert)
@@ -1366,10 +1316,7 @@ def entities_descriptor(eds, valid_for, name, ident, sign, secc):
entities = md.entities_descriptor_from_string(xmldoc) entities = md.entities_descriptor_from_string(xmldoc)
return entities return entities
def sign_entity_descriptor(edesc, valid_for, ident, secc): def sign_entity_descriptor(edesc, ident, secc):
if valid_for:
edesc.valid_until = in_a_while(hours=valid_for)
if not ident: if not ident:
ident = sid() ident = sid()

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
import logging import logging
import random
import string
import time import time
import base64 import base64
@@ -102,8 +104,19 @@ def deflate_and_base64_encode( string_val ):
""" """
return base64.b64encode( zlib.compress( string_val )[2:-4] ) return base64.b64encode( zlib.compress( string_val )[2:-4] )
def rndstr(size=16):
"""
Returns a string of random ascii characters or digits
:param size: The length of the string
:return: string
"""
_basech = string.ascii_letters + string.digits
return "".join([random.choice(_basech) for _ in range(size)])
def sid(seed=""): def sid(seed=""):
"""The hash of the server time + seed makes an unique SID for each session. """The hash of the server time + seed makes an unique SID for each session.
128-bits long so it fulfills the SAML2 requirements which states 128-160 bits
:param seed: A seed string :param seed: A seed string
:return: The hex version of the digest, prefixed by 'id-' to make it :return: The hex version of the digest, prefixed by 'id-' to make it

View File

@@ -366,7 +366,8 @@ class AssertionIDRequestType_(RequestAbstractType_):
c_attributes = RequestAbstractType_.c_attributes.copy() c_attributes = RequestAbstractType_.c_attributes.copy()
c_child_order = RequestAbstractType_.c_child_order[:] c_child_order = RequestAbstractType_.c_child_order[:]
c_cardinality = RequestAbstractType_.c_cardinality.copy() c_cardinality = RequestAbstractType_.c_cardinality.copy()
c_children['{urn:oasis:names:tc:SAML:2.0:assertion}AssertionIDRef'] = ('assertion_id_ref', [saml.AssertionIDRef]) c_children['{urn:oasis:names:tc:SAML:2.0:assertion}AssertionIDRef'] = (
'assertion_id_ref', [saml.AssertionIDRef])
c_cardinality['assertion_id_ref'] = {"min":1} c_cardinality['assertion_id_ref'] = {"min":1}
c_child_order.extend(['assertion_id_ref']) c_child_order.extend(['assertion_id_ref'])

View File

@@ -258,7 +258,7 @@ class Server(object):
try: try:
# subject information is stored in a database # subject information is stored in a database
# default database is a shelve database which is OK in some setups # default database is a shelve database which is OK in some setups
dbspec = self.conf.subject_data dbspec = self.conf.getattr("subject_data", "idp")
idb = None idb = None
if isinstance(dbspec, basestring): if isinstance(dbspec, basestring):
idb = shelve.open(dbspec, writeback=True) idb = shelve.open(dbspec, writeback=True)
@@ -376,14 +376,15 @@ class Server(object):
return response return response
def wants(self, sp_entity_id): def wants(self, sp_entity_id, index=None):
""" Returns what attributes the SP requiers and which are optional """ Returns what attributes the SP requires and which are optional
if any such demands are registered in the Metadata. if any such demands are registered in the Metadata.
:param sp_entity_id: The entity id of the SP :param sp_entity_id: The entity id of the SP
:param index: which of the attribute consumer services its all about
:return: 2-tuple, list of required and list of optional attributes :return: 2-tuple, list of required and list of optional attributes
""" """
return self.metadata.requests(sp_entity_id) return self.metadata.attribute_requirement(sp_entity_id, index)
def parse_attribute_query(self, xml_string, decode=True): def parse_attribute_query(self, xml_string, decode=True):
""" Parse an attribute query """ Parse an attribute query
@@ -410,29 +411,21 @@ class Server(object):
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
def _response(self, in_response_to, consumer_url=None, sp_entity_id=None, def _response(self, in_response_to, consumer_url=None, status=None,
identity=None, name_id=None, status=None, sign=False, issuer=None, sign=False, to_sign=None,
policy=Policy(), authn=None, authn_decl=None, issuer=None): **kwargs):
""" Create a Response that adhers to the ??? profile. """ Create a Response that adhers to the ??? profile.
:param in_response_to: The session identifier of the request :param in_response_to: The session identifier of the request
:param consumer_url: The URL which should receive the response :param consumer_url: The URL which should receive the response
:param sp_entity_id: The entity identifier of the SP
:param identity: A dictionary with attributes and values that are
expected to be the bases for the assertion in the response.
:param name_id: The identifier of the subject
:param status: The status of the response :param status: The status of the response
:param sign: Whether the assertion should be signed or not
:param policy: The attribute release policy for this instance
:param authn: A 2-tuple denoting the authn class and the authn
authority
:param authn_decl:
:param issuer: The issuer of the response :param issuer: The issuer of the response
:param sign: Whether the response should be signed or not
:param to_sign: What other parts to sign
:param kwargs: Extra key word arguments
:return: A Response instance :return: A Response instance
""" """
to_sign = []
if not status: if not status:
status = success_status_factory() status = success_status_factory()
@@ -447,13 +440,55 @@ class Server(object):
if consumer_url: if consumer_url:
response.destination = consumer_url response.destination = consumer_url
for key, val in kwargs.items():
setattr(response, key, val)
if sign:
try:
to_sign.append((class_name(response), response.id))
except AttributeError:
to_sign = [(class_name(response), response.id)]
return signed_instance_factory(response, self.sec, to_sign)
# ------------------------------------------------------------------------
def create_response(self, in_response_to, consumer_url,
sp_entity_id, identity=None, name_id=None,
status=None, authn=None,
authn_decl=None, issuer=None, policy=None,
sign_assertion=False, sign_response=False):
""" Create a response. A layer of indirection.
:param in_response_to: The session identifier of the request
:param consumer_url: The URL which should receive the response
:param sp_entity_id: The entity identifier of the SP
:param identity: A dictionary with attributes and values that are
expected to be the bases for the assertion in the response.
:param name_id: The identifier of the subject
:param status: The status of the response
:param authn: A 2-tuple denoting the authn class and the authn
authority.
:param authn_decl:
:param issuer: The issuer of the response
:param sign_assertion: Whether the assertion should be signed or not
:param sign_response: Whether the response should be signed or not
:return: A response instance
"""
to_sign = []
args = {}
if identity: if identity:
_issuer = self.issuer(issuer)
ast = Assertion(identity) ast = Assertion(identity)
if policy is None:
policy = Policy()
try: try:
ast.apply_policy(sp_entity_id, policy, self.metadata) ast.apply_policy(sp_entity_id, policy, self.metadata)
except MissingValue, exc: except MissingValue, exc:
return self.error_response(in_response_to, consumer_url, return self.create_error_response(in_response_to, consumer_url,
sp_entity_id, exc, name_id) exc, sign_response)
if authn: # expected to be a 2-tuple class+authority if authn: # expected to be a 2-tuple class+authority
(authn_class, authn_authn) = authn (authn_class, authn_authn) = authn
@@ -475,7 +510,7 @@ class Server(object):
self.conf.attribute_converters, self.conf.attribute_converters,
policy, issuer=_issuer) policy, issuer=_issuer)
if sign: if sign_assertion:
assertion.signature = pre_signature_part(assertion.id, assertion.signature = pre_signature_part(assertion.id,
self.sec.my_cert, 1) self.sec.my_cert, 1)
# Just the assertion or the response and the assertion ? # Just the assertion or the response and the assertion ?
@@ -488,73 +523,37 @@ class Server(object):
# sp_entity_id, {"ava": identity, "authn": authn}, # sp_entity_id, {"ava": identity, "authn": authn},
# assertion.conditions.not_on_or_after) # assertion.conditions.not_on_or_after)
response.assertion = assertion args["assertion"] = assertion
return signed_instance_factory(response, self.sec, to_sign) return self._response(in_response_to, consumer_url, status, issuer,
sign_response, to_sign, **args)
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
def do_response(self, in_response_to, consumer_url, def create_error_response(self, in_response_to, destination, info,
sp_entity_id, identity=None, name_id=None, sign=False, issuer=None):
status=None, sign=False, authn=None, authn_decl=None,
issuer=None):
""" Create a response. A layer of indirection.
:param in_response_to: The session identifier of the request
:param consumer_url: The URL which should receive the response
:param sp_entity_id: The entity identifier of the SP
:param identity: A dictionary with attributes and values that are
expected to be the bases for the assertion in the response.
:param name_id: The identifier of the subject
:param status: The status of the response
:param sign: Whether the assertion should be signed or not
:param authn: A 2-tuple denoting the authn class and the authn
authority.
:param authn_decl:
:param issuer: The issuer of the response
:return: A Response instance.
"""
policy = self.conf.policy
return self._response(in_response_to, consumer_url,
sp_entity_id, identity, name_id,
status, sign, policy, authn, authn_decl, issuer)
# ------------------------------------------------------------------------
def error_response(self, in_response_to, destination, spid, info,
name_id=None, sign=False, issuer=None):
""" Create a error response. """ Create a error response.
:param in_response_to: The identifier of the message this is a response :param in_response_to: The identifier of the message this is a response
to. to.
:param destination: The intended recipient of this message :param destination: The intended recipient of this message
:param spid: The entitiy ID of the SP that will get this.
:param info: Either an Exception instance or a 2-tuple consisting of :param info: Either an Exception instance or a 2-tuple consisting of
error code and descriptive text error code and descriptive text
:param name_id: :param sign: Whether the response should be signed or not
:param sign: Whether the message should be signed or not
:param issuer: The issuer of the response :param issuer: The issuer of the response
:return: A Response instance :return: A response instance
""" """
status = error_status_factory(info) status = error_status_factory(info)
return self._response( return self._response(in_response_to, destination, status, issuer,
in_response_to, # in_response_to sign)
destination, # consumer_url
spid, # sp_entity_id
name_id=name_id,
status=status,
sign=sign,
issuer=issuer
)
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
#noinspection PyUnusedLocal #noinspection PyUnusedLocal
def do_aa_response(self, in_response_to, consumer_url, sp_entity_id, def create_aa_response(self, in_response_to, consumer_url, sp_entity_id,
identity=None, userid="", name_id=None, status=None, identity=None, userid="", name_id=None, status=None,
sign=False, _name_id_policy=None, issuer=None): issuer=None, sign_assertion=False,
sign_response=False):
""" Create an attribute assertion response. """ Create an attribute assertion response.
:param in_response_to: The session identifier of the request :param in_response_to: The session identifier of the request
@@ -565,24 +564,27 @@ class Server(object):
:param userid: A identifier of the user :param userid: A identifier of the user
:param name_id: The identifier of the subject :param name_id: The identifier of the subject
:param status: The status of the response :param status: The status of the response
:param sign: Whether the assertion should be signed or not
:param _name_id_policy: Policy for NameID creation.
:param issuer: The issuer of the response :param issuer: The issuer of the response
:return: A Response instance. :param sign_assertion: Whether the assertion should be signed or not
:param sign_response: Whether the whole response should be signed
:return: A response instance
""" """
# name_id = self.ident.construct_nameid(self.conf.policy, userid, # name_id = self.ident.construct_nameid(self.conf.policy, userid,
# sp_entity_id, identity) # sp_entity_id, identity)
return self._response(in_response_to, consumer_url, return self.create_response(in_response_to, consumer_url, sp_entity_id,
sp_entity_id, identity, name_id, identity, name_id, status,
status, sign, policy=self.conf.policy, issuer=issuer) issuer=issuer,
policy=self.conf.getattr("policy", "aa"),
sign_assertion=sign_assertion,
sign_response=sign_response)
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
def authn_response(self, identity, in_response_to, destination, def create_authn_response(self, identity, in_response_to, destination,
sp_entity_id, name_id_policy, userid, sign=False, sp_entity_id, name_id_policy, userid,
authn=None, sign_response=False, authn_decl=None, authn=None, authn_decl=None, issuer=None,
issuer=None, instance=False): sign_response=False, sign_assertion=False):
""" Constructs an AuthenticationResponse """ Constructs an AuthenticationResponse
:param identity: Information about an user :param identity: Information about an user
@@ -590,73 +592,50 @@ class Server(object):
this response is an answer to. this response is an answer to.
:param destination: Where the response should be sent :param destination: Where the response should be sent
:param sp_entity_id: The entity identifier of the Service Provider :param sp_entity_id: The entity identifier of the Service Provider
:param name_id_policy: ... :param name_id_policy: How the NameID should be constructed
:param userid: The subject identifier :param userid: The subject identifier
:param sign: Whether the assertion should be signed or not. This is
different from signing the response as such.
:param authn: Information about the authentication :param authn: Information about the authentication
:param sign_response: The response can be signed separately from the
assertions.
:param authn_decl: :param authn_decl:
:param issuer: Issuer of the response :param issuer: Issuer of the response
:param instance: Whether to return the instance or a string :param sign_assertion: Whether the assertion should be signed or not.
representation :param sign_response: Whether the response should be signed or not.
:return: A XML string representing an authentication response :return: A response instance
""" """
name_id = None name_id = None
try: try:
nid_formats = [] nid_formats = []
for _sp in self.metadata.entity[sp_entity_id]["sp_sso"]: for _sp in self.metadata.entity[sp_entity_id]["spsso"]:
nid_formats.extend([n.text for n in _sp.name_id_format]) nid_formats.extend([n.text for n in _sp.name_id_format])
policy = self.conf.policy policy = self.conf.getattr("policy", "idp")
name_id = self.ident.construct_nameid(policy, userid, sp_entity_id, name_id = self.ident.construct_nameid(policy, userid, sp_entity_id,
identity, name_id_policy, identity, name_id_policy,
nid_formats) nid_formats)
except IOError, exc: except IOError, exc:
response = self.error_response(in_response_to, destination, response = self.create_error_response(in_response_to, destination,
sp_entity_id, exc, name_id) sp_entity_id, exc, name_id)
return ("%s" % response).split("\n") return ("%s" % response).split("\n")
try: try:
response = self.do_response( return self.create_response(in_response_to, # in_response_to
in_response_to, # in_response_to
destination, # consumer_url destination, # consumer_url
sp_entity_id, # sp_entity_id sp_entity_id, # sp_entity_id
identity, # identity as dictionary identity, # identity as dictionary
name_id, name_id,
sign=sign, # If the assertion should be signed
authn=authn, # Information about the authn=authn, # Information about the
# authentication # authentication
authn_decl=authn_decl, authn_decl=authn_decl,
issuer=issuer issuer=issuer,
) policy=policy,
sign_assertion=sign_assertion,
sign_response=sign_response)
except MissingValue, exc: except MissingValue, exc:
response = self.error_response(in_response_to, destination, return self.create_error_response(in_response_to, destination,
sp_entity_id, exc, name_id) sp_entity_id, exc, name_id)
if sign_response:
try:
response.signature = pre_signature_part(response.id,
self.sec.my_cert, 2)
return self.sec.sign_statement_using_xmlsec(response,
class_name(response),
nodeid=response.id)
except Exception, exc:
response = self.error_response(in_response_to, destination,
sp_entity_id, exc, name_id)
if instance:
return response
else:
return ("%s" % response).split("\n")
else:
if instance:
return response
else:
return ("%s" % response).split("\n")
def parse_logout_request(self, text, binding=BINDING_SOAP): def parse_logout_request(self, text, binding=BINDING_SOAP):
"""Parse a Logout Request """Parse a Logout Request
@@ -669,9 +648,9 @@ class Server(object):
""" """
try: try:
slo = self.conf.endpoint("single_logout_service", binding) slo = self.conf.endpoint("single_logout_service", binding, "idp")
except IndexError: except IndexError:
logger.info("enpoints: %s" % (self.conf.endpoints,)) logger.info("enpoints: %s" % self.conf.getattr("endpoints", "idp"))
logger.info("binding wanted: %s" % (binding,)) logger.info("binding wanted: %s" % (binding,))
raise raise
@@ -703,8 +682,8 @@ class Server(object):
return req return req
def logout_response(self, request, bindings, status=None, sign=False, def create_logout_response(self, request, bindings, status=None,
issuer=None): sign=False, issuer=None):
""" Create a LogoutResponse. What is returned depends on which binding """ Create a LogoutResponse. What is returned depends on which binding
is used. is used.
@@ -794,7 +773,7 @@ class Server(object):
attribute - which attributes that the requestor wants back attribute - which attributes that the requestor wants back
query - the whole query query - the whole query
""" """
receiver_addresses = self.conf.endpoint("attribute_service") receiver_addresses = self.conf.endpoint("attribute_service", "idp")
attribute_query = AttributeQuery( self.sec, receiver_addresses) attribute_query = AttributeQuery( self.sec, receiver_addresses)
attribute_query = attribute_query.loads(xml_string) attribute_query = attribute_query.loads(xml_string)

View File

@@ -7,6 +7,7 @@ try:
except ImportError: except ImportError:
xmlsec_path = '/opt/local/bin/xmlsec1' xmlsec_path = '/opt/local/bin/xmlsec1'
BASE = "http://localhost:8088"
CONFIG = { CONFIG = {
"entityid" : "urn:mace:example.com:saml:roland:idp", "entityid" : "urn:mace:example.com:saml:roland:idp",
@@ -15,10 +16,10 @@ CONFIG = {
"idp": { "idp": {
"endpoints" : { "endpoints" : {
"single_sign_on_service" : [ "single_sign_on_service" : [
("http://localhost:8088/sso", BINDING_HTTP_REDIRECT)], ("%s/sso" % BASE, BINDING_HTTP_REDIRECT)],
"single_logout_service": [ "single_logout_service": [
("http://localhost:8088/slo", BINDING_SOAP), ("%s/slo" % BASE, BINDING_SOAP),
("http://localhost:8088/slop",BINDING_HTTP_POST)] ("%s/slop" % BASE,BINDING_HTTP_POST)]
}, },
"policy": { "policy": {
"default": { "default": {
@@ -43,7 +44,7 @@ CONFIG = {
"cert_file" : "test.pem", "cert_file" : "test.pem",
"xmlsec_binary" : xmlsec_path, "xmlsec_binary" : xmlsec_path,
"metadata": { "metadata": {
"local": ["metadata.xml", "vo_metadata.xml"], "local": ["metadata_sp_1.xml", "vo_metadata.xml"],
}, },
"attribute_map_dir" : "attributemaps", "attribute_map_dir" : "attributemaps",
"organization": { "organization": {

View File

@@ -53,7 +53,7 @@ CONFIG = {
"debug" : 1, "debug" : 1,
"key_file" : "test.key", "key_file" : "test.key",
"cert_file" : "test.pem", "cert_file" : "test.pem",
#"xmlsec_binary" : xmlsec_path, "xmlsec_binary" : xmlsec_path,
"metadata": { "metadata": {
"local": ["metadata.xml", "vo_metadata.xml"], "local": ["metadata.xml", "vo_metadata.xml"],
}, },

View File

@@ -21,7 +21,7 @@ CONFIG = {
"debug" : 1, "debug" : 1,
"key_file" : "test.key", "key_file" : "test.key",
"cert_file" : "test.pem", "cert_file" : "test.pem",
#"xmlsec_binary" : xmlsec_path, "xmlsec_binary" : xmlsec_path,
"metadata": { "metadata": {
"local": ["idp_aa.xml", "vo_metadata.xml"], "local": ["idp_aa.xml", "vo_metadata.xml"],
}, },

View File

@@ -34,6 +34,7 @@ CONFIG={
"subject_data": "subject_data.db", "subject_data": "subject_data.db",
"accepted_time_diff": 60, "accepted_time_diff": 60,
"attribute_map_dir" : "attributemaps", "attribute_map_dir" : "attributemaps",
"valid_for": 6,
"organization": { "organization": {
"name": ("AB Exempel", "se"), "name": ("AB Exempel", "se"),
"display_name": ("AB Exempel", "se"), "display_name": ("AB Exempel", "se"),

View File

@@ -20,7 +20,7 @@ CONFIG = {
"debug" : 1, "debug" : 1,
"key_file" : "test.key", "key_file" : "test.key",
"cert_file" : "test.pem", "cert_file" : "test.pem",
#"xmlsec_binary" : xmlsec_path, "xmlsec_binary" : xmlsec_path,
"metadata": { "metadata": {
"local": ["idp.xml", "vo_metadata.xml"], "local": ["idp.xml", "vo_metadata.xml"],
}, },

View File

@@ -8,7 +8,7 @@ from saml2 import BINDING_SOAP
from saml2 import md, saml, samlp from saml2 import md, saml, samlp
from saml2 import time_util from saml2 import time_util
from saml2.saml import NAMEID_FORMAT_TRANSIENT, NAME_FORMAT_URI from saml2.saml import NAMEID_FORMAT_TRANSIENT, NAME_FORMAT_URI
from saml2.attribute_converter import ac_factory from saml2.attribute_converter import ac_factory, to_local_name
#from py.test import raises #from py.test import raises
@@ -48,38 +48,41 @@ def test_swami_1():
md.import_metadata(_read_file(SWAMI_METADATA),"-") md.import_metadata(_read_file(SWAMI_METADATA),"-")
print len(md.entity) print len(md.entity)
assert len(md.entity) assert len(md.entity)
idps = dict([(id,ent["idp_sso"]) for id,ent in md.entity.items() \ idps = dict([(id,ent["idpsso"]) for id,ent in md.entity.items() \
if "idp_sso" in ent]) if "idpsso" in ent])
print idps print idps
assert idps.keys() assert idps.keys()
idp_sso = md.single_sign_on_services( idpsso = md.single_sign_on_services(
'https://idp.umu.se/saml2/idp/metadata.php') 'https://idp.umu.se/saml2/idp/metadata.php')
assert md.name('https://idp.umu.se/saml2/idp/metadata.php') == ( assert md.name('https://idp.umu.se/saml2/idp/metadata.php') == (
u'Ume\xe5 University (SAML2)') u'Ume\xe5 University (SAML2)')
assert len(idp_sso) == 1 assert len(idpsso) == 1
assert idp_sso == ['https://idp.umu.se/saml2/idp/SSOService.php'] assert idpsso == ['https://idp.umu.se/saml2/idp/SSOService.php']
print md._loc_key['https://idp.umu.se/saml2/idp/SSOService.php'] print md._loc_key['https://idp.umu.se/saml2/idp/SSOService.php']
ssocerts = md.certs('https://idp.umu.se/saml2/idp/SSOService.php', "signing") ssocerts = md.certs('https://idp.umu.se/saml2/idp/SSOService.php', "signing")
print ssocerts print ssocerts
assert len(ssocerts) == 1 assert len(ssocerts) == 1
print md._wants.keys() sps = dict([(id,ent["spsso"]) for id,ent in md.entity.items()\
assert _eq(md._wants.keys(),['https://sp.swamid.se/shibboleth', if "spsso" in ent])
'https://connect8.sunet.se/shibboleth',
'https://beta.lobber.se/shibboleth',
'https://connect.uninett.no/shibboleth',
'https://www.diva-portal.org/shibboleth',
'https://connect.sunet.se/shibboleth',
'https://crowd.nordu.net/shibboleth'])
print md.wants('https://www.diva-portal.org/shibboleth') acs_sp = []
assert _eq(md.wants('https://www.diva-portal.org/shibboleth')[1].keys(), for nam, desc in sps.items():
if desc[0].attribute_consuming_service:
acs_sp.append(nam)
#print md.wants('https://www.diva-portal.org/shibboleth')
wants = md.attribute_requirement('https://connect8.sunet.se/shibboleth')
lnamn = [to_local_name(md.attrconv, attr) for attr in wants[1]]
assert _eq(lnamn,
['mail', 'givenName', 'eduPersonPrincipalName', 'sn', ['mail', 'givenName', 'eduPersonPrincipalName', 'sn',
'eduPersonScopedAffiliation']) 'eduPersonScopedAffiliation'])
assert md.wants('https://connect.sunet.se/shibboleth')[0] == {} wants = md.attribute_requirement('https://beta.lobber.se/shibboleth')
assert _eq(md.wants('https://connect.sunet.se/shibboleth')[1].keys(), assert wants[0] == []
['mail', 'givenName', 'eduPersonPrincipalName', 'sn', lnamn = [to_local_name(md.attrconv, attr) for attr in wants[1]]
'eduPersonScopedAffiliation']) assert _eq(lnamn,
['eduPersonScopedAffiliation', 'eduPersonEntitlement',
'eduPersonPrincipalName', 'sn', 'mail', 'givenName'])
def test_incommon_1(): def test_incommon_1():
md = metadata.MetaData(attrconv=ATTRCONV) md = metadata.MetaData(attrconv=ATTRCONV)
@@ -87,23 +90,39 @@ def test_incommon_1():
print len(md.entity) print len(md.entity)
assert len(md.entity) == 442 assert len(md.entity) == 442
idps = dict([ idps = dict([
(id,ent["idp_sso"]) for id,ent in md.entity.items() if "idp_sso" in ent]) (id,ent["idpsso"]) for id,ent in md.entity.items() if "idpsso" in ent])
print idps.keys() print idps.keys()
assert len(idps) == 53 # !!!!???? < 10% assert len(idps) == 53 # !!!!???? < 10%
assert md.single_sign_on_services('urn:mace:incommon:uiuc.edu') == [] assert md.single_sign_on_services('urn:mace:incommon:uiuc.edu') == []
idp_sso = md.single_sign_on_services('urn:mace:incommon:alaska.edu') idpsso = md.single_sign_on_services('urn:mace:incommon:alaska.edu')
assert len(idp_sso) == 1 assert len(idpsso) == 1
print idp_sso print idpsso
print md.wants assert idpsso == ['https://idp.alaska.edu/idp/profile/SAML2/Redirect/SSO']
assert idp_sso == ['https://idp.alaska.edu/idp/profile/SAML2/Redirect/SSO']
sps = dict([(id,ent["spsso"]) for id,ent in md.entity.items()\
if "spsso" in ent])
acs_sp = []
for nam, desc in sps.items():
if desc[0].attribute_consuming_service:
acs_sp.append(nam)
assert len(acs_sp) == 0
# Look for attribute authorities
aas = dict([(id,ent["attribute_authority"]) for id,ent in md.entity.items()\
if "attribute_authority" in ent])
print aas.keys()
assert len(aas) == 53
def test_example(): def test_example():
md = metadata.MetaData(attrconv=ATTRCONV) md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_read_file(EXAMPLE_METADATA), "-") md.import_metadata(_read_file(EXAMPLE_METADATA), "-")
print len(md.entity) print len(md.entity)
assert len(md.entity) == 1 assert len(md.entity) == 1
idps = dict([(id,ent["idp_sso"]) for id,ent in md.entity.items() \ idps = dict([(id,ent["idpsso"]) for id,ent in md.entity.items() \
if "idp_sso" in ent]) if "idpsso" in ent])
assert idps.keys() == [ assert idps.keys() == [
'http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php'] 'http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php']
print md._loc_key['http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php'] print md._loc_key['http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php']
@@ -119,14 +138,14 @@ def test_switch_1():
md.import_metadata(_read_file(SWITCH_METADATA), "-") md.import_metadata(_read_file(SWITCH_METADATA), "-")
print len(md.entity) print len(md.entity)
assert len(md.entity) == 90 assert len(md.entity) == 90
idps = dict([(id,ent["idp_sso"]) for id,ent in md.entity.items() \ idps = dict([(id,ent["idpsso"]) for id,ent in md.entity.items() \
if "idp_sso" in ent]) if "idpsso" in ent])
print idps.keys() print idps.keys()
idp_sso = md.single_sign_on_services( idpsso = md.single_sign_on_services(
'https://aai-demo-idp.switch.ch/idp/shibboleth') 'https://aai-demo-idp.switch.ch/idp/shibboleth')
assert len(idp_sso) == 1 assert len(idpsso) == 1
print idp_sso print idpsso
assert idp_sso == [ assert idpsso == [
'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO'] 'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO']
assert len(idps) == 16 assert len(idps) == 16
aas = dict([(id,ent["attribute_authority"]) for id,ent in md.entity.items() \ aas = dict([(id,ent["attribute_authority"]) for id,ent in md.entity.items() \
@@ -138,7 +157,7 @@ def test_switch_1():
assert len(aad.attribute_service) == 1 assert len(aad.attribute_service) == 1
assert len(aad.name_id_format) == 2 assert len(aad.name_id_format) == 2
dual = dict([(id,ent) for id,ent in md.entity.items() \ dual = dict([(id,ent) for id,ent in md.entity.items() \
if "idp_sso" in ent and "sp_sso" in ent]) if "idpsso" in ent and "spsso" in ent])
print len(dual) print len(dual)
assert len(dual) == 0 assert len(dual) == 0
@@ -150,10 +169,10 @@ def test_sp_metadata():
assert len(md.entity) == 1 assert len(md.entity) == 1
assert md.entity.keys() == ['urn:mace:umu.se:saml:roland:sp'] assert md.entity.keys() == ['urn:mace:umu.se:saml:roland:sp']
assert _eq(md.entity['urn:mace:umu.se:saml:roland:sp'].keys(), [ assert _eq(md.entity['urn:mace:umu.se:saml:roland:sp'].keys(), [
'valid_until',"organization","sp_sso", 'valid_until',"organization","spsso",
'contact_person']) 'contact_person'])
print md.entity['urn:mace:umu.se:saml:roland:sp']["sp_sso"][0].keyswv() print md.entity['urn:mace:umu.se:saml:roland:sp']["spsso"][0].keyswv()
(req,opt) = md.attribute_consumer('urn:mace:umu.se:saml:roland:sp') (req,opt) = md.attribute_requirement('urn:mace:umu.se:saml:roland:sp')
print req print req
assert len(req) == 3 assert len(req) == 3
assert len(opt) == 1 assert len(opt) == 1
@@ -162,13 +181,6 @@ def test_sp_metadata():
assert _eq([n.name for n in req],['urn:oid:2.5.4.4', 'urn:oid:2.5.4.42', assert _eq([n.name for n in req],['urn:oid:2.5.4.4', 'urn:oid:2.5.4.42',
'urn:oid:0.9.2342.19200300.100.1.3']) 'urn:oid:0.9.2342.19200300.100.1.3'])
assert _eq([n.friendly_name for n in req],['surName', 'givenName', 'mail']) assert _eq([n.friendly_name for n in req],['surName', 'givenName', 'mail'])
print md.wants
assert md._wants.keys() == ['urn:mace:umu.se:saml:roland:sp']
assert _eq(md.wants('urn:mace:umu.se:saml:roland:sp')[0].keys(),
["mail", "givenName", "sn"])
assert _eq(md.wants('urn:mace:umu.se:saml:roland:sp')[1].keys(),
["title"])
KALMAR2_URL = "https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2" KALMAR2_URL = "https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2"
KALMAR2_CERT = "kalmar2.pem" KALMAR2_CERT = "kalmar2.pem"
@@ -180,7 +192,7 @@ KALMAR2_CERT = "kalmar2.pem"
# print len(md.entity) # print len(md.entity)
# assert len(md.entity) > 20 # assert len(md.entity) > 20
# idps = dict([ # idps = dict([
# (id,ent["idp_sso"]) for id,ent in md.entity.items() if "idp_sso" in ent]) # (id,ent["idpsso"]) for id,ent in md.entity.items() if "idpsso" in ent])
# print idps.keys() # print idps.keys()
# assert len(idps) > 1 # assert len(idps) > 1
# assert "https://idp.umu.se/saml2/idp/metadata.php" in idps # assert "https://idp.umu.se/saml2/idp/metadata.php" in idps

View File

@@ -163,15 +163,15 @@ def test_1():
c = SPConfig().load(sp1) c = SPConfig().load(sp1)
c.context = "sp" c.context = "sp"
print c print c
assert c.endpoints assert c._sp_endpoints
assert c.name assert c._sp_name
assert c.idp assert c._sp_idp
md = c.metadata md = c.metadata
assert isinstance(md, MetaData) assert isinstance(md, MetaData)
assert len(c.idp) == 1 assert len(c._sp_idp) == 1
assert c.idp.keys() == ["urn:mace:example.com:saml:roland:idp"] assert c._sp_idp.keys() == ["urn:mace:example.com:saml:roland:idp"]
assert c.idp.values() == [{'single_sign_on_service': assert c._sp_idp.values() == [{'single_sign_on_service':
{'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': {'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect':
'http://localhost:8088/sso/'}}] 'http://localhost:8088/sso/'}}]
@@ -182,15 +182,16 @@ def test_2():
c.context = "sp" c.context = "sp"
print c print c
assert c.endpoints assert c._sp_endpoints
assert c.idp assert c.getattr("endpoints", "sp")
assert c.optional_attributes assert c._sp_idp
assert c._sp_optional_attributes
assert c.name assert c.name
assert c.required_attributes assert c._sp_required_attributes
assert len(c.idp) == 1 assert len(c._sp_idp) == 1
assert c.idp.keys() == [""] assert c._sp_idp.keys() == [""]
assert c.idp.values() == ["https://example.com/saml2/idp/SSOService.php"] assert c._sp_idp.values() == ["https://example.com/saml2/idp/SSOService.php"]
assert c.only_use_keys_in_metadata is None assert c.only_use_keys_in_metadata is None
def test_minimum(): def test_minimum():
@@ -222,7 +223,7 @@ def test_idp_1():
print c print c
assert c.endpoint("single_sign_on_service")[0] == 'http://localhost:8088/' assert c.endpoint("single_sign_on_service")[0] == 'http://localhost:8088/'
attribute_restrictions = c.policy.get_attribute_restriction("") attribute_restrictions = c.getattr("policy","idp").get_attribute_restriction("")
assert attribute_restrictions["eduPersonAffiliation"][0].match("staff") assert attribute_restrictions["eduPersonAffiliation"][0].match("staff")
def test_idp_2(): def test_idp_2():
@@ -235,7 +236,7 @@ def test_idp_2():
assert c.endpoint("single_logout_service", assert c.endpoint("single_logout_service",
BINDING_HTTP_REDIRECT) == ["http://localhost:8088/"] BINDING_HTTP_REDIRECT) == ["http://localhost:8088/"]
attribute_restrictions = c.policy.get_attribute_restriction("") attribute_restrictions = c.getattr("policy","idp").get_attribute_restriction("")
assert attribute_restrictions["eduPersonAffiliation"][0].match("staff") assert attribute_restrictions["eduPersonAffiliation"][0].match("staff")
def test_wayf(): def test_wayf():
@@ -313,15 +314,12 @@ def test_sp():
def test_dual(): def test_dual():
cnf = Config().load_file("idp_sp_conf") cnf = Config().load_file("idp_sp_conf")
assert cnf.serves() == ["sp", "idp"]
spcnf = cnf.copy_into("sp") spe = cnf.getattr("endpoints", "sp")
assert isinstance(spcnf, SPConfig) idpe = cnf.getattr("endpoints", "idp")
assert spcnf.context == "sp" assert spe
assert idpe
idpcnf = cnf.copy_into("idp") assert spe != idpe
assert isinstance(idpcnf, IdPConfig)
assert idpcnf.context == "idp"
def test_ecp(): def test_ecp():
cnf = SPConfig() cnf = SPConfig()

View File

@@ -20,6 +20,10 @@ XML_RESPONSE_FILE2 = "saml2_response.xml"
def _eq(l1,l2): def _eq(l1,l2):
return set(l1) == set(l2) return set(l1) == set(l2)
IDENTITY = {"eduPersonAffiliation": ["staff", "member"],
"surName": ["Jeter"], "givenName": ["Derek"],
"mail": ["foo@gmail.com"]}
class TestResponse: class TestResponse:
def setup_class(self): def setup_class(self):
server = Server("idp_conf") server = Server("idp_conf")
@@ -27,28 +31,28 @@ class TestResponse:
"urn:mace:example.com:saml:roland:sp", "urn:mace:example.com:saml:roland:sp",
"id12") "id12")
self._resp_ = server.do_response( self._resp_ = server.create_response(
"id12", # in_response_to "id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url "http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
{"eduPersonEntitlement":"Jeter"}, IDENTITY,
name_id = name_id name_id = name_id
) )
self._sign_resp_ = server.do_response( self._sign_resp_ = server.create_response(
"id12", # in_response_to "id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url "http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
{"eduPersonEntitlement":"Jeter"}, IDENTITY,
name_id = name_id, name_id = name_id,
sign=True sign_assertion=True
) )
self._resp_authn = server.do_response( self._resp_authn = server.create_response(
"id12", # in_response_to "id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url "http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
{"eduPersonEntitlement":"Jeter"}, IDENTITY,
name_id = name_id, name_id = name_id,
authn=(saml.AUTHN_PASSWORD, "http://www.example.com/login") authn=(saml.AUTHN_PASSWORD, "http://www.example.com/login")
) )

View File

@@ -1,51 +1,48 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from saml2 import samlp, BINDING_HTTP_POST from saml2 import saml
from saml2 import saml, config, class_name, make_instance
from saml2.server import Server from saml2.server import Server
from saml2.response import authn_response, StatusResponse from saml2.response import authn_response
from saml2.config import config_factory from saml2.config import config_factory
XML_RESPONSE_FILE = "saml_signed.xml" XML_RESPONSE_FILE = "saml_signed.xml"
XML_RESPONSE_FILE2 = "saml2_response.xml" XML_RESPONSE_FILE2 = "saml2_response.xml"
import os
def _eq(l1,l2): def _eq(l1,l2):
return set(l1) == set(l2) return set(l1) == set(l2)
IDENTITY = {"eduPersonAffiliation": ["staff", "member"],
"surName": ["Jeter"], "givenName": ["Derek"],
"mail": ["foo@gmail.com"]}
class TestAuthnResponse: class TestAuthnResponse:
def setup_class(self): def setup_class(self):
server = Server("idp_conf") server = Server("idp_conf")
name_id = server.ident.transient_nameid( name_id = server.ident.transient_nameid(
"urn:mace:example.com:saml:roland:sp","id12") "urn:mace:example.com:saml:roland:sp","id12")
policy = server.conf.getattr("policy", "idp")
self._resp_ = server.do_response( self._resp_ = server.create_response(
"id12", # in_response_to "id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url "http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
{"eduPersonEntitlement":"Jeter"}, IDENTITY, name_id = name_id, policy=policy)
name_id = name_id
)
self._sign_resp_ = server.do_response( self._sign_resp_ = server.create_response(
"id12", # in_response_to "id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url "http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
{"eduPersonEntitlement":"Jeter"}, IDENTITY,
name_id = name_id, sign_assertion=True, policy=policy)
self._resp_authn = server.create_response(
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
IDENTITY,
name_id = name_id, name_id = name_id,
sign=True authn=(saml.AUTHN_PASSWORD, "http://www.example.com/login"),
) policy=policy)
self._resp_authn = server.do_response(
"id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
{"eduPersonEntitlement":"Jeter"},
name_id = name_id,
authn=(saml.AUTHN_PASSWORD, "http://www.example.com/login")
)
self.conf = config_factory("sp", "server_conf") self.conf = config_factory("sp", "server_conf")
self.ar = authn_response(self.conf, "http://lingon.catalogix.se:8087/") self.ar = authn_response(self.conf, "http://lingon.catalogix.se:8087/")
@@ -60,7 +57,7 @@ class TestAuthnResponse:
print self.ar.__dict__ print self.ar.__dict__
assert self.ar.came_from == 'http://localhost:8088/sso' assert self.ar.came_from == 'http://localhost:8088/sso'
assert self.ar.session_id() == "id12" assert self.ar.session_id() == "id12"
assert self.ar.ava == {'eduPersonEntitlement': ['Jeter'] } assert self.ar.ava == IDENTITY
assert self.ar.name_id assert self.ar.name_id
assert self.ar.issuer() == 'urn:mace:example.com:saml:roland:idp' assert self.ar.issuer() == 'urn:mace:example.com:saml:roland:idp'
@@ -76,7 +73,7 @@ class TestAuthnResponse:
print self.ar.__dict__ print self.ar.__dict__
assert self.ar.came_from == 'http://localhost:8088/sso' assert self.ar.came_from == 'http://localhost:8088/sso'
assert self.ar.session_id() == "id12" assert self.ar.session_id() == "id12"
assert self.ar.ava == {'eduPersonEntitlement': ['Jeter'] } assert self.ar.ava == IDENTITY
assert self.ar.issuer() == 'urn:mace:example.com:saml:roland:idp' assert self.ar.issuer() == 'urn:mace:example.com:saml:roland:idp'
assert self.ar.name_id assert self.ar.name_id

View File

@@ -139,26 +139,17 @@ class TestServer1():
assert status.status_code.value == samlp.STATUS_SUCCESS assert status.status_code.value == samlp.STATUS_SUCCESS
def test_parse_faulty_request(self): def test_parse_faulty_request(self):
authn_request = self.client.authn_request( authn_request = self.client.create_authn_request(
query_id = "id1",
destination = "http://www.example.com", destination = "http://www.example.com",
service_url = "http://www.example.org", id = "id1")
spentityid = "urn:mace:example.com:saml:roland:sp",
my_name = "My real name",
)
intermed = s_utils.deflate_and_base64_encode("%s" % authn_request) intermed = s_utils.deflate_and_base64_encode("%s" % authn_request)
# should raise an error because faulty spentityid # should raise an error because faulty spentityid
raises(OtherError, self.server.parse_authn_request, intermed) raises(OtherError, self.server.parse_authn_request, intermed)
def test_parse_faulty_request_to_err_status(self): def test_parse_faulty_request_to_err_status(self):
authn_request = self.client.authn_request( authn_request = self.client.create_authn_request(
query_id = "id1", destination = "http://www.example.com")
destination = "http://www.example.com",
service_url = "http://www.example.org",
spentityid = "urn:mace:example.com:saml:roland:sp",
my_name = "My real name",
)
intermed = s_utils.deflate_and_base64_encode("%s" % authn_request) intermed = s_utils.deflate_and_base64_encode("%s" % authn_request)
try: try:
@@ -178,20 +169,17 @@ class TestServer1():
assert status_code.status_code.value == samlp.STATUS_UNKNOWN_PRINCIPAL assert status_code.status_code.value == samlp.STATUS_UNKNOWN_PRINCIPAL
def test_parse_ok_request(self): def test_parse_ok_request(self):
authn_request = self.client.authn_request( authn_request = self.client.create_authn_request(
query_id = "id1", id = "id1",
destination = "http://localhost:8088/sso", destination = "http://localhost:8088/sso")
service_url = "http://localhost:8087/",
spentityid = "urn:mace:example.com:saml:roland:sp",
my_name = "My real name",
)
print authn_request print authn_request
intermed = s_utils.deflate_and_base64_encode("%s" % authn_request) intermed = s_utils.deflate_and_base64_encode("%s" % authn_request)
response = self.server.parse_authn_request(intermed) response = self.server.parse_authn_request(intermed)
# returns a dictionary # returns a dictionary
print response print response
assert response["consumer_url"] == "http://localhost:8087/" assert response["consumer_url"] == "http://lingon.catalogix.se:8087/"
assert response["id"] == "id1" assert response["id"] == "id1"
name_id_policy = response["request"].name_id_policy name_id_policy = response["request"].name_id_policy
assert _eq(name_id_policy.keyswv(), ["format", "allow_create"]) assert _eq(name_id_policy.keyswv(), ["format", "allow_create"])
@@ -202,12 +190,16 @@ class TestServer1():
name_id = self.server.ident.transient_nameid( name_id = self.server.ident.transient_nameid(
"urn:mace:example.com:saml:roland:sp", "urn:mace:example.com:saml:roland:sp",
"id12") "id12")
resp = self.server.do_response( resp = self.server.create_response(
"id12", # in_response_to "id12", # in_response_to
"http://localhost:8087/", # consumer_url "http://localhost:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
{ "eduPersonEntitlement": "Short stop"}, # identity {"eduPersonEntitlement": "Short stop",
name_id "surName": "Jeter",
"givenName": "Derek",
"mail": "derek.jeter@nyy.mlb.com"},
name_id,
policy= self.server.conf.getattr("policy")
) )
print resp.keyswv() print resp.keyswv()
@@ -227,7 +219,7 @@ class TestServer1():
assert assertion.attribute_statement assert assertion.attribute_statement
attribute_statement = assertion.attribute_statement attribute_statement = assertion.attribute_statement
print attribute_statement print attribute_statement
assert len(attribute_statement.attribute) == 1 assert len(attribute_statement.attribute) == 4
attribute = attribute_statement.attribute[0] attribute = attribute_statement.attribute[0]
assert len(attribute.attribute_value) == 1 assert len(attribute.attribute_value) == 1
assert attribute.friendly_name == "eduPersonEntitlement" assert attribute.friendly_name == "eduPersonEntitlement"
@@ -245,7 +237,7 @@ class TestServer1():
assert confirmation.subject_confirmation_data.in_response_to == "id12" assert confirmation.subject_confirmation_data.in_response_to == "id12"
def test_sso_response_without_identity(self): def test_sso_response_without_identity(self):
resp = self.server.do_response( resp = self.server.create_response(
"id12", # in_response_to "id12", # in_response_to
"http://localhost:8087/", # consumer_url "http://localhost:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
@@ -263,8 +255,9 @@ class TestServer1():
def test_sso_failure_response(self): def test_sso_failure_response(self):
exc = s_utils.MissingValue("eduPersonAffiliation missing") exc = s_utils.MissingValue("eduPersonAffiliation missing")
resp = self.server.error_response("id12", "http://localhost:8087/", resp = self.server.create_error_response("id12",
"urn:mace:example.com:saml:roland:sp", exc ) "http://localhost:8087/",
exc )
print resp.keyswv() print resp.keyswv()
assert _eq(resp.keyswv(),['status', 'destination', 'in_response_to', assert _eq(resp.keyswv(),['status', 'destination', 'in_response_to',
@@ -291,14 +284,15 @@ class TestServer1():
ava = { "givenName": ["Derek"], "surName": ["Jeter"], ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]} "mail": ["derek@nyy.mlb.com"]}
resp_str = self.server.authn_response(ava, npolicy = samlp.NameIDPolicy(format=saml.NAMEID_FORMAT_TRANSIENT,
"id1", "http://local:8087/", allow_create="true")
resp_str = "%s" % self.server.create_authn_response(
ava, "id1", "http://local:8087/",
"urn:mace:example.com:saml:roland:sp", "urn:mace:example.com:saml:roland:sp",
samlp.NameIDPolicy(format=saml.NAMEID_FORMAT_TRANSIENT, npolicy,
allow_create="true"),
"foba0001@example.com") "foba0001@example.com")
response = samlp.response_from_string("\n".join(resp_str)) response = samlp.response_from_string(resp_str)
print response.keyswv() print response.keyswv()
assert _eq(response.keyswv(),['status', 'destination', 'assertion', assert _eq(response.keyswv(),['status', 'destination', 'assertion',
'in_response_to', 'issue_instant', 'version', 'in_response_to', 'issue_instant', 'version',
@@ -318,14 +312,16 @@ class TestServer1():
name_id = self.server.ident.transient_nameid( name_id = self.server.ident.transient_nameid(
"urn:mace:example.com:saml:roland:sp", "urn:mace:example.com:saml:roland:sp",
"id12") "id12")
ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]}
signed_resp = self.server.do_response( signed_resp = self.server.create_response(
"id12", # in_response_to "id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url "http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
{"eduPersonEntitlement":"Jeter"}, ava,
name_id = name_id, name_id = name_id,
sign=True sign_assertion=True
) )
print "%s" % signed_resp print "%s" % signed_resp
@@ -352,9 +348,9 @@ class TestServer1():
} }
self.client.users.add_information_about_person(sinfo) self.client.users.add_information_about_person(sinfo)
logout_request = self.client.construct_logout_request( logout_request = self.client.create_logout_request(
subject_id="foba0001",
destination = "http://localhost:8088/slop", destination = "http://localhost:8088/slop",
subject_id="foba0001",
issuer_entity_id = "urn:mace:example.com:saml:roland:idp", issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
reason = "I'm tired of this") reason = "I'm tired of this")
@@ -379,7 +375,8 @@ class TestServer1():
sp = client.Saml2Client(config_file="server_conf") sp = client.Saml2Client(config_file="server_conf")
sp.users.add_information_about_person(sinfo) sp.users.add_information_about_person(sinfo)
logout_request = sp.construct_logout_request(subject_id = "foba0001", logout_request = sp.create_logout_request(
subject_id = "foba0001",
destination = "http://localhost:8088/slo", destination = "http://localhost:8088/slo",
issuer_entity_id = "urn:mace:example.com:saml:roland:idp", issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
reason = "I'm tired of this") reason = "I'm tired of this")
@@ -402,10 +399,12 @@ class TestServer2():
self.server = Server("restrictive_idp_conf") self.server = Server("restrictive_idp_conf")
def test_do_aa_reponse(self): def test_do_aa_reponse(self):
aa_policy = self.server.conf.policy aa_policy = self.server.conf.getattr("policy", "idp")
print aa_policy.__dict__ print aa_policy.__dict__
response = self.server.do_aa_response("aaa", "http://example.com/sp/", response = self.server.create_aa_response("aaa",
"urn:mace:example.com:sp:1", IDENTITY.copy()) "http://example.com/sp/",
"urn:mace:example.com:sp:1",
IDENTITY.copy())
assert response is not None assert response is not None
assert response.destination == "http://example.com/sp/" assert response.destination == "http://example.com/sp/"
@@ -439,7 +438,7 @@ def _logout_request(conf_file):
} }
sp.users.add_information_about_person(sinfo) sp.users.add_information_about_person(sinfo)
return sp.construct_logout_request( return sp.create_logout_request(
subject_id = "foba0001", subject_id = "foba0001",
destination = "http://localhost:8088/slo", destination = "http://localhost:8088/slo",
issuer_entity_id = "urn:mace:example.com:saml:roland:idp", issuer_entity_id = "urn:mace:example.com:saml:roland:idp",
@@ -452,7 +451,8 @@ class TestServerLogout():
request = _logout_request("sp_slo_redirect_conf") request = _logout_request("sp_slo_redirect_conf")
print request print request
bindings = [BINDING_HTTP_REDIRECT] bindings = [BINDING_HTTP_REDIRECT]
(resp, headers, message) = server.logout_response(request, bindings) (resp, headers, message) = server.create_logout_response(request,
bindings)
assert resp == '302 Found' assert resp == '302 Found'
assert len(headers) == 1 assert len(headers) == 1
assert headers[0][0] == "Location" assert headers[0][0] == "Location"

View File

@@ -6,9 +6,12 @@ import urllib
from urlparse import urlparse, parse_qs from urlparse import urlparse, parse_qs
from saml2.client import Saml2Client, LogoutError from saml2.client import Saml2Client, LogoutError
from saml2 import samlp, BINDING_HTTP_POST from saml2 import samlp, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
from saml2 import BINDING_SOAP from saml2 import BINDING_SOAP
from saml2 import saml, config, class_name from saml2 import saml, config, class_name
from saml2.discovery import discovery_service_request_url
from saml2.discovery import discovery_service_response
from saml2.saml import NAMEID_FORMAT_PERSISTENT
from saml2.server import Server from saml2.server import Server
from saml2.s_utils import decode_base64_and_inflate from saml2.s_utils import decode_base64_and_inflate
from saml2.time_util import in_a_while from saml2.time_util import in_a_while
@@ -62,10 +65,11 @@ class TestClient:
self.client = Saml2Client(conf) self.client = Saml2Client(conf)
def test_create_attribute_query1(self): def test_create_attribute_query1(self):
req = self.client.create_attribute_query("id1", req = self.client.create_attribute_query(
"E8042FB4-4D5B-48C3-8E14-8EDD852790DD",
"https://idp.example.com/idp/", "https://idp.example.com/idp/",
nameid_format=saml.NAMEID_FORMAT_PERSISTENT) "E8042FB4-4D5B-48C3-8E14-8EDD852790DD",
nameid_format=saml.NAMEID_FORMAT_PERSISTENT,
id="id1")
reqstr = "%s" % req.to_string() reqstr = "%s" % req.to_string()
assert req.destination == "https://idp.example.com/idp/" assert req.destination == "https://idp.example.com/idp/"
@@ -93,9 +97,9 @@ class TestClient:
assert attrq.subject.name_id.text == name_id.text assert attrq.subject.name_id.text == name_id.text
def test_create_attribute_query2(self): def test_create_attribute_query2(self):
req = self.client.create_attribute_query("id1", req = self.client.create_attribute_query(
"E8042FB4-4D5B-48C3-8E14-8EDD852790DD",
"https://idp.example.com/idp/", "https://idp.example.com/idp/",
"E8042FB4-4D5B-48C3-8E14-8EDD852790DD",
attribute={ attribute={
("urn:oid:2.5.4.42", ("urn:oid:2.5.4.42",
"urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
@@ -106,7 +110,8 @@ class TestClient:
("urn:oid:1.2.840.113549.1.9.1", ("urn:oid:1.2.840.113549.1.9.1",
"urn:oasis:names:tc:SAML:2.0:attrname-format:uri"):None, "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"):None,
}, },
nameid_format=saml.NAMEID_FORMAT_PERSISTENT) nameid_format=saml.NAMEID_FORMAT_PERSISTENT,
id="id1")
print req.to_string() print req.to_string()
assert req.destination == "https://idp.example.com/idp/" assert req.destination == "https://idp.example.com/idp/"
@@ -133,13 +138,14 @@ class TestClient:
if getattr(attribute,"friendly_name"): if getattr(attribute,"friendly_name"):
assert False assert False
seen.append("email") seen.append("email")
assert set(seen) == set(["givenName", "surname", "email"]) assert set(seen) == {"givenName", "surname", "email"}
def test_create_attribute_query_3(self): def test_create_attribute_query_3(self):
req = self.client.create_attribute_query("id1", req = self.client.create_attribute_query(
"_e7b68a04488f715cda642fbdd90099f5",
"https://aai-demo-idp.switch.ch/idp/shibboleth", "https://aai-demo-idp.switch.ch/idp/shibboleth",
nameid_format=saml.NAMEID_FORMAT_TRANSIENT ) "_e7b68a04488f715cda642fbdd90099f5",
nameid_format=saml.NAMEID_FORMAT_TRANSIENT,
id="id1")
assert isinstance(req, samlp.AttributeQuery) assert isinstance(req, samlp.AttributeQuery)
assert req.destination == "https://aai-demo-idp.switch.ch/idp/shibboleth" assert req.destination == "https://aai-demo-idp.switch.ch/idp/shibboleth"
@@ -152,13 +158,13 @@ class TestClient:
assert nameid.text == "_e7b68a04488f715cda642fbdd90099f5" assert nameid.text == "_e7b68a04488f715cda642fbdd90099f5"
def test_attribute_query(self): def test_attribute_query(self):
req = self.client.attribute_query( resp = self.client.do_attribute_query(
"urn:mace:example.com:saml:roland:idp",
"_e7b68a04488f715cda642fbdd90099f5", "_e7b68a04488f715cda642fbdd90099f5",
"https://aai-demo-idp.switch.ch/idp/shibboleth",
nameid_format=saml.NAMEID_FORMAT_TRANSIENT) nameid_format=saml.NAMEID_FORMAT_TRANSIENT)
# since no one is answering on the other end # since no one is answering on the other end
assert req is None assert resp is None
# def test_idp_entry(self): # def test_idp_entry(self):
# idp_entry = self.client.idp_entry(name="Umeå Universitet", # idp_entry = self.client.idp_entry(name="Umeå Universitet",
@@ -179,19 +185,17 @@ class TestClient:
# assert idp_entry.loc == ['http://localhost:8088/sso'] # assert idp_entry.loc == ['http://localhost:8088/sso']
def test_create_auth_request_0(self): def test_create_auth_request_0(self):
ar_str = "%s" % self.client.authn_request("id1", ar_str = "%s" % self.client.create_authn_request(
"http://www.example.com/sso", "http://www.example.com/sso",
"http://www.example.org/service", id="id1")
"urn:mace:example.org:saml:sp",
"My Name")
ar = samlp.authn_request_from_string(ar_str) ar = samlp.authn_request_from_string(ar_str)
print ar print ar
assert ar.assertion_consumer_service_url == "http://www.example.org/service" assert ar.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
assert ar.destination == "http://www.example.com/sso" assert ar.destination == "http://www.example.com/sso"
assert ar.protocol_binding == BINDING_HTTP_POST assert ar.protocol_binding == BINDING_HTTP_POST
assert ar.version == "2.0" assert ar.version == "2.0"
assert ar.provider_name == "My Name" assert ar.provider_name == "urn:mace:example.com:saml:roland:sp"
assert ar.issuer.text == "urn:mace:example.org:saml:sp" assert ar.issuer.text == "urn:mace:example.com:saml:roland:sp"
nid_policy = ar.name_id_policy nid_policy = ar.name_id_policy
assert nid_policy.allow_create == "true" assert nid_policy.allow_create == "true"
assert nid_policy.format == saml.NAMEID_FORMAT_TRANSIENT assert nid_policy.format == saml.NAMEID_FORMAT_TRANSIENT
@@ -200,35 +204,33 @@ class TestClient:
assert self.client.config.virtual_organization.keys() == [ assert self.client.config.virtual_organization.keys() == [
"urn:mace:example.com:it:tek"] "urn:mace:example.com:it:tek"]
ar_str = "%s" % self.client.authn_request("666", ar_str = "%s" % self.client.create_authn_request(
"http://www.example.com/sso", "http://www.example.com/sso",
"http://www.example.org/service", "urn:mace:example.com:it:tek", # vo
"urn:mace:example.org:saml:sp", nameid_format=NAMEID_FORMAT_PERSISTENT,
"My Name", id="666")
vorg="urn:mace:example.com:it:tek")
ar = samlp.authn_request_from_string(ar_str) ar = samlp.authn_request_from_string(ar_str)
print ar print ar
assert ar.id == "666" assert ar.id == "666"
assert ar.assertion_consumer_service_url == "http://www.example.org/service" assert ar.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
assert ar.destination == "http://www.example.com/sso" assert ar.destination == "http://www.example.com/sso"
assert ar.protocol_binding == BINDING_HTTP_POST assert ar.protocol_binding == BINDING_HTTP_POST
assert ar.version == "2.0" assert ar.version == "2.0"
assert ar.provider_name == "My Name" assert ar.provider_name == "urn:mace:example.com:saml:roland:sp"
assert ar.issuer.text == "urn:mace:example.org:saml:sp" assert ar.issuer.text == "urn:mace:example.com:saml:roland:sp"
nid_policy = ar.name_id_policy nid_policy = ar.name_id_policy
assert nid_policy.allow_create == "true" assert nid_policy.allow_create == "false"
assert nid_policy.format == saml.NAMEID_FORMAT_PERSISTENT assert nid_policy.format == saml.NAMEID_FORMAT_PERSISTENT
assert nid_policy.sp_name_qualifier == "urn:mace:example.com:it:tek" assert nid_policy.sp_name_qualifier == "urn:mace:example.com:it:tek"
def test_sign_auth_request_0(self): def test_sign_auth_request_0(self):
#print self.client.config #print self.client.config
ar_str = "%s" % self.client.authn_request("id1", ar_str = "%s" % self.client.create_authn_request(
"http://www.example.com/sso", "http://www.example.com/sso",
"http://www.example.org/service", sign=True,
"urn:mace:example.org:saml:sp", id="id1")
"My Name", sign=True)
ar = samlp.authn_request_from_string(ar_str) ar = samlp.authn_request_from_string(ar_str)
@@ -251,17 +253,20 @@ class TestClient:
def test_response(self): def test_response(self):
IDP = "urn:mace:example.com:saml:roland:idp" IDP = "urn:mace:example.com:saml:roland:idp"
ava = { "givenName": ["Derek"], "surname": ["Jeter"], ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]} "mail": ["derek@nyy.mlb.com"]}
resp_str = "\n".join(self.server.authn_response( nameid_policy=samlp.NameIDPolicy(allow_create="false",
identity=ava, format=saml.NAMEID_FORMAT_PERSISTENT)
resp = self.server.create_authn_response(identity=ava,
in_response_to="id1", in_response_to="id1",
destination="http://lingon.catalogix.se:8087/", destination="http://lingon.catalogix.se:8087/",
sp_entity_id="urn:mace:example.com:saml:roland:sp", sp_entity_id="urn:mace:example.com:saml:roland:sp",
name_id_policy=samlp.NameIDPolicy( name_id_policy=nameid_policy,
format=saml.NAMEID_FORMAT_PERSISTENT), userid="foba0001@example.com")
userid="foba0001@example.com"))
resp_str = "%s" % resp
resp_str = base64.encodestring(resp_str) resp_str = base64.encodestring(resp_str)
@@ -274,7 +279,9 @@ class TestClient:
session_info = authn_response.session_info() session_info = authn_response.session_info()
print session_info print session_info
assert session_info["ava"] == {'mail': ['derek@nyy.mlb.com'], 'givenName': ['Derek'], 'sn': ['Jeter']} assert session_info["ava"] == {'mail': ['derek@nyy.mlb.com'],
'givenName': ['Derek'],
'surName': ['Jeter']}
assert session_info["issuer"] == IDP assert session_info["issuer"] == IDP
assert session_info["came_from"] == "http://foo.example.com/service" assert session_info["came_from"] == "http://foo.example.com/service"
response = samlp.response_from_string(authn_response.xmlstr) response = samlp.response_from_string(authn_response.xmlstr)
@@ -289,17 +296,16 @@ class TestClient:
# --- authenticate another person # --- authenticate another person
ava = { "givenName": ["Alfonson"], "surname": ["Soriano"], ava = { "givenName": ["Alfonson"], "surName": ["Soriano"],
"mail": ["alfonson@chc.mlb.com"]} "mail": ["alfonson@chc.mlb.com"]}
resp_str = "\n".join(self.server.authn_response( resp_str = "%s" % self.server.create_authn_response(
identity=ava, identity=ava,
in_response_to="id2", in_response_to="id2",
destination="http://lingon.catalogix.se:8087/", destination="http://lingon.catalogix.se:8087/",
sp_entity_id="urn:mace:example.com:saml:roland:sp", sp_entity_id="urn:mace:example.com:saml:roland:sp",
name_id_policy=samlp.NameIDPolicy( name_id_policy=nameid_policy,
format=saml.NAMEID_FORMAT_PERSISTENT), userid="also0001@example.com")
userid="also0001@example.com"))
resp_str = base64.encodestring(resp_str) resp_str = base64.encodestring(resp_str)
@@ -317,7 +323,6 @@ class TestClient:
entityid = self.client.config.entityid entityid = self.client.config.entityid
print entityid print entityid
assert entityid == "urn:mace:example.com:saml:roland:sp" assert entityid == "urn:mace:example.com:saml:roland:sp"
print self.client.config.idp
print self.client.config.metadata.idps() print self.client.config.metadata.idps()
print self.client.config.idps() print self.client.config.idps()
location = self.client._sso_location() location = self.client._sso_location()
@@ -332,10 +337,9 @@ class TestClient:
def test_authenticate(self): def test_authenticate(self):
print self.client.config.idps() print self.client.config.idps()
(sid, response) = self.client.authenticate( response = self.client.do_authenticate(
"urn:mace:example.com:saml:roland:idp", "urn:mace:example.com:saml:roland:idp",
"http://www.example.com/relay_state") "http://www.example.com/relay_state")
assert sid is not None
assert response[0] == "Location" assert response[0] == "Location"
o = urlparse(response[1]) o = urlparse(response[1])
qdict = parse_qs(o.query) qdict = parse_qs(o.query)
@@ -343,13 +347,11 @@ class TestClient:
saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0]) saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0])
print saml_request print saml_request
authnreq = samlp.authn_request_from_string(saml_request) authnreq = samlp.authn_request_from_string(saml_request)
assert authnreq.id == sid
def test_authenticate_no_args(self): def test_authenticate_no_args(self):
(sid, request) = self.client.authenticate(relay_state="http://www.example.com/relay_state") response = self.client.do_authenticate(relay_state="http://www.example.com/relay_state")
assert sid is not None assert response[0] == "Location"
assert request[0] == "Location" o = urlparse(response[1])
o = urlparse(request[1])
qdict = parse_qs(o.query) qdict = parse_qs(o.query)
assert _leq(qdict.keys(), ['SAMLRequest', 'RelayState']) assert _leq(qdict.keys(), ['SAMLRequest', 'RelayState'])
saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0]) saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0])
@@ -357,14 +359,13 @@ class TestClient:
print saml_request print saml_request
authnreq = samlp.authn_request_from_string(saml_request) authnreq = samlp.authn_request_from_string(saml_request)
print authnreq.keyswv() print authnreq.keyswv()
assert authnreq.id == sid
assert authnreq.destination == "http://localhost:8088/sso" assert authnreq.destination == "http://localhost:8088/sso"
assert authnreq.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/" assert authnreq.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
assert authnreq.provider_name == "urn:mace:example.com:saml:roland:sp" assert authnreq.provider_name == "urn:mace:example.com:saml:roland:sp"
assert authnreq.protocol_binding == BINDING_HTTP_POST assert authnreq.protocol_binding == BINDING_HTTP_REDIRECT
name_id_policy = authnreq.name_id_policy name_id_policy = authnreq.name_id_policy
assert name_id_policy.allow_create == "true" assert name_id_policy.allow_create == "false"
assert name_id_policy.format == "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" assert name_id_policy.format == NAMEID_FORMAT_PERSISTENT
issuer = authnreq.issuer issuer = authnreq.issuer
assert issuer.text == "urn:mace:example.com:saml:roland:sp" assert issuer.text == "urn:mace:example.com:saml:roland:sp"
@@ -386,7 +387,8 @@ class TestClient:
self.client.users.add_information_about_person(session_info) self.client.users.add_information_about_person(session_info)
entity_ids = self.client.users.issuers_of_info("123456") entity_ids = self.client.users.issuers_of_info("123456")
assert entity_ids == ["urn:mace:example.com:saml:roland:idp"] assert entity_ids == ["urn:mace:example.com:saml:roland:idp"]
resp = self.client.global_logout("123456", "Tired", in_a_while(minutes=5)) resp = self.client.global_logout("123456", "Tired",
in_a_while(minutes=5))
print resp print resp
assert resp assert resp
assert resp[0] # a session_id assert resp[0] # a session_id
@@ -401,7 +403,7 @@ class TestClient:
assert session_info["reason"] == "Tired" assert session_info["reason"] == "Tired"
assert session_info["operation"] == "SLO" assert session_info["operation"] == "SLO"
assert session_info["entity_ids"] == entity_ids assert session_info["entity_ids"] == entity_ids
assert session_info["sign"] == False assert session_info["sign"] == True
def test_logout_2(self): def test_logout_2(self):
""" one IdP/AA with BINDING_SOAP, can't actually send something""" """ one IdP/AA with BINDING_SOAP, can't actually send something"""
@@ -480,7 +482,7 @@ class TestClient:
assert state_info["reason"] == "Tired" assert state_info["reason"] == "Tired"
assert state_info["operation"] == "SLO" assert state_info["operation"] == "SLO"
assert state_info["entity_ids"] == entity_ids assert state_info["entity_ids"] == entity_ids
assert state_info["sign"] == False assert state_info["sign"] == True
def test_authz_decision_query(self): def test_authz_decision_query(self):
conf = config.SPConfig() conf = config.SPConfig()
@@ -503,7 +505,7 @@ class TestClient:
conf.attribute_converters, conf.attribute_converters,
policy, issuer=client._issuer()) policy, issuer=client._issuer())
adq = client.authz_decision_query_using_assertion("entity_id", adq = client.create_authz_decision_query_using_assertion("entity_id",
assertion, assertion,
"read", "read",
"http://example.com/text") "http://example.com/text")
@@ -517,11 +519,14 @@ class TestClient:
def test_request_to_discovery_service(self): def test_request_to_discovery_service(self):
disc_url = "http://example.com/saml2/idp/disc" disc_url = "http://example.com/saml2/idp/disc"
url = self.client.discovery_service_request_url(disc_url) url = discovery_service_request_url("urn:mace:example.com:saml:roland:sp",
disc_url)
print url print url
assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp" assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp"
url = self.client.discovery_service_request_url(disc_url, url = discovery_service_request_url(
self.client.config.entityid,
disc_url,
return_url= "http://example.org/saml2/sp/ds") return_url= "http://example.org/saml2/sp/ds")
print url print url
@@ -532,14 +537,14 @@ class TestClient:
params = urllib.urlencode(pdir) params = urllib.urlencode(pdir)
redirect_url = "http://example.com/saml2/sp/disc?%s" % params redirect_url = "http://example.com/saml2/sp/disc?%s" % params
entity_id = self.client.discovery_service_response(url=redirect_url) entity_id = discovery_service_response(url=redirect_url)
assert entity_id == "http://example.org/saml2/idp/sso" assert entity_id == "http://example.org/saml2/idp/sso"
pdir = {"idpID": "http://example.org/saml2/idp/sso"} pdir = {"idpID": "http://example.org/saml2/idp/sso"}
params = urllib.urlencode(pdir) params = urllib.urlencode(pdir)
redirect_url = "http://example.com/saml2/sp/disc?%s" % params redirect_url = "http://example.com/saml2/sp/disc?%s" % params
entity_id = self.client.discovery_service_response(url=redirect_url, entity_id = discovery_service_response(url=redirect_url,
returnIDParam="idpID") returnIDParam="idpID")
assert entity_id == "http://example.org/saml2/idp/sso" assert entity_id == "http://example.org/saml2/idp/sso"
@@ -559,17 +564,17 @@ class TestClient:
IDP = "urn:mace:example.com:saml:roland:idp" IDP = "urn:mace:example.com:saml:roland:idp"
ava = { "givenName": ["Derek"], "surname": ["Jeter"], ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]} "mail": ["derek@nyy.mlb.com"]}
resp_str = "\n".join(self.server.authn_response( resp_str = "%s" % self.server.create_authn_response(
identity=ava, identity=ava,
in_response_to="id1", in_response_to="id1",
destination="http://lingon.catalogix.se:8087/", destination="http://lingon.catalogix.se:8087/",
sp_entity_id="urn:mace:example.com:saml:roland:sp", sp_entity_id="urn:mace:example.com:saml:roland:sp",
name_id_policy=samlp.NameIDPolicy( name_id_policy=samlp.NameIDPolicy(
format=saml.NAMEID_FORMAT_PERSISTENT), format=saml.NAMEID_FORMAT_PERSISTENT),
userid="foba0001@example.com")) userid="foba0001@example.com")
resp_str = base64.encodestring(resp_str) resp_str = base64.encodestring(resp_str)
@@ -582,7 +587,9 @@ class TestClient:
session_info = authn_response.session_info() session_info = authn_response.session_info()
print session_info print session_info
assert session_info["ava"] == {'mail': ['derek@nyy.mlb.com'], 'givenName': ['Derek'], 'sn': ['Jeter']} assert session_info["ava"] == {'mail': ['derek@nyy.mlb.com'],
'givenName': ['Derek'],
'surName': ['Jeter']}
assert session_info["issuer"] == IDP assert session_info["issuer"] == IDP
assert session_info["came_from"] == "" assert session_info["came_from"] == ""
response = samlp.response_from_string(authn_response.xmlstr) response = samlp.response_from_string(authn_response.xmlstr)

View File

@@ -2,6 +2,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import base64 import base64
from saml2.saml import NAMEID_FORMAT_TRANSIENT
from saml2.samlp import NameIDPolicy
from s2repoze.plugins.sp import make_plugin from s2repoze.plugins.sp import make_plugin
from saml2.server import Server from saml2.server import Server
from saml2 import make_instance, samlp, saml from saml2 import make_instance, samlp, saml
@@ -31,6 +33,8 @@ ENV1 = {'SERVER_SOFTWARE': 'CherryPy/3.1.2 WSGI Server',
'HTTP_ACCEPT_LANGUAGE': 'en-us', 'HTTP_ACCEPT_LANGUAGE': 'en-us',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate'} 'HTTP_ACCEPT_ENCODING': 'gzip, deflate'}
trans_name_policy = NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT,
allow_create="true")
class TestSP(): class TestSP():
def setup_class(self): def setup_class(self):
self.sp = make_plugin("rem", saml_conf="server_conf") self.sp = make_plugin("rem", saml_conf="server_conf")
@@ -42,15 +46,14 @@ class TestSP():
def test_identify(self): def test_identify(self):
# Create a SAMLResponse # Create a SAMLResponse
ava = { "givenName": ["Derek"], "surname": ["Jeter"], ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]} "mail": ["derek@nyy.mlb.com"]}
resp_str = "\n".join(self.server.authn_response(ava, resp_str = "%s" % self.server.create_authn_response(ava, "id1",
"id1", "http://lingon.catalogix.se:8087/", "http://lingon.catalogix.se:8087/",
"urn:mace:example.com:saml:roland:sp", "urn:mace:example.com:saml:roland:sp",
samlp.NameIDPolicy(format=saml.NAMEID_FORMAT_TRANSIENT, trans_name_policy,
allow_create="true"), "foba0001@example.com")
"foba0001@example.com"))
resp_str = base64.encodestring(resp_str) resp_str = base64.encodestring(resp_str)
self.sp.outstanding_queries = {"id1":"http://www.example.com/service"} self.sp.outstanding_queries = {"id1":"http://www.example.com/service"}
@@ -60,4 +63,4 @@ class TestSP():
assert session_info["came_from"] == 'http://www.example.com/service' assert session_info["came_from"] == 'http://www.example.com/service'
assert session_info["ava"] == {'givenName': ['Derek'], assert session_info["ava"] == {'givenName': ['Derek'],
'mail': ['derek@nyy.mlb.com'], 'mail': ['derek@nyy.mlb.com'],
'sn': ['Jeter']} 'surName': ['Jeter']}

View File

@@ -186,7 +186,7 @@ def test_optional_attributes():
def test_do_sp_sso_descriptor(): def test_do_sp_sso_descriptor():
conf = SPConfig().load(SP, metadata_construction=True) conf = SPConfig().load(SP, metadata_construction=True)
spsso = metadata.do_sp_sso_descriptor(conf) spsso = metadata.do_spsso_descriptor(conf)
assert isinstance(spsso, md.SPSSODescriptor) assert isinstance(spsso, md.SPSSODescriptor)
assert _eq(spsso.keyswv(), ['authn_requests_signed', assert _eq(spsso.keyswv(), ['authn_requests_signed',
@@ -215,7 +215,7 @@ def test_do_sp_sso_descriptor_2():
SP["service"]["sp"]["discovery_response"] = "http://example.com/sp/ds" SP["service"]["sp"]["discovery_response"] = "http://example.com/sp/ds"
conf = SPConfig().load(SP, metadata_construction=True) conf = SPConfig().load(SP, metadata_construction=True)
spsso = metadata.do_sp_sso_descriptor(conf) spsso = metadata.do_spsso_descriptor(conf)
assert isinstance(spsso, md.SPSSODescriptor) assert isinstance(spsso, md.SPSSODescriptor)
print spsso.keyswv() print spsso.keyswv()
@@ -242,7 +242,7 @@ def test_entity_description():
#confd = eval(open("../tests/server.config").read()) #confd = eval(open("../tests/server.config").read())
confd = SPConfig().load_file("server_conf") confd = SPConfig().load_file("server_conf")
print confd.attribute_converters print confd.attribute_converters
entd = metadata.entity_descriptor(confd, 1) entd = metadata.entity_descriptor(confd)
assert entd is not None assert entd is not None
print entd.keyswv() print entd.keyswv()
assert _eq(entd.keyswv(), ['valid_until', 'entity_id', 'contact_person', assert _eq(entd.keyswv(), ['valid_until', 'entity_id', 'contact_person',
@@ -252,7 +252,7 @@ def test_entity_description():
def test_do_idp_sso_descriptor(): def test_do_idp_sso_descriptor():
conf = IdPConfig().load(IDP, metadata_construction=True) conf = IdPConfig().load(IDP, metadata_construction=True)
idpsso = metadata.do_idp_sso_descriptor(conf) idpsso = metadata.do_idpsso_descriptor(conf)
assert isinstance(idpsso, md.IDPSSODescriptor) assert isinstance(idpsso, md.IDPSSODescriptor)
assert _eq(idpsso.keyswv(), ['protocol_support_enumeration', assert _eq(idpsso.keyswv(), ['protocol_support_enumeration',

View File

@@ -108,7 +108,7 @@ def main(args):
if fil.endswith(".py"): if fil.endswith(".py"):
fil = fil[:-3] fil = fil[:-3]
cnf = Config().load_file(fil, metadata_construction=True) cnf = Config().load_file(fil, metadata_construction=True)
eds.append(entity_descriptor(cnf, valid_for)) eds.append(entity_descriptor(cnf))
secc = SecurityContext(xmlsec, keyfile, cert_file=pubkeyfile) secc = SecurityContext(xmlsec, keyfile, cert_file=pubkeyfile)
if entitiesid: if entitiesid:
@@ -118,7 +118,7 @@ def main(args):
else: else:
for eid in eds: for eid in eds:
if sign: if sign:
desc = sign_entity_descriptor(eid, valid_for, id, secc) desc = sign_entity_descriptor(eid, id, secc)
else: else:
desc = eid desc = eid
valid_instance(desc) valid_instance(desc)