Complete rewrite of the metadata handling package.

Switched from using httplib2 to requests.
This commit is contained in:
Roland Hedberg
2012-12-19 13:08:02 +01:00
parent f265c3b421
commit 50459d616f
36 changed files with 1443 additions and 3136 deletions

View File

@@ -282,8 +282,8 @@ class SAML2Plugin(FormPluginBase):
vorg_name = environ["myapp.vo"] vorg_name = environ["myapp.vo"]
except KeyError: except KeyError:
try: try:
vorg_name = self.saml_client.vorg.keys()[1] vorg_name = self.saml_client.vorg._name
except (IndexError, AttributeError): except AttributeError:
vorg_name = "" vorg_name = ""
logger.info("[sp.challenge] VO: %s" % vorg_name) logger.info("[sp.challenge] VO: %s" % vorg_name)

View File

@@ -539,16 +539,16 @@ class SamlBase(ExtensionContainer):
ExtensionContainer._add_members_to_element_tree(self, tree) ExtensionContainer._add_members_to_element_tree(self, tree)
def become_child_element_of(self, tree): def become_child_element_of(self, node):
""" """
Note: Only for use with classes that have a c_tag and c_namespace class Note: Only for use with classes that have a c_tag and c_namespace class
member. It is in SamlBase so that it can be inherited but it should member. It is in SamlBase so that it can be inherited but it should
not be called on instances of SamlBase. not be called on instances of SamlBase.
:param tree: The tree to which this instance should be a child :param node: The node to which this instance should be a child
""" """
new_child = self._to_element_tree() new_child = self._to_element_tree()
tree.append(new_child) node.append(new_child)
def _to_element_tree(self): def _to_element_tree(self):
""" """

View File

@@ -17,7 +17,6 @@
import logging import logging
import re import re
import sys
import xmlenc import xmlenc
from saml2 import saml from saml2 import saml
@@ -60,6 +59,19 @@ def _filter_values(vals, vlist=None, must=False):
else: else:
return res return res
def _match(attr, ava):
if attr in ava:
return attr
_la = attr.lower()
if _la in ava:
return _la
for _at in ava.keys():
if _at.lower() == _la:
return _at
return None
def filter_on_attributes(ava, required=None, optional=None): def filter_on_attributes(ava, required=None, optional=None):
""" Filter """ Filter
@@ -75,38 +87,40 @@ def filter_on_attributes(ava, required=None, optional=None):
if required is None: if required is None:
required = [] required = []
for attr in required: for attr in required:
if attr.friendly_name in ava: found = False
values = [av.text for av in attr.attribute_value] for nform in ["friendly_name", "name"]:
res[attr.friendly_name] = _filter_values(ava[attr.friendly_name], if nform in attr :
values, True) _fn = _match(attr[nform], ava)
elif attr.name in ava: if _fn:
values = [av.text for av in attr.attribute_value] try:
res[attr.name] = _filter_values(ava[attr.name], values, True) values = [av["text"] for av in attr["attribute_value"]]
else: except KeyError:
_name = attr.friendly_name or attr.name values = []
print >> sys.stderr, ava.keys() res[_fn] = _filter_values(ava[_fn], values, True)
raise MissingValue("Required attribute missing: '%s'" % (_name,)) found = True
break
if not found:
raise MissingValue("Required attribute missing: '%s'" % (attr[nform],))
if optional is None: if optional is None:
optional = [] optional = []
for attr in optional: for attr in optional:
if attr.friendly_name in ava: for nform in ["friendly_name", "name"]:
values = [av.text for av in attr.attribute_value] if nform in attr :
try: _fn = _match(attr[nform], ava)
res[attr.friendly_name].extend(_filter_values(ava[attr.friendly_name], if _fn:
values)) try:
except KeyError: values = [av["text"] for av in attr["attribute_value"]]
res[attr.friendly_name] = _filter_values(ava[attr.friendly_name], except KeyError:
values) values = []
elif attr.name in ava: try:
values = [av.text for av in attr.attribute_value] res[_fn].extend(_filter_values(ava[_fn],values))
try: except KeyError:
res[attr.name].extend(_filter_values(ava[attr.name], values)) res[_fn] = _filter_values(ava[_fn],values)
except KeyError:
res[attr.name] = _filter_values(ava[attr.name], values)
return res return res
@@ -123,12 +137,15 @@ def filter_on_demands(ava, required=None, optional=None):
# Is all what's required there: # Is all what's required there:
if required is None: if required is None:
required = {} required = {}
lava = dict([(k.lower(), k) for k in ava.keys()])
for attr, vals in required.items(): for attr, vals in required.items():
if attr in ava: attr = attr.lower()
if attr in lava:
if vals: if vals:
for val in vals: for val in vals:
if val not in ava[attr]: if val not in ava[lava[attr]]:
raise MissingValue( raise MissingValue(
"Required attribute value missing: %s,%s" % (attr, "Required attribute value missing: %s,%s" % (attr,
val)) val))
@@ -137,12 +154,15 @@ def filter_on_demands(ava, required=None, optional=None):
if optional is None: if optional is None:
optional = {} optional = {}
oka = [k.lower() for k in required.keys()]
oka.extend([k.lower() for k in optional.keys()])
# OK, so I can imaging releasing values that are not absolutely necessary # OK, so I can imaging releasing values that are not absolutely necessary
# but not attributes # but not attributes that are not asked for.
for attr, vals in ava.items(): for attr in lava.keys():
if attr not in required and attr not in optional: if attr not in oka:
del ava[attr] del ava[lava[attr]]
return ava return ava
@@ -383,12 +403,14 @@ 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_requirement(sp_entity_id) spec = metadata.attribute_requirement(sp_entity_id)
else: if spec:
required = optional = None return self.filter(ava, sp_entity_id, spec["required"],
spec["optional"])
return self.filter(ava, sp_entity_id, [], [])
return self.filter(ava, sp_entity_id, required, optional)
def conditions(self, sp_entity_id): def conditions(self, sp_entity_id):
""" Return a saml.Condition instance """ Return a saml.Condition instance
@@ -510,4 +532,6 @@ class Assertion(dict):
:param metadata: Metadata to use :param metadata: Metadata to use
:return: The resulting AVA after the policy is applied :return: The resulting AVA after the policy is applied
""" """
return policy.restrict(self, sp_entity_id, metadata) ava = policy.restrict(self, sp_entity_id, metadata)
self.update(ava)
return ava

View File

@@ -155,7 +155,24 @@ def to_local_name(acs, attr):
return lattr return lattr
return attr.friendly_name return attr.friendly_name
def d_to_local_name(acs, attr):
"""
:param acs: List of AttributeConverter instances
:param attr: an Attribute dictionary
:return: The local attribute name
"""
for aconv in acs:
lattr = aconv.d_from_format(attr)
if lattr:
return lattr
# if everything else fails this might be good enough
try:
return attr["friendly_name"]
except KeyError:
raise Exception("Could not find local name for %s" % attr)
class AttributeConverter(object): class AttributeConverter(object):
""" Converts from an attribute statement to a key,value dictionary and """ Converts from an attribute statement to a key,value dictionary and
vice-versa """ vice-versa """
@@ -165,28 +182,15 @@ class AttributeConverter(object):
self._to = None self._to = None
self._fro = None self._fro = None
# def set(self, name, filename):
# if name == "to":
# self.set_to(filename)
# elif name == "fro":
# self.set_fro(filename)
# # else ignore
#
# def set_fro(self, filename):
# self._fro = eval(open(filename).read())
#
# def set_to(self, filename):
# self._to = eval(open(filename).read())
#
def adjust(self): def adjust(self):
""" If one of the transformations is not defined it is expected to """ If one of the transformations is not defined it is expected to
be the mirror image of the other. be the mirror image of the other.
""" """
if self._fro is None and self._to is not None: if self._fro is None and self._to is not None:
self._fro = dict([(value, key) for key, value in self._to.items()]) self._fro = dict([(value.lower(), key) for key, value in self._to.items()])
if self._to is None and self.fro is not None: if self._to is None and self.fro is not None:
self._to = dict([(value, key) for key, value in self._fro.items()]) self._to = dict([(value.lower, key) for key, value in self._fro.items()])
def from_dict(self, mapdict): def from_dict(self, mapdict):
""" Import the attribute map from a dictionary """ Import the attribute map from a dictionary
@@ -196,11 +200,11 @@ class AttributeConverter(object):
self.name_format = mapdict["identifier"] self.name_format = mapdict["identifier"]
try: try:
self._fro = mapdict["fro"] self._fro = dict([(k.lower(),v) for k,v in mapdict["fro"].items()])
except KeyError: except KeyError:
pass pass
try: try:
self._to = mapdict["to"] self._to = dict([(k.lower(),v) for k,v in mapdict["to"].items()])
except KeyError: except KeyError:
pass pass
@@ -230,12 +234,12 @@ class AttributeConverter(object):
def ava_from(self, attribute): def ava_from(self, attribute):
try: try:
attr = self._fro[attribute.name.strip()] attr = self._fro[attribute.name.strip().lower()]
except (AttributeError, KeyError): except (AttributeError, KeyError):
try: try:
attr = attribute.friendly_name.strip() attr = attribute.friendly_name.strip().lower()
except AttributeError: except AttributeError:
attr = attribute.name.strip() attr = attribute.name.strip().lower()
val = [] val = []
for value in attribute.attribute_value: for value in attribute.attribute_value:
@@ -306,17 +310,37 @@ class AttributeConverter(object):
if attr.name_format: if attr.name_format:
if self.name_format == attr.name_format: if self.name_format == attr.name_format:
try: try:
return self._fro[attr.name] return self._fro[attr.name.lower()]
except KeyError: except KeyError:
pass pass
else: #don't know the name format so try all I have else: #don't know the name format so try all I have
try: try:
return self._fro[attr.name] return self._fro[attr.name.lower()]
except KeyError: except KeyError:
pass pass
return "" return ""
def d_from_format(self, attr):
""" Find out the local name of an attribute
:param attr: An Attribute dictionary
:return: The local attribute name or "" if no mapping could be made
"""
if attr["name_format"]:
if self.name_format == attr["name_format"]:
try:
return self._fro[attr["name"].lower()]
except KeyError:
pass
else: #don't know the name format so try all I have
try:
return self._fro[attr["name"].lower()]
except KeyError:
pass
return ""
def to_(self, attrvals): def to_(self, attrvals):
""" Create a list of Attribute instances. """ Create a list of Attribute instances.
@@ -325,6 +349,7 @@ class AttributeConverter(object):
""" """
attributes = [] attributes = []
for key, value in attrvals.items(): for key, value in attrvals.items():
key = key.lower()
try: try:
attributes.append(factory(saml.Attribute, attributes.append(factory(saml.Attribute,
name=self._to[key], name=self._to[key],

View File

@@ -50,7 +50,7 @@ class AttributeResolver(object):
""" """
result = [] result = []
for member in vo_members: for member in vo_members:
for ass in self.metadata.attribute_services(member): for ass in self.metadata.attribute_consuming_service(member):
for attr_serv in ass.attribute_service: for attr_serv in ass.attribute_service:
logger.info( logger.info(
"Send attribute request to %s" % attr_serv.location) "Send attribute request to %s" % attr_serv.location)

View File

@@ -18,9 +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.
""" """
import saml2 import saml2
from saml2.saml import AssertionIDRef, NAMEID_FORMAT_PERSISTENT
try: try:
from urlparse import parse_qs from urlparse import parse_qs
@@ -29,17 +27,17 @@ except ImportError:
from cgi import parse_qs from cgi import parse_qs
from saml2.time_util import not_on_or_after from saml2.time_util import not_on_or_after
from saml2.s_utils import decode_base64_and_inflate
from saml2 import samlp
from saml2 import saml from saml2 import saml
from saml2 import class_name from saml2 import class_name
from saml2.saml import AssertionIDRef
from saml2.saml import NAMEID_FORMAT_PERSISTENT
from saml2.sigver import pre_signature_part from saml2.sigver import pre_signature_part
from saml2.sigver import signed_instance_factory from saml2.sigver import signed_instance_factory
from saml2.binding import send_using_soap from saml2.client_base import Base
from saml2.binding import http_redirect_message from saml2.client_base import LogoutError
from saml2.binding import http_post_message from saml2.client_base import NoServiceDefined
from saml2.client_base import Base, LogoutError from saml2.mdstore import destinations
from saml2 import BINDING_HTTP_REDIRECT from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_POST from saml2 import BINDING_HTTP_POST
@@ -79,16 +77,13 @@ class Saml2Client(Base):
logger.info("AuthNReq: %s" % _req_str) logger.info("AuthNReq: %s" % _req_str)
if binding == saml2.BINDING_HTTP_POST: if binding == saml2.BINDING_HTTP_POST:
# No valid ticket; Send a form to the client
# THIS IS NOT TO BE USED RIGHT NOW
logger.info("HTTP POST") logger.info("HTTP POST")
(head, response) = http_post_message(_req_str, location, response = self.send_using_http_post(_req_str, location,
relay_state) relay_state)
elif binding == saml2.BINDING_HTTP_REDIRECT: elif binding == saml2.BINDING_HTTP_REDIRECT:
logger.info("HTTP REDIRECT") logger.info("HTTP REDIRECT")
(head, _body) = http_redirect_message(_req_str, location, response = self.send_using_http_get(_req_str, location,
relay_state) relay_state)
response = head[0]
else: else:
raise Exception("Unknown binding type: %s" % binding) raise Exception("Unknown binding type: %s" % binding)
@@ -123,7 +118,7 @@ class Saml2Client(Base):
""" """
:param subject_id: Identifier of the Subject :param subject_id: Identifier of the Subject
:param entity_ids: Entity_ids for the IdPs that have provided :param entity_ids: List of entity ids for the IdPs that have provided
information concerning the subject information concerning the subject
:param reason: The reason for doing the logout :param reason: The reason for doing the logout
:param expire: Try to logout before this time. :param expire: Try to logout before this time.
@@ -138,19 +133,19 @@ class Saml2Client(Base):
# for all where I can use the SOAP binding, do those first # for all where I can use the SOAP binding, do those first
not_done = entity_ids[:] not_done = entity_ids[:]
response = False responses = {}
for entity_id in entity_ids: for entity_id in entity_ids:
response = False response = False
for binding in [BINDING_SOAP, BINDING_HTTP_POST, for binding in [BINDING_SOAP, BINDING_HTTP_POST,
BINDING_HTTP_REDIRECT]: BINDING_HTTP_REDIRECT]:
destinations = self.config.single_logout_services(entity_id, srvs = self.metadata.single_logout_service(entity_id, "idpsso",
binding) binding=binding)
if not destinations: if not srvs:
continue continue
destination = destinations[0] destination = destinations(srvs)[0]
logger.info("destination to provider: %s" % destination) logger.info("destination to provider: %s" % destination)
request = self.create_logout_request(subject_id, request = self.create_logout_request(subject_id,
@@ -170,20 +165,18 @@ class Saml2Client(Base):
logger.info("REQUEST: %s" % request) logger.info("REQUEST: %s" % request)
request = signed_instance_factory(request, self.sec, to_sign) srequest = signed_instance_factory(request, self.sec, to_sign)
if binding == BINDING_SOAP: if binding == BINDING_SOAP:
response = send_using_soap(request, destination, response = self.send_using_soap(srequest, destination)
self.config.key_file,
self.config.cert_file,
ca_certs=self.config.ca_certs)
if response: if response:
logger.info("Verifying response") logger.info("Verifying response")
response = self.logout_response(response) response = self.logout_request_response(response)
if response: if response:
not_done.remove(entity_id) not_done.remove(entity_id)
logger.info("OK response from %s" % destination) logger.info("OK response from %s" % destination)
responses[entity_id] = response
else: else:
logger.info( logger.info(
"NOT OK response from %s" % destination) "NOT OK response from %s" % destination)
@@ -202,23 +195,27 @@ class Saml2Client(Base):
if binding == BINDING_HTTP_POST: if binding == BINDING_HTTP_POST:
(head, body) = http_post_message(request, response = self.send_using_http_post(srequest,
destination, destination,
rstate) rstate)
code = "200 OK"
else: else:
(head, body) = http_redirect_message(request, response = self.send_using_http_get(srequest,
destination, destination,
rstate) rstate)
code = "302 Found"
if response:
return session_id, code, head, body not_done.remove(entity_id)
logger.info("OK response from %s" % destination)
responses[entity_id] = response
else:
logger.info(
"NOT OK response from %s" % destination)
if not_done: if not_done:
# upstream should try later # upstream should try later
raise LogoutError("%s" % (entity_ids,)) raise LogoutError("%s" % (entity_ids,))
return 0, "", [], response return responses
def local_logout(self, subject_id): def local_logout(self, subject_id):
""" Remove the user from the cache, equals local logout """ Remove the user from the cache, equals local logout
@@ -251,65 +248,66 @@ class Saml2Client(Base):
status["reason"], status["not_on_or_after"], status["reason"], status["not_on_or_after"],
status["sign"]) status["sign"])
def do_http_redirect_logout(self, get, subject_id): # def do_http_redirect_logout(self, get, subject_id):
""" Deal with a LogoutRequest received through HTTP redirect # """ Deal with a LogoutRequest received through HTTP redirect
# !! DON'T USE, NOT WORKING !!
:param get: The request as a dictionary #
:param subject_id: the id of the current logged user # :param get: The request as a dictionary
:return: a tuple with a list of header tuples (presently only location) # :param subject_id: the id of the current logged user
and a status which will be True in case of success or False # :return: a tuple with a list of header tuples (presently only location)
otherwise. # and a status which will be True in case of success or False
""" # otherwise.
headers = [] # """
success = False # headers = []
# success = False
try: #
saml_request = get['SAMLRequest'] # try:
except KeyError: # saml_request = get['SAMLRequest']
return None # except KeyError:
# return None
if saml_request: #
xml = decode_base64_and_inflate(saml_request) # if saml_request:
# xml = decode_base64_and_inflate(saml_request)
request = samlp.logout_request_from_string(xml) #
logger.debug(request) # request = samlp.logout_request_from_string(xml)
# logger.debug(request)
if request.name_id.text == subject_id: #
status = samlp.STATUS_SUCCESS # if request.name_id.text == subject_id:
success = self.local_logout(subject_id) # status = samlp.STATUS_SUCCESS
else: # success = self.local_logout(subject_id)
status = samlp.STATUS_REQUEST_DENIED # else:
# status = samlp.STATUS_REQUEST_DENIED
destination, (id, response) = self.create_logout_response( #
request.issuer.text, # destination, (id, response) = self.create_logout_response(
request.id, # request.issuer.text,
status) # request.id,
# status)
logger.info("RESPONSE: {0:>s}".format(response)) #
# logger.info("RESPONSE: {0:>s}".format(response))
if 'RelayState' in get: #
rstate = get['RelayState'] # if 'RelayState' in get:
else: # rstate = get['RelayState']
rstate = "" # else:
# rstate = ""
(headers, _body) = http_redirect_message(str(response), #
destination, # (headers, _body) = http_redirect_message(str(response),
rstate, 'SAMLResponse') # destination,
# rstate, 'SAMLResponse')
return headers, success #
# return headers, success
def handle_logout_request(self, request, subject_id, #
binding=BINDING_HTTP_REDIRECT): # def handle_logout_request(self, request, subject_id,
""" Deal with a LogoutRequest # binding=BINDING_HTTP_REDIRECT):
# """ Deal with a LogoutRequest
:param request: The request. The format depends on which binding is #
used. # :param request: The request. The format depends on which binding is
:param subject_id: the id of the current logged user # used.
:return: What is returned also depends on which binding is used. # :param subject_id: the id of the current logged user
""" # :return: What is returned also depends on which binding is used.
# """
if binding == BINDING_HTTP_REDIRECT: #
return self.do_http_redirect_logout(request, subject_id) # if binding == BINDING_HTTP_REDIRECT:
# return self.do_http_redirect_logout(request, subject_id)
# MUST use SOAP for # MUST use SOAP for
# AssertionIDRequest, SubjectQuery, # AssertionIDRequest, SubjectQuery,
@@ -326,10 +324,7 @@ class Saml2Client(Base):
query = _create_func(destination, **kwargs) query = _create_func(destination, **kwargs)
response = send_using_soap(query, destination, response = self.send_using_soap(query, destination)
self.config.key_file,
self.config.cert_file,
ca_certs=self.config.ca_certs)
if response: if response:
logger.info("Verifying response") logger.info("Verifying response")
@@ -361,11 +356,11 @@ class Saml2Client(Base):
sp_name_qualifier=sp_name_qualifier, sp_name_qualifier=sp_name_qualifier,
name_qualifier=name_qualifier)) name_qualifier=name_qualifier))
for destination in self.config.authz_service_endpoints(entity_id, srvs = self.metadata.authz_service(entity_id, BINDING_SOAP)
BINDING_SOAP): for dest in destinations(srvs):
resp = self.use_soap(destination, "authz_decision_query", resp = self.use_soap(dest, "authz_decision_query",
action=action, evidence=evidence, action=action, evidence=evidence,
resource=resource, subject=subject) resource=resource, subject=subject)
if resp: if resp:
return resp return resp
@@ -374,26 +369,39 @@ class Saml2Client(Base):
def do_assertion_id_request(self, assertion_ids, entity_id, def do_assertion_id_request(self, assertion_ids, entity_id,
consent=None, extensions=None, sign=False): consent=None, extensions=None, sign=False):
destination = self.metadata.assertion_id_request_service(entity_id, srvs = self.metadata.assertion_id_request_service(entity_id,
BINDING_SOAP)[0] BINDING_SOAP)
if not srvs:
raise NoServiceDefined("%s: %s" % (entity_id,
"assertion_id_request_service"))
if isinstance(assertion_ids, basestring): if isinstance(assertion_ids, basestring):
assertion_ids = [assertion_ids] assertion_ids = [assertion_ids]
_id_refs = [AssertionIDRef(_id) for _id in assertion_ids] _id_refs = [AssertionIDRef(_id) for _id in assertion_ids]
return self.use_soap(destination, "assertion_id_request", for destination in destinations(srvs):
assertion_id_refs=_id_refs, consent=consent, res = self.use_soap(destination, "assertion_id_request",
extensions=extensions, sign=sign) assertion_id_refs=_id_refs, consent=consent,
extensions=extensions, sign=sign)
if res:
return res
return None
def do_authn_query(self, entity_id, def do_authn_query(self, entity_id,
consent=None, extensions=None, sign=False): consent=None, extensions=None, sign=False):
destination = self.metadata.authn_request_service(entity_id, srvs = self.metadata.authn_request_service(entity_id, BINDING_SOAP)
BINDING_SOAP)[0]
return self.use_soap(destination, "authn_query", for destination in destinations(srvs):
consent=consent, extensions=extensions, sign=sign) resp = self.use_soap(destination, "authn_query",
consent=consent, extensions=extensions,
sign=sign)
if resp:
return resp
return None
def do_attribute_query(self, entityid, subject_id, def do_attribute_query(self, entityid, subject_id,
attribute=None, sp_name_qualifier=None, attribute=None, sp_name_qualifier=None,

View File

@@ -18,6 +18,9 @@
"""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 saml2.httpbase import HTTPBase
from saml2.mdstore import destinations
from saml2.saml import AssertionIDRef, NAMEID_FORMAT_TRANSIENT from saml2.saml import AssertionIDRef, NAMEID_FORMAT_TRANSIENT
from saml2.samlp import AuthnQuery from saml2.samlp import AuthnQuery
from saml2.samlp import LogoutRequest from saml2.samlp import LogoutRequest
@@ -82,7 +85,10 @@ class VerifyError(Exception):
class LogoutError(Exception): class LogoutError(Exception):
pass pass
class Base(object): class NoServiceDefined(Exception):
pass
class Base(HTTPBase):
""" The basic pySAML2 service provider class """ """ The basic pySAML2 service provider class """
def __init__(self, config=None, identity_cache=None, state_cache=None, def __init__(self, config=None, identity_cache=None, state_cache=None,
@@ -109,6 +115,10 @@ class Base(object):
else: else:
raise Exception("Missing configuration") raise Exception("Missing configuration")
HTTPBase.__init__(self, self.config.verify_ssl_cert,
self.config.ca_certs, self.config.key_file,
self.config.cert_file)
if self.config.vorg: if self.config.vorg:
for vo in self.config.vorg.values(): for vo in self.config.vorg.values():
vo.sp = self vo.sp = self
@@ -129,7 +139,7 @@ class Base(object):
elif isinstance(virtual_organization, VirtualOrg): elif isinstance(virtual_organization, VirtualOrg):
self.vorg = virtual_organization self.vorg = virtual_organization
else: else:
self.vorg = {} self.vorg = None
for foo in ["allow_unsolicited", "authn_requests_signed", for foo in ["allow_unsolicited", "authn_requests_signed",
"logout_requests_signed"]: "logout_requests_signed"]:
@@ -170,23 +180,22 @@ class Base(object):
def _sso_location(self, entityid=None, binding=BINDING_HTTP_REDIRECT): def _sso_location(self, entityid=None, binding=BINDING_HTTP_REDIRECT):
if entityid: if entityid:
# verify that it's in the metadata # verify that it's in the metadata
try: srvs = self.metadata.single_sign_on_service(entityid, binding)
return self.config.single_sign_on_services(entityid, binding)[0] if srvs:
except IndexError: return destinations(srvs)[0]
logger.info("_sso_location: %s, %s" % (entityid, else:
binding)) logger.info("_sso_location: %s, %s" % (entityid, binding))
raise IdpUnspecified("No IdP to send to given the premises") raise IdpUnspecified("No IdP to send to given the premises")
# get the idp location from the configuration alternative the # get the idp location from the metadata. If there is more than one
# metadata. If there is more than one IdP in the configuration # IdP in the configuration raise exception
# raise exception eids = self.metadata.with_descriptor("idpsso")
eids = self.config.idps()
if len(eids) > 1: if len(eids) > 1:
raise IdpUnspecified("Too many IdPs to choose from: %s" % eids) raise IdpUnspecified("Too many IdPs to choose from: %s" % eids)
try: try:
loc = self.config.single_sign_on_services(eids.keys()[0], srvs = self.metadata.single_sign_on_service(eids.keys()[0], binding)
binding)[0] return destinations(srvs)[0]
return loc
except IndexError: except IndexError:
raise IdpUnspecified("No IdP to send to given the premises") raise IdpUnspecified("No IdP to send to given the premises")
@@ -424,8 +433,9 @@ class Base(object):
:return: A LogoutResponse instance :return: A LogoutResponse instance
""" """
destination = self.config.single_logout_services(idp_entity_id, srvs = self.metadata.single_logout_services(idp_entity_id, "idpsso",
binding)[0] binding=binding)
destination = destinations(srvs)[0]
status = samlp.Status( status = samlp.Status(
status_code=samlp.StatusCode(value=status_code)) status_code=samlp.StatusCode(value=status_code))

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
from saml2.virtual_org import VirtualOrg
__author__ = 'rolandh' __author__ = 'rolandh'
@@ -11,19 +10,42 @@ import logging.handlers
from importlib import import_module from importlib import import_module
from saml2 import BINDING_SOAP, BINDING_HTTP_REDIRECT, BINDING_HTTP_POST
from saml2 import metadata
from saml2 import root_logger from saml2 import root_logger
from saml2.attribute_converter import ac_factory from saml2.attribute_converter import ac_factory
from saml2.assertion import Policy from saml2.assertion import Policy
from saml2.sigver import get_xmlsec_binary from saml2.sigver import get_xmlsec_binary
from saml2.mdstore import MetadataStore
from saml2.virtual_org import VirtualOrg
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from saml2 import md
from saml2 import saml
from saml2.extension import mdui
from saml2.extension import idpdisc
from saml2.extension import dri
from saml2.extension import mdattr
from saml2.extension import ui
import xmldsig
import xmlenc
ONTS = {
saml.NAMESPACE: saml,
mdui.NAMESPACE: mdui,
mdattr.NAMESPACE: mdattr,
dri.NAMESPACE: dri,
ui.NAMESPACE: ui,
idpdisc.NAMESPACE: idpdisc,
md.NAMESPACE: md,
xmldsig.NAMESPACE: xmldsig,
xmlenc.NAMESPACE: xmlenc
}
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", "valid_for", "description", "valid_for", "verify_ssl_cert",
"organization", "organization",
"contact_person", "contact_person",
"name_form", "name_form",
@@ -107,6 +129,7 @@ class Config(object):
self.accepted_time_diff=None self.accepted_time_diff=None
self.name=None self.name=None
self.ca_certs=None self.ca_certs=None
self.verify_ssl_cert = False
self.description=None self.description=None
self.valid_for=None self.valid_for=None
self.organization=None self.organization=None
@@ -254,28 +277,16 @@ class Config(object):
except: except:
ca_certs = None ca_certs = None
try: try:
disable_ssl_certificate_validation = self.disable_ssl_certificate_validation disable_validation = self.disable_ssl_certificate_validation
except: except:
disable_ssl_certificate_validation = False disable_validation = False
metad = metadata.MetaData(xmlsec_binary, acs, ca_certs, mds = MetadataStore(ONTS.values(), acs, xmlsec_binary, ca_certs,
disable_ssl_certificate_validation) disable_ssl_certificate_validation=disable_validation)
if "local" in metadata_conf:
for mdfile in metadata_conf["local"]: mds.imp(metadata_conf)
metad.import_metadata(open(mdfile).read(), mdfile)
if "inline" in metadata_conf: return mds
index = 1
for md in metadata_conf["inline"]:
metad.import_metadata(md, "inline_xml.%d" % index)
index += 1
if "remote" in metadata_conf:
for spec in metadata_conf["remote"]:
try:
cert = spec["cert"]
except KeyError:
cert = None
metad.import_external_metadata(spec["url"], cert)
return metad
def endpoint(self, service, binding=None, context=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
@@ -361,73 +372,12 @@ class Config(object):
root_logger.info("Logging started") root_logger.info("Logging started")
return root_logger return root_logger
def single_logout_services(self, entity_id, binding=BINDING_SOAP):
""" returns a list of endpoints to use for sending logout requests to
:param entity_id: The entity ID of the service
:param binding: The preferred binding (which for logout by default is
the SOAP binding)
:return: list of endpoints
"""
return self.metadata.single_logout_service(entity_id, binding=binding)
class SPConfig(Config): class SPConfig(Config):
def_context = "sp" def_context = "sp"
def __init__(self): def __init__(self):
Config.__init__(self) Config.__init__(self)
def single_sign_on_services(self, entity_id,
binding=BINDING_HTTP_REDIRECT):
""" returns a list of endpoints to use for sending login requests to
:param entity_id: The entity ID of the service
:param binding: The preferred binding
:return: list of endpoints
"""
return self.metadata.single_sign_on_service(entity_id, binding=binding)
def attribute_services(self, entity_id, binding=BINDING_SOAP):
""" returns a list of endpoints to use for attribute requests to
:param entity_id: The entity ID of the service
:param binding: The preferred binding (which for logout by default is
the SOAP binding)
:return: list of endpoints
"""
res = []
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 attrserv in aad.attribute_service:
if attrserv.binding == binding:
res.append(attrserv)
else:
return self.metadata.attribute_authority()
return res
def idps(self, langpref=None):
""" Returns a dictionary of useful IdPs, the keys being the
entity ID of the service and the names of the services as values
:param langpref: The preferred languages of the name, the first match
is used.
:return: Dictionary
"""
if langpref is None:
langpref = ["en"]
eidp = self.getattr("entity_id")
if eidp:
return dict([(e, nd[0]) for (e,
nd) in self.metadata.idps(langpref).items() if e in eidp])
else:
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:
return self.virtual_organization[vo_name] return self.virtual_organization[vo_name]
@@ -455,12 +405,6 @@ class IdPConfig(Config):
def __init__(self): def __init__(self):
Config.__init__(self) Config.__init__(self)
def assertion_consumer_services(self, entity_id, binding=BINDING_HTTP_POST):
return self.metadata.assertion_consumer_services(entity_id, binding)
def authz_services(self, entity_id, binding=BINDING_SOAP):
return self.metadata.authz_service_endpoints(entity_id, binding=binding)
def config_factory(typ, file): def config_factory(typ, file):
if typ == "sp": if typ == "sp":
conf = SPConfig().load_file(file) conf = SPConfig().load_file(file)

View File

@@ -1,152 +0,0 @@
# ========================================================================
# Copyright (c) 2007, Metaweb Technologies, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY METAWEB TECHNOLOGIES AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL METAWEB
# TECHNOLOGIES OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ========================================================================
#
#
# httplib2cookie.py allows you to use python's standard
# CookieJar class with httplib2.
#
#
import logging
import re
import cookielib
from httplib2 import Http
import urllib
import urllib2
logger = logging.getLogger(__name__)
class DummyRequest(object):
"""Simulated urllib2.Request object for httplib2
implements only what's necessary for cookielib.CookieJar to work
"""
def __init__(self, url, headers=None):
self.url = url
self.headers = headers
self.origin_req_host = urllib2.request_host(self)
self.type, r = urllib.splittype(url)
self.host, r = urllib.splithost(r)
if self.host:
self.host = urllib.unquote(self.host)
def get_full_url(self):
return self.url
def get_origin_req_host(self):
# TODO to match urllib2 this should be different for redirects
return self.origin_req_host
def get_type(self):
return self.type
def get_host(self):
return self.host
def get_header(self, key, default=None):
return self.headers.get(key.lower(), default)
def has_header(self, key):
return key in self.headers
def add_unredirected_header(self, key, val):
# TODO this header should not be sent on redirect
self.headers[key.lower()] = val
def is_unverifiable(self):
# TODO to match urllib2, this should be set to True when the
# request is the result of a redirect
return False
class DummyResponse(object):
"""Simulated urllib2.Request object for httplib2
implements only what's necessary for cookielib.CookieJar to work
"""
def __init__(self, response):
self.response = response
def info(self):
return DummyMessage(self.response)
class DummyMessage(object):
"""Simulated mimetools.Message object for httplib2
implements only what's necessary for cookielib.CookieJar to work
"""
def __init__(self, response):
self.response = response
def getheaders(self, k):
k = k.lower()
v = self.response.get(k.lower(), None)
if k not in self.response:
return []
#return self.response[k].split(re.compile(',\\s*'))
# httplib2 joins multiple values for the same header
# using ','. but the netscape cookie format uses ','
# as part of the expires= date format. so we have
# to split carefully here - header.split(',') won't do it.
HEADERVAL= re.compile(r'\s*(([^,]|(,\s*\d))+)')
return [h[0] for h in HEADERVAL.findall(self.response[k])]
class CookiefulHttp(Http):
"""Subclass of httplib2.Http that keeps cookie state
constructor takes an optional cookiejar=cookielib.CookieJar
currently this does not handle redirects completely correctly:
if the server redirects to a different host the original
cookies will still be sent to that host.
"""
def __init__(self, cookiejar=None, **kws):
# note that httplib2.Http is not a new-style-class
Http.__init__(self, **kws)
if cookiejar is None:
cookiejar = cookielib.CookieJar()
self.cookiejar = cookiejar
def crequest(self, uri, **kws):
""" crequest so it's not messing up the 'real' request method
"""
headers = kws.pop('headers', None)
req = DummyRequest(uri, headers)
self.cookiejar.add_cookie_header(req)
headers = req.headers
(r, body) = Http.request(self, uri, headers=headers, **kws)
resp = DummyResponse(r)
self.cookiejar.extract_cookies(resp, req)
return r, body

View File

@@ -50,7 +50,7 @@ class Redirect(Response):
'</body>\n</html>' '</body>\n</html>'
_status = '302 Found' _status = '302 Found'
def __call__(self, environ, start_response): def __call__(self, environ, start_response, **kwargs):
location = self.message location = self.message
self.headers.append(('location', location)) self.headers.append(('location', location))
start_response(self.status, self.headers) start_response(self.status, self.headers)
@@ -62,7 +62,7 @@ class SeeOther(Response):
'</body>\n</html>' '</body>\n</html>'
_status = '303 See Other' _status = '303 See Other'
def __call__(self, environ, start_response): def __call__(self, environ, start_response, **kwargs):
location = self.message location = self.message
self.headers.append(('location', location)) self.headers.append(('location', location))
start_response(self.status, self.headers) start_response(self.status, self.headers)

View File

@@ -83,14 +83,14 @@ def _kwa(val, onts):
:param onts: Schemas to use in the conversion :param onts: Schemas to use in the conversion
:return: A converted dictionary :return: A converted dictionary
""" """
return dict([(k,_x(v, onts)) for k,v in val.items() if k not in EXP_SKIP]) return dict([(k,from_dict(v, onts)) for k,v in val.items() if k not in EXP_SKIP])
def _x(val, onts): def from_dict(val, onts):
""" """
Converts a dictionary into a pysaml2 metadata object Converts a dictionary into a pysaml2 object
:param val: A dictionary :param val: A dictionary
:param onts: Schemas to use in the conversion :param onts: Schemas to use in the conversion
:return: The pysaml2 metadata object :return: The pysaml2 object instance
""" """
if isinstance(val, dict): if isinstance(val, dict):
if "__class__" in val: if "__class__" in val:
@@ -115,22 +115,12 @@ def _x(val, onts):
else: else:
res = {} res = {}
for key, v in val.items(): for key, v in val.items():
res[key] = _x(v, onts) res[key] = from_dict(v, onts)
return res return res
elif isinstance(val, basestring): elif isinstance(val, basestring):
return val return val
elif isinstance(val, list): elif isinstance(val, list):
return [_x(v, onts) for v in val] return [from_dict(v, onts) for v in val]
else: else:
return val return val
def from_dict(_dict, onts):
"""
Converts a dictionary into a pysaml2 metadata object.
The import interface.
:param val: A dictionary
:param onts: Schemas to use in the conversion
:return: The pysaml2 metadata object
"""
return dict([(key, _x(val, onts)) for key, val in _dict.items()])

File diff suppressed because it is too large Load Diff

View File

@@ -28,13 +28,17 @@ import saml2
import base64 import base64
import urllib import urllib
from saml2.s_utils import deflate_and_base64_encode from saml2.s_utils import deflate_and_base64_encode
from saml2.soap import SOAPClient, HTTPClient
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
from xml.etree import cElementTree as ElementTree from xml.etree import cElementTree as ElementTree
if ElementTree.VERSION < '1.3.0':
# cElementTree has no support for register_namespace
# neither _namespace_map, thus we sacrify performance
# for correctness
from xml.etree import ElementTree
except ImportError: except ImportError:
try: try:
import cElementTree as ElementTree import cElementTree as ElementTree
@@ -48,7 +52,7 @@ FORM_SPEC = """<form method="post" action="%s">
<input type="submit" value="Submit" /> <input type="submit" value="Submit" />
</form>""" </form>"""
def http_post_message(message, location, relay_state="", typ="SAMLRequest"): def http_form_post_message(message, location, relay_state="", typ="SAMLRequest"):
"""The HTTP POST binding defines a mechanism by which SAML protocol """The HTTP POST binding defines a mechanism by which SAML protocol
messages may be transmitted within the base64-encoded content of a messages may be transmitted within the base64-encoded content of a
HTML form control. HTML form control.
@@ -73,9 +77,20 @@ def http_post_message(message, location, relay_state="", typ="SAMLRequest"):
response.append("</body>") response.append("</body>")
return [("Content-type", "text/html")], response return [("Content-type", "text/html")], response
def http_redirect_message(message, location, relay_state="", #noinspection PyUnresolvedReferences
typ="SAMLRequest"): def http_post_message(message, location, relay_state="", typ="SAMLRequest"):
"""
:param message:
:param location:
:param relay_state:
:param typ:
:return:
"""
return [("Content-type", "text/xml")], message
def http_redirect_message(message, location, relay_state="", typ="SAMLRequest"):
"""The HTTP Redirect binding defines a mechanism by which SAML protocol """The HTTP Redirect binding defines a mechanism by which SAML protocol
messages can be transmitted within URL parameters. messages can be transmitted within URL parameters.
Messages are encoded for use with this binding using a URL encoding Messages are encoded for use with this binding using a URL encoding
@@ -191,56 +206,15 @@ def parse_soap_enveloped_saml(text, body_class, header_class=None):
# #
# return response # return response
def send_using_http_post(request, destination, relay_state, key_file=None,
cert_file=None, ca_certs=""):
http = HTTPClient(destination, key_file, cert_file, ca_certs)
logger.info("HTTP client initiated")
if not isinstance(request, basestring):
request = "%s" % (request,)
(headers, message) = http_post_message(request, destination, relay_state)
try:
response = http.post(message, headers)
except Exception, exc:
logger.info("HTTPClient exception: %s" % (exc,))
return None
logger.info("HTTP request sent and got response: %s" % response)
return response
def send_using_soap(message, destination, key_file=None, cert_file=None,
ca_certs=""):
"""
Actual construction of the SOAP message is done by the SOAPClient
:param message: The SAML message to send
:param destination: Where to send the message
:param key_file: If HTTPS this is the client certificate
:param cert_file: If HTTPS this a certificates file
:param ca_certs: CA certificates to use when verifying server certificates
:return: The response gotten from the other side interpreted by the
SOAPClient
"""
soapclient = SOAPClient(destination, key_file, cert_file, ca_certs)
logger.info("SOAP client initiated")
try:
response = soapclient.send(message)
except Exception, exc:
logger.info("SoapClient exception: %s" % (exc,))
return None
logger.info("SOAP request sent and got response: %s" % response)
return response
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
PACKING = { PACKING = {
saml2.BINDING_HTTP_REDIRECT: http_redirect_message, saml2.BINDING_HTTP_REDIRECT: http_redirect_message,
saml2.BINDING_HTTP_POST: http_post_message, saml2.BINDING_HTTP_POST: http_form_post_message,
} }
def packager( identifier ): def packager( identifier ):

View File

@@ -23,6 +23,11 @@ import logging
import shelve import shelve
import sys import sys
import memcache import memcache
from saml2.pack import http_soap_message
from saml2.pack import http_redirect_message
from saml2.pack import http_post_message
from saml2.httpbase import HTTPBase
from saml2.mdstore import destinations
from saml2 import saml, BINDING_HTTP_POST from saml2 import saml, BINDING_HTTP_POST
from saml2 import class_name from saml2 import class_name
@@ -45,10 +50,6 @@ from saml2.s_utils import error_status_factory
from saml2.time_util import instant from saml2.time_util import instant
from saml2.binding import http_soap_message
from saml2.binding import http_redirect_message
from saml2.binding import http_post_message
from saml2.sigver import security_context from saml2.sigver import security_context
from saml2.sigver import signed_instance_factory from saml2.sigver import signed_instance_factory
from saml2.sigver import pre_signature_part from saml2.sigver import pre_signature_part
@@ -218,9 +219,10 @@ class Identifier(object):
except KeyError: except KeyError:
return None return None
class Server(object): class Server(HTTPBase):
""" A class that does things that IdPs or AAs do """ """ A class that does things that IdPs or AAs do """
def __init__(self, config_file="", config=None, _cache="", stype="idp"): def __init__(self, config_file="", config=None, _cache="", stype="idp"):
self.ident = None self.ident = None
if config_file: if config_file:
self.load_config(config_file, stype) self.load_config(config_file, stype)
@@ -229,6 +231,10 @@ class Server(object):
else: else:
raise Exception("Missing configuration") raise Exception("Missing configuration")
HTTPBase.__init__(self, self.conf.verify_ssl_cert,
self.conf.ca_certs, self.conf.key_file,
self.conf.cert_file)
self.conf.setup_logger() self.conf.setup_logger()
self.metadata = self.conf.metadata self.metadata = self.conf.metadata
@@ -277,7 +283,12 @@ class Server(object):
(dbspec,)) (dbspec,))
except AttributeError: except AttributeError:
self.ident = None self.ident = None
def close_shelve_db(self):
"""Close the shelve db to prevent file system locking issues"""
if self.ident:
self.ident.map.close()
def issuer(self, entityid=None): def issuer(self, entityid=None):
""" Return an Issuer precursor """ """ Return an Issuer precursor """
if entityid: if entityid:
@@ -351,12 +362,13 @@ class Server(object):
_binding = authn_request.message.protocol_binding _binding = authn_request.message.protocol_binding
try: try:
consumer_url = self.metadata.assertion_consumer_service(sp_entity_id, srvs = self.metadata.assertion_consumer_service(sp_entity_id,
binding=_binding)[0] binding=_binding)
consumer_url = destinations(srvs)[0]
except (KeyError, IndexError): except (KeyError, IndexError):
_log_info("Failed to find consumer URL for %s" % sp_entity_id) _log_info("Failed to find consumer URL for %s" % sp_entity_id)
_log_info("Binding: %s" % _binding) _log_info("Binding: %s" % _binding)
_log_info("entities: %s" % self.metadata.entity.keys()) _log_info("entities: %s" % self.metadata.keys())
raise UnknownPrincipal(sp_entity_id) raise UnknownPrincipal(sp_entity_id)
if not consumer_url: # what to do ? if not consumer_url: # what to do ?
@@ -609,8 +621,9 @@ class Server(object):
name_id = None name_id = None
try: try:
nid_formats = [] nid_formats = []
for _sp in self.metadata.entity[sp_entity_id]["spsso"]: for _sp in self.metadata[sp_entity_id]["spsso_descriptor"]:
nid_formats.extend([n.text for n in _sp.name_id_format]) if "name_id_format" in _sp:
nid_formats.extend([n.text for n in _sp["name_id_format"]])
policy = self.conf.getattr("policy", "idp") 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,
@@ -701,24 +714,24 @@ class Server(object):
sp_entity_id = request.issuer.text.strip() sp_entity_id = request.issuer.text.strip()
binding = None binding = None
destinations = [] dests = []
for binding in bindings: for binding in bindings:
destinations = self.conf.single_logout_services(sp_entity_id, srvs = self.metadata.single_logout_service(sp_entity_id, "spsso",
binding) binding=binding)
if destinations: if srvs:
dests = destinations(srvs)
break break
if not destinations: if not dests:
logger.error("No way to return a response !!!") logger.error("No way to return a response !!!")
return ("412 Precondition Failed", return ("412 Precondition Failed",
[("Content-type", "text/html")], [("Content-type", "text/html")],
["No return way defined"]) ["No return way defined"])
# Pick the first # Pick the first
destination = destinations[0] destination = dests[0]
logger.info("Logout Destination: %s, binding: %s" % (destination, logger.info("Logout Destination: %s, binding: %s" % (dests, binding))
binding))
if not status: if not status:
status = success_status_factory() status = success_status_factory()

View File

@@ -232,7 +232,14 @@ def _instance(klass, ava, seccont, base64encode=False, elements_to_sign=None):
return instance return instance
def signed_instance_factory(instance, seccont, elements_to_sign=None): def signed_instance_factory(instance, seccont, elements_to_sign=None):
"""
:param instance: The instance to be signed or not
:param seccont: The security context
:param elements_to_sign: Which parts if any that should be signed
:return: A class instance if not signed otherwise a string
"""
if elements_to_sign: if elements_to_sign:
signed_xml = "%s" % instance signed_xml = "%s" % instance
for (node_name, nodeid) in elements_to_sign: for (node_name, nodeid) in elements_to_sign:
@@ -242,7 +249,7 @@ def signed_instance_factory(instance, seccont, elements_to_sign=None):
#print "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" #print "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
#print "%s" % signed_xml #print "%s" % signed_xml
#print "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" #print "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
return create_class_from_xml_string(instance.__class__, signed_xml) return signed_xml
else: else:
return instance return instance
@@ -632,7 +639,16 @@ class SecurityContext(object):
# More trust in certs from metadata then certs in the XML document # More trust in certs from metadata then certs in the XML document
if self.metadata: if self.metadata:
certs = self.metadata.certs(issuer, "signing") try:
_certs = self.metadata.certs(issuer, "any", "signing")
except KeyError:
_certs = []
certs = []
for cert in _certs:
if isinstance(cert, basestring):
certs.append(make_temp(pem_format(cert), ".pem", False))
else:
certs.append(cert)
else: else:
certs = [] certs = []
@@ -677,7 +693,7 @@ class SecurityContext(object):
def check_signature(self, item, node_name=NODE_NAME, origdoc=None, def check_signature(self, item, node_name=NODE_NAME, origdoc=None,
id_attr=""): id_attr=""):
return self._check_signature( "%s" % (item,), item, node_name, origdoc, return self._check_signature( origdoc, item, node_name, origdoc,
id_attr=id_attr) id_attr=id_attr)
def correctly_signed_logout_request(self, decoded_xml, must=False, def correctly_signed_logout_request(self, decoded_xml, must=False,

View File

@@ -20,14 +20,9 @@ Suppport for the client part of the SAML2.0 SOAP binding.
""" """
import logging import logging
from httplib2 import Http
from saml2 import httplib2cookie
from saml2 import create_class_from_element_tree from saml2 import create_class_from_element_tree
from saml2.samlp import NAMESPACE as SAMLP_NAMESPACE from saml2.samlp import NAMESPACE as SAMLP_NAMESPACE
#from saml2 import element_to_extension_element
from saml2.schema import soapenv from saml2.schema import soapenv
from saml2 import class_name
try: try:
from xml.etree import cElementTree as ElementTree from xml.etree import cElementTree as ElementTree
@@ -44,9 +39,6 @@ logger = logging.getLogger(__name__)
class XmlParseError(Exception): class XmlParseError(Exception):
pass pass
#NAMESPACE = "http://schemas.xmlsoap.org/soap/envelope/"
def parse_soap_enveloped_saml_response(text): def parse_soap_enveloped_saml_response(text):
tags = ['{%s}Response' % SAMLP_NAMESPACE, tags = ['{%s}Response' % SAMLP_NAMESPACE,
'{%s}LogoutResponse' % SAMLP_NAMESPACE] '{%s}LogoutResponse' % SAMLP_NAMESPACE]
@@ -198,140 +190,3 @@ def soap_fault(message=None, actor=None, code=None, detail=None):
) )
return "%s" % fault return "%s" % fault
class HTTPClient(object):
""" For sending a message to a HTTP server using POST or GET """
def __init__(self, path, keyfile=None, certfile=None, cookiejar=None,
ca_certs="", disable_ssl_certificate_validation=True):
self.path = path
if cookiejar is not None:
self.cj = True
self.server = httplib2cookie.CookiefulHttp(cookiejar,
ca_certs=ca_certs,
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
else:
self.cj = False
self.server = Http(ca_certs=ca_certs,
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
self.response = None
if keyfile:
self.server.add_certificate(keyfile, certfile, "")
def post(self, data, headers=None, path=None):
if headers is None:
headers = {}
if path is None:
path = self.path
if self.cj:
(response, content) = self.server.crequest(path, method="POST",
body=data,
headers=headers)
else:
(response, content) = self.server.request(path, method="POST",
body=data,
headers=headers)
if response.status == 200 or response.status == 201:
return content
# elif response.status == 302: # redirect
# return self.post(data, headers, response["location"])
else:
self.response = response
self.error_description = content
return False
def get(self, headers=None, path=None):
if path is None:
path = self.path
if headers is None:
headers = {"content-type": "text/html"}
(response, content) = self.server.crequest(path, method="GET",
headers=headers)
if response.status == 200 or response.status == 201:
return content
# elif response.status == 302: # redirect
# return self.get(headers, response["location"])
else:
self.response = response
self.error_description = content
return None
def put(self, data, headers=None, path=None):
if headers is None:
headers = {}
if path is None:
path = self.path
(response, content) = self.server.crequest(path, method="PUT",
body=data,
headers=headers)
if response.status == 200 or response.status == 201:
return content
else:
self.response = response
self.error_description = content
return False
def delete(self, headers=None, path=None):
if headers is None:
headers = {}
if path is None:
path = self.path
(response, content) = self.server.crequest(path, method="DELETE",
headers=headers)
if response.status == 200 or response.status == 201:
return content
else:
self.response = response
self.error_description = content
return False
def add_credentials(self, name, passwd):
self.server.add_credentials(name, passwd)
def clear_credentials(self):
self.server.clear_credentials()
class SOAPClient(object):
def __init__(self, server_url, keyfile=None, certfile=None,
cookiejar=None, ca_certs="",
disable_ssl_certificate_validation=True):
self.server = HTTPClient(server_url, keyfile, certfile, cookiejar,
ca_certs=ca_certs,
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
self.response = None
def send(self, request, path=None, headers=None, sign=None, sec=None):
if headers is None:
headers = {"content-type": "application/soap+xml"}
else:
headers.update({"content-type": "application/soap+xml"})
soap_message = make_soap_enveloped_saml_thingy(request)
if sign:
_signed = sec.sign_statement_using_xmlsec(soap_message,
class_name(request),
nodeid=request.id)
soap_message = _signed
_response = self.server.post(soap_message, headers, path=path)
self.response = _response
if _response:
logger.info("SOAP response: %s" % _response)
return parse_soap_enveloped_saml_response(_response)
else:
return False
def add_credentials(self, name, passwd):
self.server.add_credentials(name, passwd)

View File

@@ -36,7 +36,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": ["sp_slo_redirect.xml"], "local": ["sp_slo_redirect.xml"],
}, },

View File

@@ -1,17 +1,74 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<ns0:EntitiesDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns1="http://www.w3.org/2000/09/xmldsig#"><ns0:EntityDescriptor entityID="urn:mace:example.com:saml:roland:sp"><ns0:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:KeyDescriptor><ns1:KeyInfo><ns1:X509Data><ns1:X509Certificate>MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV <ns0:EntitiesDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata"
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX xmlns:ns1="http://www.w3.org/2000/09/xmldsig#">
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF <ns0:EntityDescriptor entityID="urn:mace:example.com:saml:roland:sp">
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 <ns0:SPSSODescriptor AuthnRequestsSigned="false"
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB WantAssertionsSigned="true"
gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN <ns0:KeyDescriptor>
efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G <ns1:KeyInfo>
A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs <ns1:X509Data>
iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt <ns1:X509Certificate>
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
mrPzGzk3ECbupFnqyREH3+ZPSdk= ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
</ns1:X509Certificate></ns1:X509Data></ns1:KeyInfo></ns0:KeyDescriptor><ns0:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://lingon.catalogix.se:8087/" index="1" /><ns0:AttributeConsumingService index="1"><ns0:ServiceName xml:lang="en">urn:mace:example.com:saml:roland:sp</ns0:ServiceName><ns0:ServiceDescription xml:lang="en">My own SP</ns0:ServiceDescription><ns0:RequestedAttribute Name="surName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true" /><ns0:RequestedAttribute FriendlyName="givenName" Name="urn:oid:2.5.4.42" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true" /><ns0:RequestedAttribute FriendlyName="mail" Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true" /><ns0:RequestedAttribute FriendlyName="title" Name="urn:oid:2.5.4.12" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="false" /></ns0:AttributeConsumingService></ns0:SPSSODescriptor><ns0:Organization><ns0:OrganizationName xml:lang="se">AB Exempel</ns0:OrganizationName><ns0:OrganizationDisplayName xml:lang="se">AB Exempel</ns0:OrganizationDisplayName><ns0:OrganizationURL xml:lang="en">http://www.example.org</ns0:OrganizationURL></ns0:Organization><ns0:ContactPerson contactType="technical"><ns0:GivenName>Roland</ns0:GivenName><ns0:SurName>Hedberg</ns0:SurName><ns0:EmailAddress>tech@eample.com</ns0:EmailAddress><ns0:EmailAddress>tech@example.org</ns0:EmailAddress><ns0:TelephoneNumber>+46 70 100 0000</ns0:TelephoneNumber></ns0:ContactPerson></ns0:EntityDescriptor></ns0:EntitiesDescriptor> gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy
3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN
efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G
A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs
iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw
mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6
h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5
U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6
mrPzGzk3ECbupFnqyREH3+ZPSdk=
</ns1:X509Certificate>
</ns1:X509Data>
</ns1:KeyInfo>
</ns0:KeyDescriptor>
<ns0:AssertionConsumerService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://lingon.catalogix.se:8087/" index="1"/>
<ns0:AttributeConsumingService index="1">
<ns0:ServiceName xml:lang="en">
urn:mace:example.com:saml:roland:sp
</ns0:ServiceName>
<ns0:ServiceDescription xml:lang="en">My own SP
</ns0:ServiceDescription>
<ns0:RequestedAttribute FriendlyName="surName"
Name="urn:oid:2.5.4.4"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
isRequired="true"/>
<ns0:RequestedAttribute FriendlyName="givenName"
Name="urn:oid:2.5.4.42"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
isRequired="true"/>
<ns0:RequestedAttribute FriendlyName="mail"
Name="urn:oid:0.9.2342.19200300.100.1.3"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
isRequired="true"/>
<ns0:RequestedAttribute FriendlyName="title"
Name="urn:oid:2.5.4.12"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
isRequired="false"/>
</ns0:AttributeConsumingService>
</ns0:SPSSODescriptor>
<ns0:Organization>
<ns0:OrganizationName xml:lang="se">AB Exempel
</ns0:OrganizationName>
<ns0:OrganizationDisplayName xml:lang="se">AB Exempel
</ns0:OrganizationDisplayName>
<ns0:OrganizationURL xml:lang="en">http://www.example.org
</ns0:OrganizationURL>
</ns0:Organization>
<ns0:ContactPerson contactType="technical">
<ns0:GivenName>Roland</ns0:GivenName>
<ns0:SurName>Hedberg</ns0:SurName>
<ns0:EmailAddress>tech@eample.com</ns0:EmailAddress>
<ns0:EmailAddress>tech@example.org</ns0:EmailAddress>
<ns0:TelephoneNumber>+46 70 100 0000</ns0:TelephoneNumber>
</ns0:ContactPerson>
</ns0:EntityDescriptor>
</ns0:EntitiesDescriptor>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ns0:EntitiesDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" name="urn:mace:umu.se:saml:test" validUntil="2010-12-01T09:22:16Z"> <ns0:EntitiesDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" name="urn:mace:umu.se:saml:test" validUntil="2010-12-01T09:22:16Z">
<ns0:EntityDescriptor entityID="urn:mace:umu.se:saml:roland:sp" validUntil="2010-12-01T09:22:16Z"> <ns0:EntityDescriptor entityID="urn:mace:umu.se:saml:roland:sp">
<ns0:SPSSODescriptor AuthnRequestsSigned="False" WantAssertionsSigned="True" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> <ns0:SPSSODescriptor AuthnRequestsSigned="False" WantAssertionsSigned="True" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<ns0:KeyDescriptor> <ns0:KeyDescriptor>
<ns1:KeyInfo xmlns:ns1="http://www.w3.org/2000/09/xmldsig#"> <ns1:KeyInfo xmlns:ns1="http://www.w3.org/2000/09/xmldsig#">

View File

@@ -1,5 +1,5 @@
import saml2 import saml2
from saml2 import metadata from saml2 import mdstore
from saml2 import md from saml2 import md
from saml2 import BINDING_HTTP_POST from saml2 import BINDING_HTTP_POST
from saml2 import extension_elements_to_elements from saml2 import extension_elements_to_elements

View File

@@ -40,7 +40,7 @@ CONFIG = {
}, },
"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": ["sp_0.metadata"], "local": ["sp_0.metadata"],
}, },

View File

@@ -30,7 +30,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_slo_redirect.xml"], "local": ["idp_slo_redirect.xml"],
}, },

View File

@@ -1,3 +1,4 @@
from saml2.mdie import to_dict
from saml2 import md, assertion from saml2 import md, assertion
from saml2.saml import Attribute, NAME_FORMAT_URI, AttributeValue from saml2.saml import Attribute, NAME_FORMAT_URI, AttributeValue
from saml2.assertion import Policy, Assertion, filter_on_attributes from saml2.assertion import Policy, Assertion, filter_on_attributes
@@ -8,30 +9,38 @@ from saml2.attribute_converter import ac_factory
from py.test import raises from py.test import raises
from saml2.extension import mdui
from saml2.extension import idpdisc
from saml2.extension import dri
from saml2.extension import mdattr
from saml2.extension import ui
from saml2 import saml
import xmldsig
import xmlenc
ONTS = [saml, mdui, mdattr, dri, ui, idpdisc, md, xmldsig, xmlenc]
def _eq(l1,l2): def _eq(l1,l2):
return set(l1) == set(l2) return set(l1) == set(l2)
gn = md.RequestedAttribute( gn = to_dict(md.RequestedAttribute(name="urn:oid:2.5.4.42",
name="urn:oid:2.5.4.42", friendly_name="givenName",
friendly_name="givenName", name_format=NAME_FORMAT_URI),ONTS)
name_format=NAME_FORMAT_URI)
sn = md.RequestedAttribute( sn = to_dict(md.RequestedAttribute(name="urn:oid:2.5.4.4",
name="urn:oid:2.5.4.4", friendly_name="surName",
friendly_name="surName", name_format=NAME_FORMAT_URI), ONTS)
name_format=NAME_FORMAT_URI)
mail = md.RequestedAttribute( mail = to_dict(md.RequestedAttribute(name="urn:oid:0.9.2342.19200300.100.1.3",
name="urn:oid:0.9.2342.19200300.100.1.3", friendly_name="mail",
friendly_name="mail", name_format=NAME_FORMAT_URI), ONTS)
name_format=NAME_FORMAT_URI)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_filter_on_attributes_0(): def test_filter_on_attributes_0():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, a = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber") friendly_name="serialNumber"), ONTS)
required = [a] required = [a]
ava = { "serialNumber": ["12345"]} ava = { "serialNumber": ["12345"]}
@@ -40,8 +49,8 @@ def test_filter_on_attributes_0():
assert ava["serialNumber"] == ["12345"] assert ava["serialNumber"] == ["12345"]
def test_filter_on_attributes_1(): def test_filter_on_attributes_1():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, a = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber") friendly_name="serialNumber"), ONTS)
required = [a] required = [a]
ava = { "serialNumber": ["12345"], "givenName":["Lars"]} ava = { "serialNumber": ["12345"], "givenName":["Lars"]}
@@ -144,20 +153,16 @@ def test_ava_filter_2():
ava = {"givenName":"Derek", ava = {"givenName":"Derek",
"surName": "Jeter", "surName": "Jeter",
"mail":"derek@example.com"} "mail":"derek@example.com"}
# I'm filtering away something the SP deems necessary raises(Exception, policy.filter, ava, 'urn:mace:umu.se:saml:roland:sp',
[mail], [gn, sn])
#policy.filter(ava, 'urn:mace:umu.se:saml:roland:sp', [mail], [gn, sn])
raises(MissingValue, policy.filter, ava, 'urn:mace:umu.se:saml:roland:sp',
[mail], [gn, sn])
ava = {"givenName":"Derek", ava = {"givenName":"Derek",
"surName": "Jeter"} "surName": "Jeter"}
# it wasn't there to begin with # it wasn't there to begin with
raises(MissingValue, policy.filter, ava, 'urn:mace:umu.se:saml:roland:sp', raises(Exception, policy.filter, ava, 'urn:mace:umu.se:saml:roland:sp',
[gn,sn,mail]) [gn, sn, mail])
def test_filter_attribute_value_assertions_0(AVA): def test_filter_attribute_value_assertions_0(AVA):
p = Policy({ p = Policy({
@@ -291,10 +296,10 @@ def test_assertion_2():
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
def test_filter_values_req_2(): def test_filter_values_req_2():
a1 = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, a1 = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber") friendly_name="serialNumber"), ONTS)
a2 = Attribute(name="urn:oid:2.5.4.4", name_format=NAME_FORMAT_URI, a2 = to_dict(Attribute(name="urn:oid:2.5.4.4", name_format=NAME_FORMAT_URI,
friendly_name="surName") friendly_name="surName"), ONTS)
required = [a1,a2] required = [a1,a2]
ava = { "serialNumber": ["12345"], "givenName":["Lars"]} ava = { "serialNumber": ["12345"], "givenName":["Lars"]}
@@ -302,9 +307,9 @@ def test_filter_values_req_2():
raises(MissingValue, filter_on_attributes, ava, required) raises(MissingValue, filter_on_attributes, ava, required)
def test_filter_values_req_3(): def test_filter_values_req_3():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, a = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[ friendly_name="serialNumber", attribute_value=[
AttributeValue(text="12345")]) AttributeValue(text="12345")]), ONTS)
required = [a] required = [a]
ava = { "serialNumber": ["12345"]} ava = { "serialNumber": ["12345"]}
@@ -314,9 +319,9 @@ def test_filter_values_req_3():
assert ava["serialNumber"] == ["12345"] assert ava["serialNumber"] == ["12345"]
def test_filter_values_req_4(): def test_filter_values_req_4():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, a = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[ friendly_name="serialNumber", attribute_value=[
AttributeValue(text="54321")]) AttributeValue(text="54321")]), ONTS)
required = [a] required = [a]
ava = { "serialNumber": ["12345"]} ava = { "serialNumber": ["12345"]}
@@ -324,9 +329,9 @@ def test_filter_values_req_4():
raises(MissingValue, filter_on_attributes, ava, required) raises(MissingValue, filter_on_attributes, ava, required)
def test_filter_values_req_5(): def test_filter_values_req_5():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, a = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[ friendly_name="serialNumber", attribute_value=[
AttributeValue(text="12345")]) AttributeValue(text="12345")]), ONTS)
required = [a] required = [a]
ava = { "serialNumber": ["12345", "54321"]} ava = { "serialNumber": ["12345", "54321"]}
@@ -336,9 +341,9 @@ def test_filter_values_req_5():
assert ava["serialNumber"] == ["12345"] assert ava["serialNumber"] == ["12345"]
def test_filter_values_req_6(): def test_filter_values_req_6():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, a = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[ friendly_name="serialNumber", attribute_value=[
AttributeValue(text="54321")]) AttributeValue(text="54321")]),ONTS)
required = [a] required = [a]
ava = { "serialNumber": ["12345", "54321"]} ava = { "serialNumber": ["12345", "54321"]}
@@ -348,12 +353,12 @@ def test_filter_values_req_6():
assert ava["serialNumber"] == ["54321"] assert ava["serialNumber"] == ["54321"]
def test_filter_values_req_opt_0(): def test_filter_values_req_opt_0():
r = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, r = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[ friendly_name="serialNumber", attribute_value=[
AttributeValue(text="54321")]) AttributeValue(text="54321")]),ONTS)
o = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, o = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[ friendly_name="serialNumber", attribute_value=[
AttributeValue(text="12345")]) AttributeValue(text="12345")]),ONTS)
ava = { "serialNumber": ["12345", "54321"]} ava = { "serialNumber": ["12345", "54321"]}
@@ -362,13 +367,13 @@ def test_filter_values_req_opt_0():
assert _eq(ava["serialNumber"], ["12345","54321"]) assert _eq(ava["serialNumber"], ["12345","54321"])
def test_filter_values_req_opt_1(): def test_filter_values_req_opt_1():
r = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, r = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[ friendly_name="serialNumber", attribute_value=[
AttributeValue(text="54321")]) AttributeValue(text="54321")]), ONTS)
o = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI, o = to_dict(Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[ friendly_name="serialNumber", attribute_value=[
AttributeValue(text="12345"), AttributeValue(text="12345"),
AttributeValue(text="abcd0")]) AttributeValue(text="abcd0")]), ONTS)
ava = { "serialNumber": ["12345", "54321"]} ava = { "serialNumber": ["12345", "54321"]}
@@ -377,18 +382,22 @@ def test_filter_values_req_opt_1():
assert _eq(ava["serialNumber"], ["12345","54321"]) assert _eq(ava["serialNumber"], ["12345","54321"])
def test_filter_values_req_opt_2(): def test_filter_values_req_opt_2():
r = [Attribute(friendly_name="surName", r = [to_dict(Attribute(friendly_name="surName",
name="urn:oid:2.5.4.4", name="urn:oid:2.5.4.4",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"), name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"),
Attribute(friendly_name="givenName", ONTS),
to_dict(Attribute(friendly_name="givenName",
name="urn:oid:2.5.4.42", name="urn:oid:2.5.4.42",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"), name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"),
Attribute(friendly_name="mail", ONTS),
to_dict(Attribute(friendly_name="mail",
name="urn:oid:0.9.2342.19200300.100.1.3", name="urn:oid:0.9.2342.19200300.100.1.3",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri")] name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"),
o = [Attribute(friendly_name="title", ONTS)]
o = [to_dict(Attribute(friendly_name="title",
name="urn:oid:2.5.4.12", name="urn:oid:2.5.4.12",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri")] name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"),
ONTS)]
ava = { "surname":["Hedberg"], "givenName":["Roland"], ava = { "surname":["Hedberg"], "givenName":["Roland"],
@@ -399,13 +408,13 @@ def test_filter_values_req_opt_2():
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_filter_values_req_opt_4(): def test_filter_values_req_opt_4():
r = [Attribute(friendly_name="surName", r = [Attribute(friendly_name="surName",
name="urn:oid:2.5.4.4", name="urn:oid:2.5.4.4",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"), name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"),
Attribute(friendly_name="givenName", Attribute(friendly_name="givenName",
name="urn:oid:2.5.4.42", name="urn:oid:2.5.4.42",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri")] name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"),]
o = [Attribute(friendly_name="title", o = [Attribute(friendly_name="title",
name="urn:oid:2.5.4.12", name="urn:oid:2.5.4.12",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri")] name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri")]
@@ -544,22 +553,22 @@ def test_filter_ava_4():
assert _eq(ava["mail"], ["derek@nyy.mlb.com", "dj@example.com"]) assert _eq(ava["mail"], ["derek@nyy.mlb.com", "dj@example.com"])
def test_req_opt(): def test_req_opt():
req = [md.RequestedAttribute(friendly_name="surname", name="urn:oid:2.5.4.4", req = [to_dict(md.RequestedAttribute(friendly_name="surname", name="urn:oid:2.5.4.4",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri", name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
is_required="true"), is_required="true"),ONTS),
md.RequestedAttribute(friendly_name="givenname", to_dict(md.RequestedAttribute(friendly_name="givenname",
name="urn:oid:2.5.4.42", name="urn:oid:2.5.4.42",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri", name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
is_required="true"), is_required="true"),ONTS),
md.RequestedAttribute(friendly_name="edupersonaffiliation", to_dict(md.RequestedAttribute(friendly_name="edupersonaffiliation",
name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1", name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri", name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
is_required="true")] is_required="true"),ONTS)]
opt = [md.RequestedAttribute(friendly_name="title", opt = [to_dict(md.RequestedAttribute(friendly_name="title",
name="urn:oid:2.5.4.12", name="urn:oid:2.5.4.12",
name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri", name_format="urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
is_required="false")] is_required="false"), ONTS)]
policy = Policy() policy = Policy()
ava = {'givenname': 'Roland', 'surname': 'Hedberg', ava = {'givenname': 'Roland', 'surname': 'Hedberg',

View File

@@ -53,11 +53,11 @@ class TestAC():
except attribute_converter.UnknownNameFormat: except attribute_converter.UnknownNameFormat:
pass pass
print ava.keys() print ava.keys()
assert _eq(ava.keys(),['uid', 'swissEduPersonUniqueID', assert _eq(ava.keys(),['uid', 'swissedupersonuniqueid',
'swissEduPersonHomeOrganizationType', 'swissedupersonhomeorganizationtype',
'eduPersonEntitlement', 'eduPersonEntitlement', 'eduPersonAffiliation',
'eduPersonAffiliation', 'sn', 'mail', 'sn', 'mail', 'swissedupersonhomeorganization',
'swissEduPersonHomeOrganization', 'givenName']) 'givenName'])
def test_to_attrstat_1(self): def test_to_attrstat_1(self):
ava = { "givenName": "Roland", "sn": "Hedberg" } ava = { "givenName": "Roland", "sn": "Hedberg" }
@@ -74,7 +74,7 @@ class TestAC():
assert a1.friendly_name == "givenName" assert a1.friendly_name == "givenName"
assert a1.name == 'urn:mace:dir:attribute-def:givenName' assert a1.name == 'urn:mace:dir:attribute-def:givenName'
assert a1.name_format == BASIC_NF assert a1.name_format == BASIC_NF
elif a0.friendly_name == 'givenName': elif a0.friendly_name == 'givenname':
assert a0.name == 'urn:mace:dir:attribute-def:givenName' assert a0.name == 'urn:mace:dir:attribute-def:givenName'
assert a0.name_format == BASIC_NF assert a0.name_format == BASIC_NF
assert a1.friendly_name == "sn" assert a1.friendly_name == "sn"
@@ -97,7 +97,7 @@ class TestAC():
assert a1.friendly_name == "givenName" assert a1.friendly_name == "givenName"
assert a1.name == 'urn:oid:2.5.4.42' assert a1.name == 'urn:oid:2.5.4.42'
assert a1.name_format == URI_NF assert a1.name_format == URI_NF
elif a0.friendly_name == 'givenName': elif a0.friendly_name == 'givenname':
assert a0.name == 'urn:oid:2.5.4.42' assert a0.name == 'urn:oid:2.5.4.42'
assert a0.name_format == URI_NF assert a0.name_format == URI_NF
assert a1.friendly_name == "surname" assert a1.friendly_name == "surname"

47
tests/test_22_mdie.py Normal file
View File

@@ -0,0 +1,47 @@
__author__ = 'rolandh'
from saml2 import md
from saml2.mdie import from_dict
from saml2 import saml
from saml2.extension import mdui
from saml2.extension import idpdisc
from saml2.extension import dri
from saml2.extension import mdattr
from saml2.extension import ui
import xmldsig
import xmlenc
ONTS = {
saml.NAMESPACE: saml,
mdui.NAMESPACE: mdui,
mdattr.NAMESPACE: mdattr,
dri.NAMESPACE: dri,
ui.NAMESPACE: ui,
idpdisc.NAMESPACE: idpdisc,
md.NAMESPACE: md,
xmldsig.NAMESPACE: xmldsig,
xmlenc.NAMESPACE: xmlenc
}
def _eq(l1,l2):
return set(l1) == set(l2)
def _class(cls):
return "%s&%s" % (cls.c_namespace, cls.c_tag)
def test_construct_contact():
c = from_dict({
"__class__": _class(md.ContactPerson),
"given_name":{"text":"Roland", "__class__": _class(md.GivenName)},
"sur_name": {"text":"Hedberg", "__class__": _class(md.SurName)},
"email_address": [{"text":"roland@catalogix.se",
"__class__": _class(md.EmailAddress)}],
}, ONTS)
print c
assert c.given_name.text == "Roland"
assert c.sur_name.text == "Hedberg"
assert c.email_address[0].text == "roland@catalogix.se"
assert _eq(c.keyswv(), ["given_name","sur_name","email_address"])

463
tests/test_30_mdstore.py Normal file
View File

@@ -0,0 +1,463 @@
# -*- coding: utf-8 -*-
import datetime
import re
from saml2.mdstore import MetadataStore
from saml2.mdstore import destinations
from saml2.mdstore import name
from saml2 import md
from saml2 import BINDING_SOAP
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_HTTP_ARTIFACT
from saml2 import saml
from saml2.attribute_converter import ac_factory
from saml2.attribute_converter import d_to_local_name
from saml2.extension import mdui
from saml2.extension import idpdisc
from saml2.extension import dri
from saml2.extension import mdattr
from saml2.extension import ui
import xmldsig
import xmlenc
try:
from saml2.sigver import get_xmlsec_binary
xmlsec_path = get_xmlsec_binary(["/opt/local/bin"])
except ImportError:
xmlsec_path = '/usr/bin/xmlsec1'
ONTS = {
saml.NAMESPACE: saml,
mdui.NAMESPACE: mdui,
mdattr.NAMESPACE: mdattr,
dri.NAMESPACE: dri,
ui.NAMESPACE: ui,
idpdisc.NAMESPACE: idpdisc,
md.NAMESPACE: md,
xmldsig.NAMESPACE: xmldsig,
xmlenc.NAMESPACE: xmlenc
}
ATTRCONV = ac_factory("attributemaps")
METADATACONF = {
"1": {
"local": ["swamid-1.0.xml"]
},
"2": {
"local": ["InCommon-metadata.xml"]
},
"3": {
"local": ["extended.xml"]
},
"7": {
"local": ["metadata_sp_1.xml", "InCommon-metadata.xml"],
"remote": [{"url": "https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2",
"cert": "kalmar2.pem"}]
},
"4": {
"local": ["metadata_example.xml"]
},
"5": {
"local": ["metadata.aaitest.xml"]
},
"6": {
"local": ["metasp.xml"]
}
}
def _eq(l1,l2):
return set(l1) == set(l2)
def _fix_valid_until(xmlstring):
new_date = datetime.datetime.now() + datetime.timedelta(days=1)
new_date = new_date.strftime("%Y-%m-%dT%H:%M:%SZ")
return re.sub(r' validUntil=".*?"', ' validUntil="%s"' % new_date,
xmlstring)
def test_swami_1():
UMU_IDP = 'https://idp.umu.se/saml2/idp/metadata.php'
mds = MetadataStore(ONTS.values(), ATTRCONV, xmlsec_path,
disable_ssl_certificate_validation=True)
mds.imp(METADATACONF["1"])
assert len(mds) == 1 # One source
idps = mds.with_descriptor("idpsso")
assert idps.keys()
idpsso = mds.single_sign_on_service(UMU_IDP)
assert len(idpsso) == 1
assert destinations(idpsso) == ['https://idp.umu.se/saml2/idp/SSOService.php']
_name = name(mds[UMU_IDP])
assert _name == u'Umeå University (SAML2)'
certs = mds.certs(UMU_IDP, "idpsso", "signing")
assert len(certs) == 1
sps = mds.with_descriptor("spsso")
assert len(sps) == 108
wants = mds.attribute_requirement('https://connect8.sunet.se/shibboleth')
lnamn = [d_to_local_name(mds.attrc, attr) for attr in wants["optional"]]
assert _eq(lnamn, ['eduPersonPrincipalName', 'mail', 'givenName', 'sn',
'eduPersonScopedAffiliation'])
wants = mds.attribute_requirement('https://beta.lobber.se/shibboleth')
assert wants["required"] == []
lnamn = [d_to_local_name(mds.attrc, attr) for attr in wants["optional"]]
assert _eq(lnamn, ['eduPersonPrincipalName', 'mail', 'givenName', 'sn',
'eduPersonScopedAffiliation', 'eduPersonEntitlement'])
def test_incommon_1():
mds = MetadataStore(ONTS.values(), ATTRCONV, xmlsec_path,
disable_ssl_certificate_validation=True)
mds.imp(METADATACONF["2"])
print mds.entities()
assert mds.entities() == 169
idps = mds.with_descriptor("idpsso")
print idps.keys()
assert len(idps) == 53 # !!!!???? < 10%
assert mds.single_sign_on_service('urn:mace:incommon:uiuc.edu') == []
idpsso = mds.single_sign_on_service('urn:mace:incommon:alaska.edu')
assert len(idpsso) == 1
print idpsso
assert destinations(idpsso) == ['https://idp.alaska.edu/idp/profile/SAML2/Redirect/SSO']
sps = mds.with_descriptor("spsso")
acs_sp = []
for nam, desc in sps.items():
if "attribute_consuming_service" in desc:
acs_sp.append(nam)
assert len(acs_sp) == 0
# Look for attribute authorities
aas = mds.with_descriptor("attribute_authority")
print aas.keys()
assert len(aas) == 53
def test_ext_2():
mds = MetadataStore(ONTS.values(), ATTRCONV, xmlsec_path,
disable_ssl_certificate_validation=True)
mds.imp(METADATACONF["3"])
# No specific binding defined
ents = mds.with_descriptor("spsso")
for binding in [BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_ARTIFACT,
BINDING_HTTP_REDIRECT]:
assert mds.single_logout_service(ents.keys()[0], "spsso",
binding=binding)
def test_example():
mds = MetadataStore(ONTS.values(), ATTRCONV, xmlsec_path,
disable_ssl_certificate_validation=True)
mds.imp(METADATACONF["4"])
assert len(mds.keys()) == 1
idps = mds.with_descriptor("idpsso")
assert idps.keys() == [
'http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php']
certs = mds.certs(
'http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php',
"idpsso", "signing")
assert len(certs) == 1
def test_switch_1():
mds = MetadataStore(ONTS.values(), ATTRCONV, xmlsec_path,
disable_ssl_certificate_validation=True)
mds.imp(METADATACONF["5"])
assert len(mds.keys()) == 41
idps = mds.with_descriptor("idpsso")
print idps.keys()
idpsso = mds.single_sign_on_service(
'https://aai-demo-idp.switch.ch/idp/shibboleth')
assert len(idpsso) == 1
print idpsso
assert destinations(idpsso) == [
'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO']
assert len(idps) == 16
aas = mds.with_descriptor("attribute_authority")
print aas.keys()
aad = aas['https://aai-demo-idp.switch.ch/idp/shibboleth']
print aad.keys()
assert len(aad["attribute_authority_descriptor"]) == 1
assert len(aad["idpsso_descriptor"]) == 1
sps = mds.with_descriptor("spsso")
dual = [id for id,ent in idps.items() if id in sps]
print len(dual)
assert len(dual) == 0
def test_sp_metadata():
mds = MetadataStore(ONTS.values(), ATTRCONV, xmlsec_path,
disable_ssl_certificate_validation=True)
mds.imp(METADATACONF["6"])
assert len(mds.keys()) == 1
assert mds.keys() == ['urn:mace:umu.se:saml:roland:sp']
assert _eq(mds['urn:mace:umu.se:saml:roland:sp'].keys(), [
'entity_id', '__class__', 'spsso_descriptor'])
req = mds.attribute_requirement('urn:mace:umu.se:saml:roland:sp')
print req
assert len(req["required"]) == 3
assert len(req["optional"]) == 1
assert req["optional"][0]["name"] == 'urn:oid:2.5.4.12'
assert req["optional"][0]["friendly_name"] == 'title'
assert _eq([n["name"] for n in req["required"]],['urn:oid:2.5.4.4',
'urn:oid:2.5.4.42',
'urn:oid:0.9.2342.19200300.100.1.3'])
assert _eq([n["friendly_name"] for n in req["required"]],
['surName', 'givenName', 'mail'])
##def test_import_external_metadata(xmlsec):
## md = metadata.MetaData(xmlsec,attrconv=ATTRCONV)
## mds.import_external_metadata(KALMAR2_URL, KALMAR2_CERT)
##
## print len(mds.entity)
## assert len(mds.entity) > 20
## idps = dict([
## (id,ent["idpsso"]) for id,ent in mds.entity.items() if "idpsso" in ent])
## print idps.keys()
## assert len(idps) > 1
## assert "https://idp.umu.se/saml2/idp/metadata.php" in idps
#
## ------------ Constructing metadata ----------------------------------------
#
#def test_construct_contact():
# c = make_instance(mds.ContactPerson, {
# "given_name":"Roland",
# "sur_name": "Hedberg",
# "email_address": "roland@catalogix.se",
# })
# print c
# assert c.given_name.text == "Roland"
# assert c.sur_name.text == "Hedberg"
# assert c.email_address[0].text == "roland@catalogix.se"
# assert _eq(c.keyswv(), ["given_name","sur_name","email_address"])
#
#
#def test_construct_organisation():
# c = make_instance( mds.Organization, {
# "organization_name": ["Example Co.",
# {"text":"Exempel AB", "lang":"se"}],
# "organization_url": "http://www.example.com/"
# })
#
# assert _eq(c.keyswv(), ["organization_name","organization_url"])
# assert len(c.organization_name) == 2
# org_names = [on.text for on in c.organization_name]
# assert _eq(org_names,["Exempel AB","Example Co."])
# assert len(c.organization_url) == 1
#
#def test_construct_entity_descr_1():
# ed = make_instance(mds.EntityDescriptor,
# {"organization": {
# "organization_name":"Catalogix",
# "organization_url": "http://www.catalogix.se/"},
# "entity_id": "urn:mace:catalogix.se:sp1",
# })
#
# assert ed.entity_id == "urn:mace:catalogix.se:sp1"
# org = ed.organization
# assert org
# assert _eq(org.keyswv(), ["organization_name","organization_url"])
# assert len(org.organization_name) == 1
# assert org.organization_name[0].text == "Catalogix"
# assert org.organization_url[0].text == "http://www.catalogix.se/"
#
#def test_construct_entity_descr_2():
# ed = make_instance(mds.EntityDescriptor,
# {"organization": {
# "organization_name":"Catalogix",
# "organization_url": "http://www.catalogix.se/"},
# "entity_id": "urn:mace:catalogix.se:sp1",
# "contact_person": {
# "given_name":"Roland",
# "sur_name": "Hedberg",
# "email_address": "roland@catalogix.se",
# }
# })
#
# assert _eq(ed.keyswv(), ["entity_id", "contact_person", "organization"])
# assert ed.entity_id == "urn:mace:catalogix.se:sp1"
# org = ed.organization
# assert org
# assert _eq(org.keyswv(), ["organization_name", "organization_url"])
# assert len(org.organization_name) == 1
# assert org.organization_name[0].text == "Catalogix"
# assert org.organization_url[0].text == "http://www.catalogix.se/"
# assert len(ed.contact_person) == 1
# c = ed.contact_person[0]
# assert c.given_name.text == "Roland"
# assert c.sur_name.text == "Hedberg"
# assert c.email_address[0].text == "roland@catalogix.se"
# assert _eq(c.keyswv(), ["given_name","sur_name","email_address"])
#
#def test_construct_key_descriptor():
# cert = "".join(_read_lines("test.pem")[1:-1]).strip()
# spec = {
# "use": "signing",
# "key_info" : {
# "x509_data": {
# "x509_certificate": cert
# }
# }
# }
# kd = make_instance(mds.KeyDescriptor, spec)
# assert _eq(kd.keyswv(), ["use", "key_info"])
# assert kd.use == "signing"
# ki = kd.key_info
# assert _eq(ki.keyswv(), ["x509_data"])
# assert len(ki.x509_data) == 1
# data = ki.x509_data[0]
# assert _eq(data.keyswv(), ["x509_certificate"])
# assert data.x509_certificate
# assert len(data.x509_certificate.text.strip()) == len(cert)
#
#def test_construct_key_descriptor_with_key_name():
# cert = "".join(_read_lines("test.pem")[1:-1]).strip()
# spec = {
# "use": "signing",
# "key_info" : {
# "key_name": "example.com",
# "x509_data": {
# "x509_certificate": cert
# }
# }
# }
# kd = make_instance(mds.KeyDescriptor, spec)
# assert _eq(kd.keyswv(), ["use", "key_info"])
# assert kd.use == "signing"
# ki = kd.key_info
# assert _eq(ki.keyswv(), ["x509_data", "key_name"])
# assert len(ki.key_name) == 1
# assert ki.key_name[0].text.strip() == "example.com"
# assert len(ki.x509_data) == 1
# data = ki.x509_data[0]
# assert _eq(data.keyswv(), ["x509_certificate"])
# assert data.x509_certificate
# assert len(data.x509_certificate.text.strip()) == len(cert)
#
#def test_construct_AttributeAuthorityDescriptor():
# aad = make_instance(
# mds.AttributeAuthorityDescriptor, {
# "valid_until": time_util.in_a_while(30), # 30 days from now
# "id": "aad.example.com",
# "protocol_support_enumeration": SAML2_NAMESPACE,
# "attribute_service": {
# "binding": BINDING_SOAP,
# "location": "http://example.com:6543/saml2/aad",
# },
# "name_id_format":[
# NAMEID_FORMAT_TRANSIENT,
# ],
# "key_descriptor": {
# "use": "signing",
# "key_info" : {
# "key_name": "example.com",
# }
# }
# })
#
# print aad
# assert _eq(aad.keyswv(),["valid_until", "id", "attribute_service",
# "name_id_format", "key_descriptor",
# "protocol_support_enumeration"])
# assert time_util.str_to_time(aad.valid_until)
# assert aad.id == "aad.example.com"
# assert aad.protocol_support_enumeration == SAML2_NAMESPACE
# assert len(aad.attribute_service) == 1
# atsr = aad.attribute_service[0]
# assert _eq(atsr.keyswv(),["binding", "location"])
# assert atsr.binding == BINDING_SOAP
# assert atsr.location == "http://example.com:6543/saml2/aad"
# assert len(aad.name_id_format) == 1
# nif = aad.name_id_format[0]
# assert nif.text.strip() == NAMEID_FORMAT_TRANSIENT
# assert len(aad.key_descriptor) == 1
# kdesc = aad.key_descriptor[0]
# assert kdesc.use == "signing"
# assert kdesc.key_info.key_name[0].text.strip() == "example.com"
#
#STATUS_RESULT = """<?xml version='1.0' encoding='UTF-8'?>
#<ns0:Status xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder"><ns0:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal" /></ns0:StatusCode><ns0:StatusMessage>Error resolving principal</ns0:StatusMessage></ns0:Status>"""
#
#def test_status():
# input = {
# "status_code": {
# "value": samlp.STATUS_RESPONDER,
# "status_code":
# {
# "value": samlp.STATUS_UNKNOWN_PRINCIPAL,
# },
# },
# "status_message": "Error resolving principal",
# }
# status_text = "%s" % make_instance( samlp.Status, input)
# assert status_text == STATUS_RESULT
#
#def test_attributes():
# required = ["surname", "givenname", "edupersonaffiliation"]
# ra = metadata.do_requested_attribute(required, ATTRCONV, "True")
# print ra
# assert ra
# assert len(ra) == 3
# for i in range(3):
# assert isinstance(ra[i], mds.RequestedAttribute)
# assert ra[i].name_format == NAME_FORMAT_URI
# assert ra[i].attribute_value == []
# assert ra[i].is_required == "True"
# assert ra[0].friendly_name == "surname"
# assert ra[0].name == 'urn:oid:2.5.4.4'
#
#def test_extend():
# md = metadata.MetaData(attrconv=ATTRCONV)
# mds.import_metadata(_fix_valid_until(_read_file("extended.xml")), "-")
#
# signcerts = mds.certs("https://coip-test.sunet.se/shibboleth", "signing")
# assert len(signcerts) == 1
# enccerts = mds.certs("https://coip-test.sunet.se/shibboleth", "encryption")
# assert len(enccerts) == 1
# assert signcerts[0] == enccerts[0]
#
#def test_ui_info():
# md = metadata.MetaData(attrconv=ATTRCONV)
# mds.import_metadata(_fix_valid_until(_read_file("idp_uiinfo.xml")), "-")
# loc = mds.single_sign_on_services_with_uiinfo(
# "http://example.com/saml2/idp.xml")
# assert len(loc) == 1
# assert loc[0][0] == "http://example.com/saml2/"
# assert len(loc[0][1]) == 1
# ui_info = loc[0][1][0]
# print ui_info
# assert ui_info.description[0].text == "Exempel bolag"
#
#def test_pdp():
# md = metadata.MetaData(attrconv=ATTRCONV)
# mds.import_metadata(_fix_valid_until(_read_file("pdp_meta.xml")), "-")
#
# assert md
#
# pdps = mds.pdp_services("http://www.example.org/pysaml2/")
#
# assert len(pdps) == 1
# pdp = pdps[0]
# assert len(pdp.authz_service) == 1
# assert pdp.authz_service[0].location == "http://www.example.org/pysaml2/authz"
# assert pdp.authz_service[0].binding == BINDING_SOAP
# endpoints = mds.authz_service("http://www.example.org/pysaml2/")
# assert len(endpoints) == 1
# assert endpoints[0] == "http://www.example.org/pysaml2/authz"

View File

@@ -1,498 +0,0 @@
import datetime
import re
#import os
from saml2 import metadata
from saml2 import make_vals
from saml2 import make_instance
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_SOAP
from saml2 import BINDING_HTTP_ARTIFACT
from saml2 import md, saml, samlp
from saml2 import time_util
from saml2 import NAMESPACE as SAML2_NAMESPACE
from saml2.saml import NAMEID_FORMAT_TRANSIENT, NAME_FORMAT_URI
from saml2.attribute_converter import ac_factory, to_local_name
#from py.test import raises
SWAMI_METADATA = "swamid-1.0.xml"
INCOMMON_METADATA = "InCommon-metadata.xml"
EXAMPLE_METADATA = "metadata_example.xml"
SWITCH_METADATA = "metadata.aaitest.xml"
SP_METADATA = "metasp.xml"
def _eq(l1,l2):
return set(l1) == set(l2)
def _read_file(name):
try:
return open(name).read()
except IOError:
name = "tests/"+name
return open(name).read()
def _read_lines(name):
try:
return open(name).readlines()
except IOError:
name = "tests/"+name
return open(name).readlines()
def _fix_valid_until(xmlstring):
new_date = datetime.datetime.now() + datetime.timedelta(days=1)
new_date = new_date.strftime("%Y-%m-%dT%H:%M:%SZ")
return re.sub(r' validUntil=".*?"', ' validUntil="%s"' % new_date,
xmlstring)
ATTRCONV = ac_factory("attributemaps")
def test_swami_1():
md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_read_file(SWAMI_METADATA),"-")
print len(md.entity)
assert len(md.entity)
idps = dict([(id,ent["idpsso"]) for id,ent in md.entity.items() \
if "idpsso" in ent])
print idps
assert idps.keys()
idpsso = md.single_sign_on_service(
'https://idp.umu.se/saml2/idp/metadata.php')
assert md.name('https://idp.umu.se/saml2/idp/metadata.php') == (
u'Ume\xe5 University (SAML2)')
assert len(idpsso) == 1
assert idpsso == ['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")
print ssocerts
assert len(ssocerts) == 1
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)
#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',
'eduPersonScopedAffiliation'])
wants = md.attribute_requirement('https://beta.lobber.se/shibboleth')
assert wants[0] == []
lnamn = [to_local_name(md.attrconv, attr) for attr in wants[1]]
assert _eq(lnamn,
['eduPersonScopedAffiliation', 'eduPersonEntitlement',
'eduPersonPrincipalName', 'sn', 'mail', 'givenName'])
def test_incommon_1():
md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_read_file(INCOMMON_METADATA),"-")
print len(md.entity)
assert len(md.entity) == 442
idps = dict([
(id,ent["idpsso"]) for id,ent in md.entity.items() if "idpsso" in ent])
print idps.keys()
assert len(idps) == 53 # !!!!???? < 10%
assert md.single_sign_on_service('urn:mace:incommon:uiuc.edu') == []
idpsso = md.single_sign_on_service('urn:mace:incommon:alaska.edu')
assert len(idpsso) == 1
print idpsso
assert idpsso == ['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_ext_2():
md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_read_file("extended.xml"),"-")
# No specific binding defined
eid = [id for id,ent in md.entity.items() if "spsso" in ent]
endps = md.single_logout_service(eid[0], None)
assert len(endps) == 4
assert _eq([b for b, e in endps], [BINDING_SOAP, BINDING_HTTP_REDIRECT,
BINDING_HTTP_POST, BINDING_HTTP_ARTIFACT])
def test_example():
md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_read_file(EXAMPLE_METADATA), "-")
print len(md.entity)
assert len(md.entity) == 1
idps = dict([(id,ent["idpsso"]) for id,ent in md.entity.items() \
if "idpsso" in ent])
assert idps.keys() == [
'http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php']
print md._loc_key['http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php']
certs = md.certs(
'http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php',
"signing")
assert len(certs) == 1
assert isinstance(certs[0], tuple)
assert len(certs[0]) == 2
def test_switch_1():
md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_read_file(SWITCH_METADATA), "-")
print len(md.entity)
assert len(md.entity) == 90
idps = dict([(id,ent["idpsso"]) for id,ent in md.entity.items() \
if "idpsso" in ent])
print idps.keys()
idpsso = md.single_sign_on_service(
'https://aai-demo-idp.switch.ch/idp/shibboleth')
assert len(idpsso) == 1
print idpsso
assert idpsso == [
'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO']
assert len(idps) == 16
aas = dict([(id,ent["attribute_authority"]) for id,ent in md.entity.items() \
if "attribute_authority" in ent])
print aas.keys()
aads = aas['https://aai-demo-idp.switch.ch/idp/shibboleth']
assert len(aads) == 1
aad = aads[0]
assert len(aad.attribute_service) == 1
assert len(aad.name_id_format) == 2
dual = dict([(id,ent) for id,ent in md.entity.items() \
if "idpsso" in ent and "spsso" in ent])
print len(dual)
assert len(dual) == 0
def test_sp_metadata():
md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_fix_valid_until(_read_file(SP_METADATA)), "-")
print md.entity
assert len(md.entity) == 1
assert md.entity.keys() == ['urn:mace:umu.se:saml:roland:sp']
assert _eq(md.entity['urn:mace:umu.se:saml:roland:sp'].keys(), [
'valid_until',"organization","spsso",
'contact_person'])
print md.entity['urn:mace:umu.se:saml:roland:sp']["spsso"][0].keyswv()
(req,opt) = md.attribute_requirement('urn:mace:umu.se:saml:roland:sp')
print req
assert len(req) == 3
assert len(opt) == 1
assert opt[0].name == 'urn:oid:2.5.4.12'
assert opt[0].friendly_name == 'title'
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'])
assert _eq([n.friendly_name for n in req],['surName', 'givenName', 'mail'])
KALMAR2_URL = "https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2"
KALMAR2_CERT = "kalmar2.pem"
#def test_import_external_metadata(xmlsec):
# md = metadata.MetaData(xmlsec,attrconv=ATTRCONV)
# md.import_external_metadata(KALMAR2_URL, KALMAR2_CERT)
#
# print len(md.entity)
# assert len(md.entity) > 20
# idps = dict([
# (id,ent["idpsso"]) for id,ent in md.entity.items() if "idpsso" in ent])
# print idps.keys()
# assert len(idps) > 1
# assert "https://idp.umu.se/saml2/idp/metadata.php" in idps
# ------------ Constructing metaval ----------------------------------------
def test_construct_organisation_name():
o = md.Organization()
make_vals({"text":"Exempel AB", "lang":"se"},
md.OrganizationName, o, "organization_name")
print o
assert str(o) == """<?xml version='1.0' encoding='UTF-8'?>
<ns0:Organization xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata"><ns0:OrganizationName xml:lang="se">Exempel AB</ns0:OrganizationName></ns0:Organization>"""
def test_make_int_value():
val = make_vals( 1, saml.AttributeValue, part=True)
assert isinstance(val, saml.AttributeValue)
assert val.text == "1"
def test_make_true_value():
val = make_vals( True, saml.AttributeValue, part=True )
assert isinstance(val, saml.AttributeValue)
assert val.text == "true"
def test_make_false_value():
val = make_vals( False, saml.AttributeValue, part=True )
assert isinstance(val, saml.AttributeValue)
assert val.text == "false"
NO_VALUE = """<?xml version='1.0' encoding='UTF-8'?>
<saml:AttributeValue xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" />"""
def test_make_no_value():
val = make_vals( None, saml.AttributeValue, part=True )
assert isinstance(val, saml.AttributeValue)
assert val.text == ""
print val
assert val.to_string({'saml': saml.NAMESPACE}) == NO_VALUE
def test_make_string():
val = make_vals( "example", saml.AttributeValue, part=True )
assert isinstance(val, saml.AttributeValue)
assert val.text == "example"
def test_make_list_of_strings():
attr = saml.Attribute()
vals = ["foo", "bar"]
make_vals(vals, saml.AttributeValue, attr, "attribute_value")
assert attr.keyswv() == ["attribute_value"]
print attr.attribute_value
assert _eq([val.text for val in attr.attribute_value], vals)
def test_make_dict():
vals = ["foo", "bar"]
attrval = { "attribute_value": vals}
attr = make_vals(attrval, saml.Attribute, part=True)
assert attr.keyswv() == ["attribute_value"]
assert _eq([val.text for val in attr.attribute_value], vals)
# ------------ Constructing metadata ----------------------------------------
def test_construct_contact():
c = make_instance(md.ContactPerson, {
"given_name":"Roland",
"sur_name": "Hedberg",
"email_address": "roland@catalogix.se",
})
print c
assert c.given_name.text == "Roland"
assert c.sur_name.text == "Hedberg"
assert c.email_address[0].text == "roland@catalogix.se"
assert _eq(c.keyswv(), ["given_name","sur_name","email_address"])
def test_construct_organisation():
c = make_instance( md.Organization, {
"organization_name": ["Example Co.",
{"text":"Exempel AB", "lang":"se"}],
"organization_url": "http://www.example.com/"
})
assert _eq(c.keyswv(), ["organization_name","organization_url"])
assert len(c.organization_name) == 2
org_names = [on.text for on in c.organization_name]
assert _eq(org_names,["Exempel AB","Example Co."])
assert len(c.organization_url) == 1
def test_construct_entity_descr_1():
ed = make_instance(md.EntityDescriptor,
{"organization": {
"organization_name":"Catalogix",
"organization_url": "http://www.catalogix.se/"},
"entity_id": "urn:mace:catalogix.se:sp1",
})
assert ed.entity_id == "urn:mace:catalogix.se:sp1"
org = ed.organization
assert org
assert _eq(org.keyswv(), ["organization_name","organization_url"])
assert len(org.organization_name) == 1
assert org.organization_name[0].text == "Catalogix"
assert org.organization_url[0].text == "http://www.catalogix.se/"
def test_construct_entity_descr_2():
ed = make_instance(md.EntityDescriptor,
{"organization": {
"organization_name":"Catalogix",
"organization_url": "http://www.catalogix.se/"},
"entity_id": "urn:mace:catalogix.se:sp1",
"contact_person": {
"given_name":"Roland",
"sur_name": "Hedberg",
"email_address": "roland@catalogix.se",
}
})
assert _eq(ed.keyswv(), ["entity_id", "contact_person", "organization"])
assert ed.entity_id == "urn:mace:catalogix.se:sp1"
org = ed.organization
assert org
assert _eq(org.keyswv(), ["organization_name", "organization_url"])
assert len(org.organization_name) == 1
assert org.organization_name[0].text == "Catalogix"
assert org.organization_url[0].text == "http://www.catalogix.se/"
assert len(ed.contact_person) == 1
c = ed.contact_person[0]
assert c.given_name.text == "Roland"
assert c.sur_name.text == "Hedberg"
assert c.email_address[0].text == "roland@catalogix.se"
assert _eq(c.keyswv(), ["given_name","sur_name","email_address"])
def test_construct_key_descriptor():
cert = "".join(_read_lines("test.pem")[1:-1]).strip()
spec = {
"use": "signing",
"key_info" : {
"x509_data": {
"x509_certificate": cert
}
}
}
kd = make_instance(md.KeyDescriptor, spec)
assert _eq(kd.keyswv(), ["use", "key_info"])
assert kd.use == "signing"
ki = kd.key_info
assert _eq(ki.keyswv(), ["x509_data"])
assert len(ki.x509_data) == 1
data = ki.x509_data[0]
assert _eq(data.keyswv(), ["x509_certificate"])
assert data.x509_certificate
assert len(data.x509_certificate.text.strip()) == len(cert)
def test_construct_key_descriptor_with_key_name():
cert = "".join(_read_lines("test.pem")[1:-1]).strip()
spec = {
"use": "signing",
"key_info" : {
"key_name": "example.com",
"x509_data": {
"x509_certificate": cert
}
}
}
kd = make_instance(md.KeyDescriptor, spec)
assert _eq(kd.keyswv(), ["use", "key_info"])
assert kd.use == "signing"
ki = kd.key_info
assert _eq(ki.keyswv(), ["x509_data", "key_name"])
assert len(ki.key_name) == 1
assert ki.key_name[0].text.strip() == "example.com"
assert len(ki.x509_data) == 1
data = ki.x509_data[0]
assert _eq(data.keyswv(), ["x509_certificate"])
assert data.x509_certificate
assert len(data.x509_certificate.text.strip()) == len(cert)
def test_construct_AttributeAuthorityDescriptor():
aad = make_instance(
md.AttributeAuthorityDescriptor, {
"valid_until": time_util.in_a_while(30), # 30 days from now
"id": "aad.example.com",
"protocol_support_enumeration": SAML2_NAMESPACE,
"attribute_service": {
"binding": BINDING_SOAP,
"location": "http://example.com:6543/saml2/aad",
},
"name_id_format":[
NAMEID_FORMAT_TRANSIENT,
],
"key_descriptor": {
"use": "signing",
"key_info" : {
"key_name": "example.com",
}
}
})
print aad
assert _eq(aad.keyswv(),["valid_until", "id", "attribute_service",
"name_id_format", "key_descriptor",
"protocol_support_enumeration"])
assert time_util.str_to_time(aad.valid_until)
assert aad.id == "aad.example.com"
assert aad.protocol_support_enumeration == SAML2_NAMESPACE
assert len(aad.attribute_service) == 1
atsr = aad.attribute_service[0]
assert _eq(atsr.keyswv(),["binding", "location"])
assert atsr.binding == BINDING_SOAP
assert atsr.location == "http://example.com:6543/saml2/aad"
assert len(aad.name_id_format) == 1
nif = aad.name_id_format[0]
assert nif.text.strip() == NAMEID_FORMAT_TRANSIENT
assert len(aad.key_descriptor) == 1
kdesc = aad.key_descriptor[0]
assert kdesc.use == "signing"
assert kdesc.key_info.key_name[0].text.strip() == "example.com"
STATUS_RESULT = """<?xml version='1.0' encoding='UTF-8'?>
<ns0:Status xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder"><ns0:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal" /></ns0:StatusCode><ns0:StatusMessage>Error resolving principal</ns0:StatusMessage></ns0:Status>"""
def test_status():
input = {
"status_code": {
"value": samlp.STATUS_RESPONDER,
"status_code":
{
"value": samlp.STATUS_UNKNOWN_PRINCIPAL,
},
},
"status_message": "Error resolving principal",
}
status_text = "%s" % make_instance( samlp.Status, input)
assert status_text == STATUS_RESULT
def test_attributes():
required = ["surname", "givenname", "edupersonaffiliation"]
ra = metadata.do_requested_attribute(required, ATTRCONV, "True")
print ra
assert ra
assert len(ra) == 3
for i in range(3):
assert isinstance(ra[i], md.RequestedAttribute)
assert ra[i].name_format == NAME_FORMAT_URI
assert ra[i].attribute_value == []
assert ra[i].is_required == "True"
assert ra[0].friendly_name == "surname"
assert ra[0].name == 'urn:oid:2.5.4.4'
def test_extend():
md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_fix_valid_until(_read_file("extended.xml")), "-")
signcerts = md.certs("https://coip-test.sunet.se/shibboleth", "signing")
assert len(signcerts) == 1
enccerts = md.certs("https://coip-test.sunet.se/shibboleth", "encryption")
assert len(enccerts) == 1
assert signcerts[0] == enccerts[0]
def test_ui_info():
md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_fix_valid_until(_read_file("idp_uiinfo.xml")), "-")
loc = md.single_sign_on_services_with_uiinfo(
"http://example.com/saml2/idp.xml")
assert len(loc) == 1
assert loc[0][0] == "http://example.com/saml2/"
assert len(loc[0][1]) == 1
ui_info = loc[0][1][0]
print ui_info
assert ui_info.description[0].text == "Exempel bolag"
def test_pdp():
md = metadata.MetaData(attrconv=ATTRCONV)
md.import_metadata(_fix_valid_until(_read_file("pdp_meta.xml")), "-")
assert md
pdps = md.pdp_services("http://www.example.org/pysaml2/")
assert len(pdps) == 1
pdp = pdps[0]
assert len(pdp.authz_service) == 1
assert pdp.authz_service[0].location == "http://www.example.org/pysaml2/authz"
assert pdp.authz_service[0].binding == BINDING_SOAP
endpoints = md.authz_service("http://www.example.org/pysaml2/")
assert len(endpoints) == 1
assert endpoints[0] == "http://www.example.org/pysaml2/authz"

View File

@@ -3,10 +3,10 @@
import sys import sys
import logging import logging
from saml2.mdstore import MetadataStore, name
from saml2 import BINDING_HTTP_REDIRECT, BINDING_SOAP, BINDING_HTTP_POST from saml2 import BINDING_HTTP_REDIRECT, BINDING_SOAP, BINDING_HTTP_POST
from saml2.config import SPConfig, IdPConfig, Config from saml2.config import SPConfig, IdPConfig, Config
from saml2.metadata import MetaData
from py.test import raises from py.test import raises
from saml2 import root_logger from saml2 import root_logger
@@ -167,7 +167,7 @@ def test_1():
assert c._sp_name assert c._sp_name
assert c._sp_idp assert c._sp_idp
md = c.metadata md = c.metadata
assert isinstance(md, MetaData) assert isinstance(md, MetadataStore)
assert len(c._sp_idp) == 1 assert len(c._sp_idp) == 1
assert c._sp_idp.keys() == ["urn:mace:example.com:saml:roland:idp"] assert c._sp_idp.keys() == ["urn:mace:example.com:saml:roland:idp"]
@@ -243,10 +243,10 @@ def test_wayf():
c = SPConfig().load_file("server_conf") c = SPConfig().load_file("server_conf")
c.context = "sp" c.context = "sp"
idps = c.idps() idps = c.metadata.with_descriptor("idpsso")
assert idps == {'urn:mace:example.com:saml:roland:idp': 'Example Co.'} ent = idps.values()[0]
idps = c.idps(["se","en"]) assert name(ent) == 'Example Co.'
assert idps == {'urn:mace:example.com:saml:roland:idp': 'Exempel AB'} assert name(ent, "se") == 'Exempel AB'
c.setup_logger() c.setup_logger()
@@ -306,11 +306,8 @@ def test_3():
def test_sp(): def test_sp():
cnf = SPConfig() cnf = SPConfig()
cnf.load_file("sp_1_conf") cnf.load_file("sp_1_conf")
assert cnf.single_logout_services("urn:mace:example.com:saml:roland:idp",
BINDING_HTTP_POST) == ["http://localhost:8088/slo"]
assert cnf.endpoint("assertion_consumer_service") == \ assert cnf.endpoint("assertion_consumer_service") == \
["http://lingon.catalogix.se:8087/"] ["http://lingon.catalogix.se:8087/"]
assert len(cnf.idps()) == 1
def test_dual(): def test_dual():
cnf = Config().load_file("idp_sp_conf") cnf = Config().load_file("idp_sp_conf")
@@ -336,12 +333,9 @@ def test_assertion_consumer_service():
c.load_file("idp_conf") c.load_file("idp_conf")
c.context = "idp" c.context = "idp"
xml_src = open("InCommon-metadata.xml").read() c.metadata.load("local", "InCommon-metadata.xml")
# A trick so outdated data is allowed
c.metadata.import_metadata(xml_src, "-")
print c.metadata.entity.keys()
entity_id = "https://www.zimride.com/shibboleth" entity_id = "https://www.zimride.com/shibboleth"
acs = c.assertion_consumer_services(entity_id) acs = c.metadata.assertion_consumer_service(entity_id)
assert len(acs) == 1 assert len(acs) == 1
assert acs[0].location == 'https://www.zimride.com/Shibboleth.sso/SAML2/POST' assert acs[0]["location"] == 'https://www.zimride.com/Shibboleth.sso/SAML2/POST'

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
import base64 import base64
from saml2.saml import assertion_from_string
from saml2.samlp import response_from_string
from saml2 import sigver from saml2 import sigver
from saml2 import class_name from saml2 import class_name
@@ -125,7 +127,7 @@ class TestSecurity():
print xmlsec_version(get_xmlsec_binary()) print xmlsec_version(get_xmlsec_binary())
item = self.sec.check_signature(sass, node_name=class_name(sass)) item = self.sec.check_signature(sass, class_name(sass), sign_ass)
assert isinstance(item, saml.Assertion) assert isinstance(item, saml.Assertion)
@@ -141,17 +143,17 @@ class TestSecurity():
assert s_response is not None assert s_response is not None
print s_response print s_response
print response = response_from_string(s_response)
sass = s_response.assertion[0] sass = response.assertion[0]
print sass print sass
assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant', assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant',
'version', 'signature', 'id']) 'version', 'signature', 'id'])
assert sass.version == "2.0" assert sass.version == "2.0"
assert sass.id == "11111" assert sass.id == "11111"
item = self.sec.check_signature(s_response, item = self.sec.check_signature(response, class_name(response),
node_name=class_name(s_response)) s_response)
assert isinstance(item, samlp.Response) assert isinstance(item, samlp.Response)
assert item.id == "22222" assert item.id == "22222"
@@ -177,14 +179,16 @@ class TestSecurity():
s_response = sigver.signed_instance_factory(response, self.sec, to_sign) s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
assert s_response is not None assert s_response is not None
sass = s_response.assertion[0] response2 = response_from_string(s_response)
sass = response2.assertion[0]
assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant', assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant',
'version', 'signature', 'id']) 'version', 'signature', 'id'])
assert sass.version == "2.0" assert sass.version == "2.0"
assert sass.id == "11122" assert sass.id == "11122"
item = self.sec.check_signature(s_response, item = self.sec.check_signature(response2, class_name(response),
node_name=class_name(s_response)) s_response)
assert isinstance(item, samlp.Response) assert isinstance(item, samlp.Response)
@@ -217,24 +221,21 @@ class TestSecurity():
s_response = sigver.signed_instance_factory(response, self.sec, to_sign) s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
print s_response.keyswv() response2 = response_from_string(s_response)
print s_response.signature.keyswv()
print s_response.signature.key_info.keyswv() ci = "".join(sigver.cert_from_instance(response2)[0].split())
ci = "".join(sigver.cert_from_instance(s_response)[0].split())
print ci
print self.sec.my_cert
assert ci == self.sec.my_cert assert ci == self.sec.my_cert
res = self.sec.verify_signature("%s" % s_response, res = self.sec.verify_signature("%s" % s_response,
node_name=class_name(samlp.Response())) node_name=class_name(samlp.Response()))
assert res assert res
res = self.sec._check_signature("%s" % s_response, s_response,
class_name(s_response)) res = self.sec._check_signature(s_response, response2,
class_name(response2), s_response)
assert res == s_response assert res == response2
def test_sign_verify_assertion_with_cert_from_instance(self): def test_sign_verify_assertion_with_cert_from_instance(self):
assertion = factory( saml.Assertion, assertion = factory( saml.Assertion,
@@ -251,16 +252,15 @@ class TestSecurity():
to_sign = [(class_name(assertion), assertion.id)] to_sign = [(class_name(assertion), assertion.id)]
s_assertion = sigver.signed_instance_factory(assertion, self.sec, to_sign) s_assertion = sigver.signed_instance_factory(assertion, self.sec, to_sign)
print s_assertion print s_assertion
ass = assertion_from_string(s_assertion)
ci = "".join(sigver.cert_from_instance(s_assertion)[0].split()) ci = "".join(sigver.cert_from_instance(ass)[0].split())
assert ci == self.sec.my_cert assert ci == self.sec.my_cert
res = self.sec.verify_signature("%s" % s_assertion, res = self.sec.verify_signature("%s" % s_assertion,
node_name=class_name(s_assertion)) node_name=class_name(ass))
assert res assert res
res = self.sec._check_signature("%s" % s_assertion, s_assertion, res = self.sec._check_signature(s_assertion, ass, class_name(ass))
class_name(s_assertion))
assert res assert res
@@ -285,8 +285,9 @@ class TestSecurity():
s_response = sigver.signed_instance_factory(response, self.sec, to_sign) s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
response2 = response_from_string(s_response)
# Change something that should make everything fail # Change something that should make everything fail
s_response.id = "23456" response2.id = "23456"
raises(sigver.SignatureError, self.sec._check_signature, raises(sigver.SignatureError, self.sec._check_signature,
"%s" % s_response, s_response, class_name(s_response)) s_response, response2, class_name(response2))

View File

@@ -8,21 +8,20 @@ from saml2.server import Server
from saml2.response import response_factory from saml2.response import response_factory
from saml2.response import StatusResponse from saml2.response import StatusResponse
from saml2.response import AuthnResponse from saml2.response import AuthnResponse
from saml2.sigver import security_context from saml2.sigver import security_context, MissingKey
from saml2.sigver import MissingKey
from pytest import raises from pytest import raises
XML_RESPONSE_FILE = "saml_signed.xml" XML_RESPONSE_FILE = "saml_signed.xml"
XML_RESPONSE_FILE2 = "saml2_response.xml" 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"], IDENTITY = {"eduPersonAffiliation": ["staff", "member"],
"surName": ["Jeter"], "givenName": ["Derek"], "surName": ["Jeter"], "givenName": ["Derek"],
"mail": ["foo@gmail.com"]} "mail": ["foo@gmail.com"],
"title": ["shortstop"]}
class TestResponse: class TestResponse:
def setup_class(self): def setup_class(self):
@@ -62,7 +61,7 @@ class TestResponse:
self.conf = conf self.conf = conf
def test_1(self): def test_1(self):
xml_response = ("%s" % (self._resp_,)).split("\n")[1] xml_response = ("%s" % (self._resp_,))
resp = response_factory(xml_response, self.conf, resp = response_factory(xml_response, self.conf,
return_addr="http://lingon.catalogix.se:8087/", return_addr="http://lingon.catalogix.se:8087/",
outstanding_queries={"id12": "http://localhost:8088/sso"}, outstanding_queries={"id12": "http://localhost:8088/sso"},
@@ -72,7 +71,7 @@ class TestResponse:
assert isinstance(resp, AuthnResponse) assert isinstance(resp, AuthnResponse)
def test_2(self): def test_2(self):
xml_response = ("%s" % (self._sign_resp_,)).split("\n",1)[1] xml_response = self._sign_resp_
resp = response_factory(xml_response, self.conf, resp = response_factory(xml_response, self.conf,
return_addr="http://lingon.catalogix.se:8087/", return_addr="http://lingon.catalogix.se:8087/",
outstanding_queries={"id12": "http://localhost:8088/sso"}, outstanding_queries={"id12": "http://localhost:8088/sso"},

View File

@@ -14,7 +14,8 @@ def _eq(l1,l2):
IDENTITY = {"eduPersonAffiliation": ["staff", "member"], IDENTITY = {"eduPersonAffiliation": ["staff", "member"],
"surName": ["Jeter"], "givenName": ["Derek"], "surName": ["Jeter"], "givenName": ["Derek"],
"mail": ["foo@gmail.com"]} "mail": ["foo@gmail.com"],
"title": ["shortstop"]}
class TestAuthnResponse: class TestAuthnResponse:
def setup_class(self): def setup_class(self):
@@ -49,7 +50,8 @@ class TestAuthnResponse:
self.ar = authn_response(self.conf, "http://lingon.catalogix.se:8087/") self.ar = authn_response(self.conf, "http://lingon.catalogix.se:8087/")
def test_verify_1(self): def test_verify_1(self):
xml_response = ("%s" % (self._resp_,)).split("\n")[1] xml_response = "%s" % (self._resp_,)
print xml_response
self.ar.outstanding_queries = {"id12": "http://localhost:8088/sso"} self.ar.outstanding_queries = {"id12": "http://localhost:8088/sso"}
self.ar.timeslack = 10000 self.ar.timeslack = 10000
self.ar.loads(xml_response, decode=False) self.ar.loads(xml_response, decode=False)
@@ -58,12 +60,12 @@ 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 == IDENTITY assert self.ar.ava["eduPersonAffiliation"] == IDENTITY["eduPersonAffiliation"]
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'
def test_verify_signed_1(self): def test_verify_signed_1(self):
xml_response = ("%s" % (self._sign_resp_,)).split("\n",1)[1] xml_response = self._sign_resp_
print xml_response print xml_response
self.ar.outstanding_queries = {"id12": "http://localhost:8088/sso"} self.ar.outstanding_queries = {"id12": "http://localhost:8088/sso"}
@@ -74,7 +76,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 == IDENTITY assert self.ar.ava["sn"] == IDENTITY["surName"]
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
@@ -95,7 +97,7 @@ class TestAuthnResponse:
assert self.ar.name_id assert self.ar.name_id
def test_verify_w_authn(self): def test_verify_w_authn(self):
xml_response = ("%s" % (self._resp_authn,)).split("\n",1)[1] xml_response = "%s" % (self._resp_authn,)
self.ar.outstanding_queries = {"id12": "http://localhost:8088/sso"} self.ar.outstanding_queries = {"id12": "http://localhost:8088/sso"}
self.ar.return_addr = "http://lingon.catalogix.se:8087/" self.ar.return_addr = "http://lingon.catalogix.se:8087/"
self.ar.entity_id = "urn:mace:example.com:saml:roland:sp" self.ar.entity_id = "urn:mace:example.com:saml:roland:sp"

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from saml2.samlp import response_from_string
from saml2.server import Server, Identifier from saml2.server import Server, Identifier
from saml2 import samlp, saml, client, config from saml2 import samlp, saml, client, config
@@ -55,6 +56,8 @@ class TestIdentifier():
if os.path.exists("foobar.db"): if os.path.exists("foobar.db"):
os.unlink("foobar.db") os.unlink("foobar.db")
class TestServer1(): class TestServer1():
def setup_class(self): def setup_class(self):
self.server = Server("idp_conf") self.server = Server("idp_conf")
@@ -63,6 +66,9 @@ class TestServer1():
conf.load_file("server_conf") conf.load_file("server_conf")
self.client = client.Saml2Client(conf) self.client = client.Saml2Client(conf)
def teardown_class(self):
self.server.close_shelve_db()
def test_issuer(self): def test_issuer(self):
issuer = self.server.issuer() issuer = self.server.issuer()
assert isinstance(issuer, saml.Issuer) assert isinstance(issuer, saml.Issuer)
@@ -197,7 +203,8 @@ class TestServer1():
{"eduPersonEntitlement": "Short stop", {"eduPersonEntitlement": "Short stop",
"surName": "Jeter", "surName": "Jeter",
"givenName": "Derek", "givenName": "Derek",
"mail": "derek.jeter@nyy.mlb.com"}, "mail": "derek.jeter@nyy.mlb.com",
"title": "The man"},
name_id, name_id,
policy= self.server.conf.getattr("policy") policy= self.server.conf.getattr("policy")
) )
@@ -219,13 +226,15 @@ 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) == 4 assert len(attribute_statement.attribute) == 5
attribute = attribute_statement.attribute[0] # Pick out one attribute
assert len(attribute.attribute_value) == 1 for attr in attribute_statement.attribute:
assert attribute.friendly_name == "eduPersonEntitlement" if attr.friendly_name == "edupersonentitlement":
assert attribute.name == "urn:oid:1.3.6.1.4.1.5923.1.1.1.7" break
assert attribute.name_format == "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" assert len(attr.attribute_value) == 1
value = attribute.attribute_value[0] assert attr.name == "urn:oid:1.3.6.1.4.1.5923.1.1.1.7"
assert attr.name_format == "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
value = attr.attribute_value[0]
assert value.text.strip() == "Short stop" assert value.text.strip() == "Short stop"
assert value.get_type() == "xs:string" assert value.get_type() == "xs:string"
assert assertion.subject assert assertion.subject
@@ -282,7 +291,7 @@ class TestServer1():
self.client = client.Saml2Client(conf) self.client = client.Saml2Client(conf)
ava = { "givenName": ["Derek"], "surName": ["Jeter"], ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]} "mail": ["derek@nyy.mlb.com"], "title": "The man"}
npolicy = samlp.NameIDPolicy(format=saml.NAMEID_FORMAT_TRANSIENT, npolicy = samlp.NameIDPolicy(format=saml.NAMEID_FORMAT_TRANSIENT,
allow_create="true") allow_create="true")
@@ -306,14 +315,14 @@ class TestServer1():
assert len(assertion.attribute_statement) == 1 assert len(assertion.attribute_statement) == 1
astate = assertion.attribute_statement[0] astate = assertion.attribute_statement[0]
print astate print astate
assert len(astate.attribute) == 3 assert len(astate.attribute) == 4
def test_signed_response(self): def test_signed_response(self):
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"], ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]} "mail": ["derek@nyy.mlb.com"], "title": "The man"}
signed_resp = self.server.create_response( signed_resp = self.server.create_response(
"id12", # in_response_to "id12", # in_response_to
@@ -324,12 +333,13 @@ class TestServer1():
sign_assertion=True sign_assertion=True
) )
print "%s" % signed_resp print signed_resp
assert signed_resp assert signed_resp
sresponse = response_from_string(signed_resp)
# It's the assertions that are signed not the response per se # It's the assertions that are signed not the response per se
assert len(signed_resp.assertion) == 1 assert len(sresponse.assertion) == 1
assertion = signed_resp.assertion[0] assertion = sresponse.assertion[0]
# Since the reponse is created dynamically I don't know the signature # Since the reponse is created dynamically I don't know the signature
# value. Just that there should be one # value. Just that there should be one
@@ -384,20 +394,25 @@ class TestServer1():
_ = s_utils.deflate_and_base64_encode("%s" % (logout_request,)) _ = s_utils.deflate_and_base64_encode("%s" % (logout_request,))
saml_soap = make_soap_enveloped_saml_thingy(logout_request) saml_soap = make_soap_enveloped_saml_thingy(logout_request)
self.server.close_shelve_db()
idp = Server("idp_soap_conf") idp = Server("idp_soap_conf")
request = idp.parse_logout_request(saml_soap) request = idp.parse_logout_request(saml_soap)
idp.close_shelve_db()
assert request assert request
#------------------------------------------------------------------------ #------------------------------------------------------------------------
IDENTITY = {"eduPersonAffiliation": ["staff", "member"], IDENTITY = {"eduPersonAffiliation": ["staff", "member"],
"surName": ["Jeter"], "givenName": ["Derek"], "surName": ["Jeter"], "givenName": ["Derek"],
"mail": ["foo@gmail.com"]} "mail": ["foo@gmail.com"], "title": "The man"}
class TestServer2(): class TestServer2():
def setup_class(self): def setup_class(self):
self.server = Server("restrictive_idp_conf") self.server = Server("restrictive_idp_conf")
def teardown_class(self):
self.server.close_shelve_db()
def test_do_aa_reponse(self): def test_do_aa_reponse(self):
aa_policy = self.server.conf.getattr("policy", "idp") aa_policy = self.server.conf.getattr("policy", "idp")
print aa_policy.__dict__ print aa_policy.__dict__

View File

@@ -157,15 +157,7 @@ class TestClient:
assert nameid.format == saml.NAMEID_FORMAT_TRANSIENT assert nameid.format == saml.NAMEID_FORMAT_TRANSIENT
assert nameid.text == "_e7b68a04488f715cda642fbdd90099f5" assert nameid.text == "_e7b68a04488f715cda642fbdd90099f5"
def test_attribute_query(self):
resp = self.client.do_attribute_query(
"urn:mace:example.com:saml:roland:idp",
"_e7b68a04488f715cda642fbdd90099f5",
nameid_format=saml.NAMEID_FORMAT_TRANSIENT)
# since no one is answering on the other end
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",
# location="https://idp.umu.se/") # location="https://idp.umu.se/")
@@ -254,7 +246,7 @@ 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"], "title":["The man"]}
nameid_policy=samlp.NameIDPolicy(allow_create="false", nameid_policy=samlp.NameIDPolicy(allow_create="false",
format=saml.NAMEID_FORMAT_PERSISTENT) format=saml.NAMEID_FORMAT_PERSISTENT)
@@ -281,7 +273,8 @@ class TestClient:
print session_info print session_info
assert session_info["ava"] == {'mail': ['derek@nyy.mlb.com'], assert session_info["ava"] == {'mail': ['derek@nyy.mlb.com'],
'givenName': ['Derek'], 'givenName': ['Derek'],
'surName': ['Jeter']} 'sn': ['Jeter'],
'title': ["The man"]}
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)
@@ -297,7 +290,7 @@ 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"], "title": ["outfielder"]}
resp_str = "%s" % self.server.create_authn_response( resp_str = "%s" % self.server.create_authn_response(
identity=ava, identity=ava,
@@ -323,8 +316,7 @@ 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.metadata.idps() print self.client.metadata.with_descriptor("idpsso")
print self.client.config.idps()
location = self.client._sso_location() location = self.client._sso_location()
print location print location
assert location == 'http://localhost:8088/sso' assert location == 'http://localhost:8088/sso'
@@ -335,266 +327,277 @@ class TestClient:
print my_name print my_name
assert my_name == "urn:mace:example.com:saml:roland:sp" assert my_name == "urn:mace:example.com:saml:roland:sp"
def test_authenticate(self): # Below can only be done with dummy Server
print self.client.config.idps() # def test_attribute_query(self):
id, response = self.client.do_authenticate( # resp = self.client.do_attribute_query(
"urn:mace:example.com:saml:roland:idp", # "urn:mace:example.com:saml:roland:idp",
"http://www.example.com/relay_state") # "_e7b68a04488f715cda642fbdd90099f5",
assert response[0] == "Location" # nameid_format=saml.NAMEID_FORMAT_TRANSIENT)
o = urlparse(response[1]) #
qdict = parse_qs(o.query) # # since no one is answering on the other end
assert _leq(qdict.keys(), ['SAMLRequest', 'RelayState']) # assert resp is None
saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0]) # def test_authenticate(self):
print saml_request # print self.client.metadata.with_descriptor("idpsso")
authnreq = samlp.authn_request_from_string(saml_request) # id, response = self.client.do_authenticate(
# "urn:mace:example.com:saml:roland:idp",
def test_authenticate_no_args(self): # "http://www.example.com/relay_state")
id, response = self.client.do_authenticate(relay_state="http://www.example.com/relay_state") # 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) # 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]) # print saml_request
assert qdict["RelayState"][0] == "http://www.example.com/relay_state" # authnreq = samlp.authn_request_from_string(saml_request)
print saml_request #
authnreq = samlp.authn_request_from_string(saml_request) # def test_authenticate_no_args(self):
print authnreq.keyswv() # id, response = self.client.do_authenticate(relay_state="http://www.example.com/relay_state")
assert authnreq.destination == "http://localhost:8088/sso" # assert response[0] == "Location"
assert authnreq.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/" # o = urlparse(response[1])
assert authnreq.provider_name == "urn:mace:example.com:saml:roland:sp" # qdict = parse_qs(o.query)
assert authnreq.protocol_binding == BINDING_HTTP_REDIRECT # assert _leq(qdict.keys(), ['SAMLRequest', 'RelayState'])
name_id_policy = authnreq.name_id_policy # saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0])
assert name_id_policy.allow_create == "false" # assert qdict["RelayState"][0] == "http://www.example.com/relay_state"
assert name_id_policy.format == NAMEID_FORMAT_PERSISTENT # print saml_request
issuer = authnreq.issuer # authnreq = samlp.authn_request_from_string(saml_request)
assert issuer.text == "urn:mace:example.com:saml:roland:sp" # print authnreq.keyswv()
# assert authnreq.destination == "http://localhost:8088/sso"
# assert authnreq.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
def test_logout_1(self): # assert authnreq.provider_name == "urn:mace:example.com:saml:roland:sp"
""" one IdP/AA with BINDING_HTTP_REDIRECT on single_logout_service""" # assert authnreq.protocol_binding == BINDING_HTTP_REDIRECT
# name_id_policy = authnreq.name_id_policy
# information about the user from an IdP # assert name_id_policy.allow_create == "false"
session_info = { # assert name_id_policy.format == NAMEID_FORMAT_PERSISTENT
"name_id": "123456", # issuer = authnreq.issuer
"issuer": "urn:mace:example.com:saml:roland:idp", # assert issuer.text == "urn:mace:example.com:saml:roland:sp"
"not_on_or_after": in_a_while(minutes=15), #
"ava": { #
"givenName": "Anders", # def test_logout_1(self):
"surName": "Andersson", # """ one IdP/AA with BINDING_HTTP_REDIRECT on single_logout_service"""
"mail": "anders.andersson@example.com" #
} # # information about the user from an IdP
} # session_info = {
self.client.users.add_information_about_person(session_info) # "name_id": "123456",
entity_ids = self.client.users.issuers_of_info("123456") # "issuer": "urn:mace:example.com:saml:roland:idp",
assert entity_ids == ["urn:mace:example.com:saml:roland:idp"] # "not_on_or_after": in_a_while(minutes=15),
resp = self.client.global_logout("123456", "Tired", # "ava": {
in_a_while(minutes=5)) # "givenName": "Anders",
print resp # "surName": "Andersson",
assert resp # "mail": "anders.andersson@example.com"
assert resp[0] # a session_id # }
assert resp[1] == '200 OK' # }
assert resp[2] == [('Content-type', 'text/html')] # self.client.users.add_information_about_person(session_info)
assert resp[3][0] == '<head>' # entity_ids = self.client.users.issuers_of_info("123456")
assert resp[3][1] == '<title>SAML 2.0 POST</title>' # assert entity_ids == ["urn:mace:example.com:saml:roland:idp"]
session_info = self.client.state[resp[0]] # resp = self.client.global_logout("123456", "Tired",
print session_info # in_a_while(minutes=5))
assert session_info["entity_id"] == entity_ids[0] # print resp
assert session_info["subject_id"] == "123456" # assert resp
assert session_info["reason"] == "Tired" # assert resp[0] # a session_id
assert session_info["operation"] == "SLO" # assert resp[1] == '200 OK'
assert session_info["entity_ids"] == entity_ids # assert resp[2] == [('Content-type', 'text/html')]
assert session_info["sign"] == True # assert resp[3][0] == '<head>'
# assert resp[3][1] == '<title>SAML 2.0 POST</title>'
def test_logout_2(self): # session_info = self.client.state[resp[0]]
""" one IdP/AA with BINDING_SOAP, can't actually send something""" # print session_info
# assert session_info["entity_id"] == entity_ids[0]
conf = config.SPConfig() # assert session_info["subject_id"] == "123456"
conf.load_file("server2_conf") # assert session_info["reason"] == "Tired"
client = Saml2Client(conf) # assert session_info["operation"] == "SLO"
# assert session_info["entity_ids"] == entity_ids
# information about the user from an IdP # assert session_info["sign"] == True
session_info = { #
"name_id": "123456", # def test_logout_2(self):
"issuer": "urn:mace:example.com:saml:roland:idp", # """ one IdP/AA with BINDING_SOAP, can't actually send something"""
"not_on_or_after": in_a_while(minutes=15), #
"ava": { # conf = config.SPConfig()
"givenName": "Anders", # conf.load_file("server2_conf")
"surName": "Andersson", # client = Saml2Client(conf)
"mail": "anders.andersson@example.com" #
} # # information about the user from an IdP
} # session_info = {
client.users.add_information_about_person(session_info) # "name_id": "123456",
entity_ids = self.client.users.issuers_of_info("123456") # "issuer": "urn:mace:example.com:saml:roland:idp",
assert entity_ids == ["urn:mace:example.com:saml:roland:idp"] # "not_on_or_after": in_a_while(minutes=15),
destinations = client.config.single_logout_services(entity_ids[0], # "ava": {
BINDING_SOAP) # "givenName": "Anders",
print destinations # "surName": "Andersson",
assert destinations == ['http://localhost:8088/slo'] # "mail": "anders.andersson@example.com"
# }
# Will raise an error since there is noone at the other end. # }
raises(LogoutError, 'client.global_logout("123456", "Tired", in_a_while(minutes=5))') # client.users.add_information_about_person(session_info)
# entity_ids = self.client.users.issuers_of_info("123456")
def test_logout_3(self): # assert entity_ids == ["urn:mace:example.com:saml:roland:idp"]
""" two or more IdP/AA with BINDING_HTTP_REDIRECT""" # destinations = client.config.single_logout_services(entity_ids[0],
# BINDING_SOAP)
conf = config.SPConfig() # print destinations
conf.load_file("server3_conf") # assert destinations == ['http://localhost:8088/slo']
client = Saml2Client(conf) #
# # Will raise an error since there is noone at the other end.
# information about the user from an IdP # raises(LogoutError, 'client.global_logout("123456", "Tired", in_a_while(minutes=5))')
session_info_authn = { #
"name_id": "123456", # def test_logout_3(self):
"issuer": "urn:mace:example.com:saml:roland:idp", # """ two or more IdP/AA with BINDING_HTTP_REDIRECT"""
"not_on_or_after": in_a_while(minutes=15), #
"ava": { # conf = config.SPConfig()
"givenName": "Anders", # conf.load_file("server3_conf")
"surName": "Andersson", # client = Saml2Client(conf)
"mail": "anders.andersson@example.com" #
} # # information about the user from an IdP
} # session_info_authn = {
client.users.add_information_about_person(session_info_authn) # "name_id": "123456",
session_info_aa = { # "issuer": "urn:mace:example.com:saml:roland:idp",
"name_id": "123456", # "not_on_or_after": in_a_while(minutes=15),
"issuer": "urn:mace:example.com:saml:roland:aa", # "ava": {
"not_on_or_after": in_a_while(minutes=15), # "givenName": "Anders",
"ava": { # "surName": "Andersson",
"eduPersonEntitlement": "Foobar", # "mail": "anders.andersson@example.com"
} # }
} # }
client.users.add_information_about_person(session_info_aa) # client.users.add_information_about_person(session_info_authn)
entity_ids = client.users.issuers_of_info("123456") # session_info_aa = {
assert _leq(entity_ids, ["urn:mace:example.com:saml:roland:idp", # "name_id": "123456",
"urn:mace:example.com:saml:roland:aa"]) # "issuer": "urn:mace:example.com:saml:roland:aa",
resp = client.global_logout("123456", "Tired", in_a_while(minutes=5)) # "not_on_or_after": in_a_while(minutes=15),
print resp # "ava": {
assert resp # "eduPersonEntitlement": "Foobar",
assert resp[0] # a session_id # }
assert resp[1] == '200 OK' # }
# HTTP POST # client.users.add_information_about_person(session_info_aa)
assert resp[2] == [('Content-type', 'text/html')] # entity_ids = client.users.issuers_of_info("123456")
assert resp[3][0] == '<head>' # assert _leq(entity_ids, ["urn:mace:example.com:saml:roland:idp",
assert resp[3][1] == '<title>SAML 2.0 POST</title>' # "urn:mace:example.com:saml:roland:aa"])
# resp = client.global_logout("123456", "Tired", in_a_while(minutes=5))
state_info = client.state[resp[0]] # print resp
print state_info # assert resp
assert state_info["entity_id"] == entity_ids[0] # assert resp[0] # a session_id
assert state_info["subject_id"] == "123456" # assert resp[1] == '200 OK'
assert state_info["reason"] == "Tired" # # HTTP POST
assert state_info["operation"] == "SLO" # assert resp[2] == [('Content-type', 'text/html')]
assert state_info["entity_ids"] == entity_ids # assert resp[3][0] == '<head>'
assert state_info["sign"] == True # assert resp[3][1] == '<title>SAML 2.0 POST</title>'
#
def test_authz_decision_query(self): # state_info = client.state[resp[0]]
conf = config.SPConfig() # print state_info
conf.load_file("server3_conf") # assert state_info["entity_id"] == entity_ids[0]
client = Saml2Client(conf) # assert state_info["subject_id"] == "123456"
# assert state_info["reason"] == "Tired"
AVA = {'mail': u'roland.hedberg@adm.umu.se', # assert state_info["operation"] == "SLO"
'eduPersonTargetedID': '95e9ae91dbe62d35198fbbd5e1fb0976', # assert state_info["entity_ids"] == entity_ids
'displayName': u'Roland Hedberg', # assert state_info["sign"] == True
'uid': 'http://roland.hedberg.myopenid.com/'} #
# def test_authz_decision_query(self):
sp_entity_id = "sp_entity_id" # conf = config.SPConfig()
in_response_to = "1234" # conf.load_file("server3_conf")
consumer_url = "http://example.com/consumer" # client = Saml2Client(conf)
name_id = saml.NameID(saml.NAMEID_FORMAT_TRANSIENT, text="name_id") #
policy = Policy() # AVA = {'mail': u'roland.hedberg@adm.umu.se',
ava = Assertion(AVA) # 'eduPersonTargetedID': '95e9ae91dbe62d35198fbbd5e1fb0976',
assertion = ava.construct(sp_entity_id, in_response_to, # 'displayName': u'Roland Hedberg',
consumer_url, name_id, # 'uid': 'http://roland.hedberg.myopenid.com/'}
conf.attribute_converters, #
policy, issuer=client._issuer()) # sp_entity_id = "sp_entity_id"
# in_response_to = "1234"
adq = client.create_authz_decision_query_using_assertion("entity_id", # consumer_url = "http://example.com/consumer"
assertion, # name_id = saml.NameID(saml.NAMEID_FORMAT_TRANSIENT, text="name_id")
"read", # policy = Policy()
"http://example.com/text") # ava = Assertion(AVA)
# assertion = ava.construct(sp_entity_id, in_response_to,
assert adq # consumer_url, name_id,
print adq # conf.attribute_converters,
assert adq.keyswv() != [] # policy, issuer=client._issuer())
assert adq.destination == "entity_id" #
assert adq.resource == "http://example.com/text" # adq = client.create_authz_decision_query_using_assertion("entity_id",
assert adq.action[0].text == "read" # assertion,
# "read",
def test_request_to_discovery_service(self): # "http://example.com/text")
disc_url = "http://example.com/saml2/idp/disc" #
url = discovery_service_request_url("urn:mace:example.com:saml:roland:sp", # assert adq
disc_url) # print adq
print url # assert adq.keyswv() != []
assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp" # assert adq.destination == "entity_id"
# assert adq.resource == "http://example.com/text"
url = discovery_service_request_url( # assert adq.action[0].text == "read"
self.client.config.entityid, #
disc_url, # def test_request_to_discovery_service(self):
return_url= "http://example.org/saml2/sp/ds") # disc_url = "http://example.com/saml2/idp/disc"
# url = discovery_service_request_url("urn:mace:example.com:saml:roland:sp",
print url # disc_url)
assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp&return=http%3A%2F%2Fexample.org%2Fsaml2%2Fsp%2Fds" # print url
# assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp"
def test_get_idp_from_discovery_service(self): #
pdir = {"entityID": "http://example.org/saml2/idp/sso"} # url = discovery_service_request_url(
params = urllib.urlencode(pdir) # self.client.config.entityid,
redirect_url = "http://example.com/saml2/sp/disc?%s" % params # disc_url,
# return_url= "http://example.org/saml2/sp/ds")
entity_id = discovery_service_response(url=redirect_url) #
assert entity_id == "http://example.org/saml2/idp/sso" # print url
# assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp&return=http%3A%2F%2Fexample.org%2Fsaml2%2Fsp%2Fds"
pdir = {"idpID": "http://example.org/saml2/idp/sso"} #
params = urllib.urlencode(pdir) # def test_get_idp_from_discovery_service(self):
redirect_url = "http://example.com/saml2/sp/disc?%s" % params # pdir = {"entityID": "http://example.org/saml2/idp/sso"}
# params = urllib.urlencode(pdir)
entity_id = discovery_service_response(url=redirect_url, # redirect_url = "http://example.com/saml2/sp/disc?%s" % params
returnIDParam="idpID") #
# 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"
#
def test_unsolicited_response(self): # pdir = {"idpID": "http://example.org/saml2/idp/sso"}
""" # params = urllib.urlencode(pdir)
# redirect_url = "http://example.com/saml2/sp/disc?%s" % params
""" #
self.server = Server("idp_conf") # entity_id = discovery_service_response(url=redirect_url,
# returnIDParam="idpID")
conf = config.SPConfig() #
conf.load_file("server_conf") # assert entity_id == "http://example.org/saml2/idp/sso"
self.client = Saml2Client(conf) # self.server.close_shelve_db()
#
for subject in self.client.users.subjects(): # def test_unsolicited_response(self):
self.client.users.remove_person(subject) # """
#
IDP = "urn:mace:example.com:saml:roland:idp" # """
# self.server = Server("idp_conf")
ava = { "givenName": ["Derek"], "surName": ["Jeter"], #
"mail": ["derek@nyy.mlb.com"]} # conf = config.SPConfig()
# conf.load_file("server_conf")
resp_str = "%s" % self.server.create_authn_response( # self.client = Saml2Client(conf)
identity=ava, #
in_response_to="id1", # for subject in self.client.users.subjects():
destination="http://lingon.catalogix.se:8087/", # self.client.users.remove_person(subject)
sp_entity_id="urn:mace:example.com:saml:roland:sp", #
name_id_policy=samlp.NameIDPolicy( # IDP = "urn:mace:example.com:saml:roland:idp"
format=saml.NAMEID_FORMAT_PERSISTENT), #
userid="foba0001@example.com") # ava = { "givenName": ["Derek"], "surName": ["Jeter"],
# "mail": ["derek@nyy.mlb.com"], "title": ["The man"]}
resp_str = base64.encodestring(resp_str) #
# resp_str = "%s" % self.server.create_authn_response(
self.client.allow_unsolicited = True # identity=ava,
authn_response = self.client.authn_request_response( # in_response_to="id1",
{"SAMLResponse":resp_str}, ()) # destination="http://lingon.catalogix.se:8087/",
# sp_entity_id="urn:mace:example.com:saml:roland:sp",
assert authn_response is not None # name_id_policy=samlp.NameIDPolicy(
assert authn_response.issuer() == IDP # format=saml.NAMEID_FORMAT_PERSISTENT),
assert authn_response.response.assertion[0].issuer.text == IDP # userid="foba0001@example.com")
session_info = authn_response.session_info() #
# resp_str = base64.encodestring(resp_str)
print session_info #
assert session_info["ava"] == {'mail': ['derek@nyy.mlb.com'], # self.client.allow_unsolicited = True
'givenName': ['Derek'], # authn_response = self.client.authn_request_response(
'surName': ['Jeter']} # {"SAMLResponse":resp_str}, ())
assert session_info["issuer"] == IDP #
assert session_info["came_from"] == "" # assert authn_response is not None
response = samlp.response_from_string(authn_response.xmlstr) # assert authn_response.issuer() == IDP
assert response.destination == "http://lingon.catalogix.se:8087/" # assert authn_response.response.assertion[0].issuer.text == IDP
# session_info = authn_response.session_info()
# One person in the cache #
assert len(self.client.users.subjects()) == 1 # print session_info
# assert session_info["ava"] == {'mail': ['derek@nyy.mlb.com'],
# 'givenName': ['Derek'],
# 'surName': ['Jeter']}
# assert session_info["issuer"] == IDP
# assert session_info["came_from"] == ""
# response = samlp.response_from_string(authn_response.xmlstr)
# assert response.destination == "http://lingon.catalogix.se:8087/"
#
# # One person in the cache
# assert len(self.client.users.subjects()) == 1
# self.server.close_shelve_db()

View File

@@ -46,7 +46,7 @@ class TestSP():
# 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"], "title":["The man"]}
resp_str = "%s" % self.server.create_authn_response(ava, "id1", resp_str = "%s" % self.server.create_authn_response(ava, "id1",
"http://lingon.catalogix.se:8087/", "http://lingon.catalogix.se:8087/",
@@ -62,4 +62,5 @@ 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'],
'surName': ['Jeter']} 'sn': ['Jeter'],
'title': ['The man']}

View File

@@ -1,6 +1,5 @@
__author__ = 'rolandh' __author__ = 'rolandh'
from saml2.virtual_org import VirtualOrg
from saml2 import config from saml2 import config
from saml2.client import Saml2Client from saml2.client import Saml2Client
from saml2.time_util import str_to_time, in_a_while from saml2.time_util import str_to_time, in_a_while
@@ -12,7 +11,7 @@ def add_derek_info(sp):
not_on_or_after = str_to_time(in_a_while(days=1)) not_on_or_after = str_to_time(in_a_while(days=1))
session_info = SESSION_INFO_PATTERN.copy() session_info = SESSION_INFO_PATTERN.copy()
session_info["ava"] = {"givenName":["Derek"], "umuselin":["deje0001"]} session_info["ava"] = {"givenName":["Derek"], "umuselin":["deje0001"]}
session_info["issuer"] = "https://toylan3.umdc.umu.se/shibboleth" session_info["issuer"] = "urn:mace:example.com:saml:idp"
session_info["name_id"] = "abcdefgh" session_info["name_id"] = "abcdefgh"
session_info["not_on_or_after"] = not_on_or_after session_info["not_on_or_after"] = not_on_or_after
# subject_id, entity_id, info, timestamp # subject_id, entity_id, info, timestamp
@@ -31,14 +30,13 @@ class TestVirtualOrg():
def test_mta(self): def test_mta(self):
aas = self.vo.members_to_ask("abcdefgh") aas = self.vo.members_to_ask("abcdefgh")
print aas print aas
assert len(aas) == 2 assert len(aas) == 1
assert 'urn:mace:example.com:saml:aa' in aas assert 'urn:mace:example.com:saml:aa' in aas
assert 'urn:mace:example.com:saml:idp' in aas
def test_unknown_subject(self): def test_unknown_subject(self):
aas = self.vo.members_to_ask("01234567") aas = self.vo.members_to_ask("01234567")
print aas print aas
assert len(aas) == 0 assert len(aas) == 2
def test_id(self): def test_id(self):
id = self.vo.get_common_identifier("abcdefgh") id = self.vo.get_common_identifier("abcdefgh")
@@ -60,14 +58,13 @@ class TestVirtualOrg_2():
def test_mta(self): def test_mta(self):
aas = self.sp.vorg.members_to_ask("abcdefgh") aas = self.sp.vorg.members_to_ask("abcdefgh")
print aas print aas
assert len(aas) == 2 assert len(aas) == 1
assert 'urn:mace:example.com:saml:aa' in aas assert 'urn:mace:example.com:saml:aa' in aas
assert 'urn:mace:example.com:saml:idp' in aas
def test_unknown_subject(self): def test_unknown_subject(self):
aas = self.sp.vorg.members_to_ask("01234567") aas = self.sp.vorg.members_to_ask("01234567")
print aas print aas
assert len(aas) == 0 assert len(aas) == 2
def test_id(self): def test_id(self):
id = self.sp.vorg.get_common_identifier("abcdefgh") id = self.sp.vorg.get_common_identifier("abcdefgh")

View File

@@ -1,12 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
import sys import sys
from saml2 import metadata
from saml2 import saml from saml2 import saml
from saml2 import md from saml2 import md
from saml2.attribute_converter import ac_factory
from saml2.mdie import to_dict
from saml2.extension import mdui from saml2.extension import mdui
from saml2.extension import idpdisc from saml2.extension import idpdisc
from saml2.extension import dri from saml2.extension import dri
@@ -15,28 +11,31 @@ from saml2.extension import ui
import xmldsig import xmldsig
import xmlenc import xmlenc
from saml2.mdstore import MetaDataFile, MetaDataExtern
__author__ = 'rolandh' __author__ = 'rolandh'
""" """
A script that imports and verifies metadata and dumps it in a basic A script that imports and verifies metadata and then dumps it in a basic
dictionary format. dictionary format.
""" """
MDIMPORT = { MDIMPORT = {
"swamid": { "swamid": {
"url": "https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2", "url": "https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2",
"cert":"kalmar2.pem" "cert":"kalmar2.pem",
"type": "external"
}, },
"incommon": { "incommon": {
"url": "file://InCommon-metadata.xml" "file": "InCommon-metadata.xml",
"type": "local"
}, },
"test": { "test": {
"url": "file://mdtest.xml" "file": "mdtest.xml",
"type": "local"
} }
} }
ATTRCONV = ac_factory("attributemaps")
ONTS = { ONTS = {
saml.NAMESPACE: saml, saml.NAMESPACE: saml,
mdui.NAMESPACE: mdui, mdui.NAMESPACE: mdui,
@@ -49,20 +48,17 @@ ONTS = {
xmlenc.NAMESPACE: xmlenc xmlenc.NAMESPACE: xmlenc
} }
item = MDIMPORT[sys.argv[1]]
metad = metadata.MetaData(xmlsec_binary="/opt/local/bin/xmlsec1", metad = None
attrconv=ATTRCONV)
for src in sys.argv[1:]: if item["type"] == "local":
spec = MDIMPORT[src] metad = MetaDataFile(sys.argv[1], ONTS.values(), item["file"])
url = spec["url"] elif item["type"] == "external":
if url.startswith("file://"): metad = MetaDataExtern(sys.argv[1], ONTS.values(),
metad.import_metadata(open(url[7:]).read(), src) item["url"], "/opt/local/bin/xmlsec1", item["cert"])
else:
metad.import_external_metadata(url, spec["cert"])
_dict = to_dict(metad.entity, ONTS.values()) if metad:
metad.load()
import json print metad.dumps()
print json.dumps(_dict, indent=2)