Refactoring spree

This commit is contained in:
Roland Hedberg
2010-03-25 17:07:01 +01:00
parent 55a86ee1bc
commit 8e23bd2b0a
15 changed files with 243 additions and 829 deletions

View File

@@ -98,7 +98,7 @@ class SAML2Plugin(FormPluginBase):
session_info["not_on_or_after"])
return name_id
def _pick_idp(self):
def _pick_idp(self, environ):
"""
If more than one idp and if none is selected, I have to do wayf
"""
@@ -147,7 +147,7 @@ class SAML2Plugin(FormPluginBase):
self.log and self.log.info("VO: %s" % vorg)
# If more than one idp and if none is selected, I have to do wayf
idp_url = self._pick_idp()
idp_url = self._pick_idp(environ)
# Do the AuthnRequest
scl = Saml2Client(environ, self.conf)
@@ -169,7 +169,7 @@ class SAML2Plugin(FormPluginBase):
else :
HTTPInternalServerError(detail='Incorrect returned data')
def _get_post(self):
def _get_post(self, environ):
""" Get the posted information """
post_env = environ.copy()
post_env['QUERY_STRING'] = ''
@@ -219,7 +219,7 @@ class SAML2Plugin(FormPluginBase):
if self.debug:
self.log and self.log.info('identify query: %s' % (query,))
post = self._get_post()
post = self._get_post(environ)
# Not for me, put the post back where next in line can find it
try:

View File

@@ -36,6 +36,8 @@ from saml2.sigver import pre_signature_part, sign_assertion_using_xmlsec
from saml2.sigver import sign_statement_using_xmlsec
from saml2.soap import SOAPClient
from saml2.attribute_converter import to_local
DEFAULT_BINDING = saml2.BINDING_HTTP_REDIRECT
FORM_SPEC = """<form method="post" action="%s">
@@ -365,11 +367,12 @@ class Saml2Client(object):
lax)
# The assertion can contain zero or one attributeStatements
assert len(assertion.attribute_statement) <= 1
if assertion.attribute_statement:
ava = get_attribute_values(assertion.attribute_statement[0])
else:
if not assertion.attribute_statement:
ava = {}
else:
assert len(assertion.attribute_statement) == 1
ava = to_local(self.config.attribute_converters(),
assertion.attribute_statement[0])
log and log.info("AVA: %s" % (ava,))
@@ -391,7 +394,7 @@ class Saml2Client(object):
# The subject must contain a name_id
assert subject.name_id
name_id = subject.name_id.text.strip()
return {"ava":ava, "name_id":name_id, "came_from":came_from,
"not_on_or_after":not_on_or_after}
@@ -599,28 +602,6 @@ def for_me(condition, myself ):
if audience.text.strip() == myself:
return True
def get_attribute_values(attribute_statement):
""" Get the attributes and the attribute values
:param response: The AttributeStatement.
:return: A dictionary containing attributes and values
"""
result = {}
for attribute in attribute_statement.attribute:
# Check name_format ??
try:
name = attribute.friendly_name.strip()
except AttributeError:
name = attribute.name.strip()
result[name] = []
for value in attribute.attribute_value:
if not value.text:
result[name].append('')
else:
result[name].append(value.text.strip())
return result
ROW = """<tr><td>%s</td><td>%s</td></tr>"""
def _print_statement(statem):

View File

@@ -3,6 +3,8 @@
#
from saml2 import metadata, utils
from saml2.assertion import Policy
from saml2.attribute_converter import ac_factory, AttributeConverter
import re
class MissingValue(Exception):
@@ -18,41 +20,6 @@ def entity_id2url(meta, entity_id):
:return: An endpoint (URL)
"""
return meta.single_sign_on_services(entity_id)[0]
def do_assertions(assertions):
""" This is only for IdPs or AAs, and it's about limiting what
is returned to the SP.
In the configuration file, restrictions on which values that
can be returned are specified with the help of regular expressions.
This function goes through and pre-compile the regular expressions.
:param assertions:
:return: The assertion with the string specification replaced with
a compiled regular expression.
"""
for _, spec in assertions.items():
if spec == None:
continue
try:
restr = spec["attribute_restrictions"]
except KeyError:
continue
if restr == None:
continue
for key, values in restr.items():
if not values:
spec["attribute_restrictions"][key] = None
continue
rev = []
for value in values:
rev.append(re.compile(value))
spec["attribute_restrictions"][key] = rev
return assertions
class Config(dict):
def sp_check(self, config, metadata=None):
@@ -80,16 +47,14 @@ class Config(dict):
assert "url" in config
assert "name" in config
def idp_check(self, config):
def idp_aa_check(self, config):
assert "url" in config
if "assertions" in config:
config["assertions"] = do_assertions(config["assertions"])
def aa_check(self, config):
assert "url" in config
if "assertions" in config:
config["assertions"] = do_assertions(config["assertions"])
config["policy"] = Policy(config["assertions"])
del config["assertions"]
elif "policy" in config:
config["policy"] = Policy(config["policy"])
def load_metadata(self, metadata_conf, xmlsec_binary):
""" Loads metadata into an internal structure """
metad = metadata.MetaData(xmlsec_binary)
@@ -125,14 +90,11 @@ class Config(dict):
config["metadata"] = self.load_metadata(config["metadata"],
config["xmlsec_binary"])
if "attribute_maps" in config:
(forward, backward) = utils.parse_attribute_map(config[
"attribute_maps"])
config["am_forward"] = forward
config["am_backward"] = backward
if "attribute_map_dir" in config:
config["attrconverters"] = ac_factory(
config["attribute_map_dir"])
else:
config["am_forward"] = None
config["am_backward"] = None
config["attrconverters"] = [AttributeConverter()]
if "sp" in config["service"]:
#print config["service"]["sp"]
@@ -141,11 +103,38 @@ class Config(dict):
else:
self.sp_check(config["service"]["sp"])
if "idp" in config["service"]:
self.idp_check(config["service"]["idp"])
self.idp_aa_check(config["service"]["idp"])
if "aa" in config["service"]:
self.aa_check(config["service"]["aa"])
self.idp_aa_check(config["service"]["aa"])
for key, val in config.items():
self[key] = val
return self
return self
def services(self):
return self["service"].keys()
def idp_policy(self):
try:
return self["service"]["idp"]["policy"]
except KeyError:
return Policy()
def aa_policy(self):
try:
return self["service"]["aa"]["policy"]
except KeyError:
return Policy()
def aa_url(self):
return self["service"]["aa"]["url"]
def idp_url(self):
return self["service"]["idp"]["url"]
def vo_conf(self, name):
return self.conf["vitual_organization"][name]
def attribute_converters(self):
return self["attrconverters"]

View File

@@ -32,8 +32,11 @@ from saml2.time_util import valid
@decorator
def keep_updated(f, self, entity_id, *args, **kwargs):
#print "In keep_updated"
if not valid(self.entity[entity_id]["valid_until"]):
self.reload_entity(entity_id)
try:
if not valid(self.entity[entity_id]["valid_until"]):
self.reload_entity(entity_id)
except KeyError:
pass
return f(self, entity_id, *args, **kwargs)
@@ -195,6 +198,8 @@ class MetaData(object):
def reload_entity(self, entity_id):
for source, eids in self._import.items():
if entity_id in eids:
if source == "-":
return
self.clear_from_source(source)
if isinstance(source, basestring):
f = open(source)

View File

@@ -24,54 +24,29 @@ import shelve
from saml2 import saml, samlp, VERSION, make_instance
from saml2.utils import sid, decode_base64_and_inflate
from saml2.utils import response_factory, do_ava_statement
from saml2.utils import response_factory
from saml2.utils import MissingValue, args2dict
from saml2.utils import success_status_factory, assertion_factory
from saml2.utils import filter_attribute_value_assertions
from saml2.utils import OtherError, do_attribute_statement
from saml2.utils import VersionMismatch, UnknownPrincipal, UnsupportedBinding
from saml2.utils import filter_on_attributes, status_from_exception_factory
from saml2.utils import status_from_exception_factory
from saml2.sigver import correctly_signed_authn_request
from saml2.sigver import pre_signature_part
from saml2.time_util import instant, in_a_while
from saml2.config import Config
from saml2.cache import Cache
from saml2.cache import Cache
from saml2.assertion import Assertion, Policy
class Server(object):
""" A class that does things that IdPs or AAs do """
def __init__(self, config_file="", config=None, cache="",
log=None, debug=0):
if config_file:
self.load_config(config_file)
elif config:
self.conf = config
self.metadata = self.conf["metadata"]
if cache:
self.cache = Cache(cache)
else:
self.cache = Cache()
self.log = log
class IdentifierMap(object):
def __init__(self, dbname, debug=0, log=None):
self.map = shelve.open(dbname,writeback=True)
self.debug = debug
def load_config(self, config_file):
self.conf = Config()
self.conf.load_file(config_file)
if "subject_data" in self.conf:
self.id_map = shelve.open(self.conf["subject_data"],
writeback=True)
else:
self.id_map = None
self.log = log
def issuer(self):
return args2dict( self.conf["entityid"],
format=saml.NAMEID_FORMAT_ENTITY)
def persistent_id(self, entity_id, subject_id):
def persistent(self, entity_id, subject_id):
""" Keeps the link between a permanent identifier and a
temporary/pseudotemporary identifier for a subject
temporary/pseudo-temporary identifier for a subject
:param entity_id: SP entity ID or VO entity ID
:param subject_id: The local identifier of the subject
@@ -79,12 +54,12 @@ class Server(object):
entity_id
"""
if self.debug:
self.log and self.log.debug("Id map keys: %s" % self.id_map.keys())
self.log and self.log.debug("Id map keys: %s" % self.map.keys())
try:
emap = self.id_map[entity_id]
emap = self.map[entity_id]
except KeyError:
emap = self.id_map[entity_id] = {"forward":{}, "backward":{}}
emap = self.map[entity_id] = {"forward":{}, "backward":{}}
try:
if self.debug:
@@ -97,11 +72,43 @@ class Server(object):
break
emap["forward"][subject_id] = temp_id
emap["backward"][temp_id] = subject_id
self.id_map[entity_id] = emap
self.id_map.sync()
self.map[entity_id] = emap
self.map.sync()
return temp_id
class Server(object):
""" A class that does things that IdPs or AAs do """
def __init__(self, config_file="", config=None, cache="",
log=None, debug=0):
self.log = log
self.debug = debug
if config_file:
self.load_config(config_file)
elif config:
self.conf = config
self.metadata = self.conf["metadata"]
if cache:
self.cache = Cache(cache)
else:
self.cache = Cache()
def load_config(self, config_file):
self.conf = Config()
self.conf.load_file(config_file)
if "subject_data" in self.conf:
self.id = IdentifierMap(self.conf["subject_data"],
self.debug, self.log)
else:
self.id = None
def issuer(self):
return args2dict( self.conf["entityid"],
format=saml.NAMEID_FORMAT_ENTITY)
def parse_authn_request(self, enc_request):
"""Parse a Authentication Request
@@ -169,9 +176,8 @@ class Server(object):
query = samlp.attribute_query_from_string(xml_string)
assert query.version == VERSION
self.log and self.log.info(
"%s ?= %s" % (query.destination,
self.conf["service"]["aa"]["url"]))
assert query.destination == self.conf["service"]["aa"]["url"]
"%s ?= %s" % (query.destination, self.conf.aa_url))
assert query.destination == self.conf.aa_url
# verify signature
@@ -185,93 +191,11 @@ class Server(object):
def find_subject(self, subject, attribute=None):
pass
def _not_on_or_after(self, sp_entity_id):
""" When the assertion stops being valid, should not be
used after this time.
:return: String representation of the time
"""
if "assertions" in self.conf:
try:
spec = self.conf["assertions"][sp_entity_id]["lifetime"]
return in_a_while(**spec)
except KeyError:
try:
spec = self.conf["assertions"]["default"]["lifetime"]
return in_a_while(**spec)
except KeyError:
pass
# default is a hour
return in_a_while(**{"hours":1})
def filter_ava(self, ava, sp_entity_id, required=None, optional=None,
typ="idp"):
""" What attribute and attribute values returns depends on what
the SP has said it wants in the request or in the metadata file and
what the IdP/AA wants to release. An assumption is that what the SP
asks for overrides whatever is in the metadata. But of course the
IdP never releases anything it doesn't want to.
:param ava: All the attributes and values that are available
:param sp_entity_id: The entity ID of the SP
:param required: Attributes that the SP requires in the assertion
:param optional: Attributes that the SP thinks is optional
:param typ: IdPs and AAs does this, and they have different parts
of the configuration.
:return: A possibly modified AVA
"""
try:
assertions = self.conf["service"][typ]["assertions"]
try:
restrictions = assertions[sp_entity_id][
"attribute_restrictions"]
except KeyError:
try:
restrictions = assertions["default"][
"attribute_restrictions"]
except KeyError:
restrictions = None
except KeyError:
restrictions = None
if restrictions:
#print restrictions
ava = filter_attribute_value_assertions(ava, restrictions)
if required or optional:
ava = filter_on_attributes(ava, required, optional)
return ava
def restrict_ava(self, identity, spid):
""" Identity attribute names are expected to be expressed in
the local lingo (== friendlyName)
:param identity: A dictionary with attributes and values
:return: A filtered ava according to the IdPs/AAs rules and
the list of required/optional attributes according to the SP.
If the requirements can't be met an exception is raised.
"""
(required, optional) = self.conf["metadata"].attribute_consumer(spid)
return self.filter_ava(identity, spid, required, optional)
def _conditions(self, sp_entity_id):
return args2dict(
not_before=instant(),
# How long might depend on who's getting it
not_on_or_after=self._not_on_or_after(sp_entity_id),
audience_restriction=args2dict(
audience=args2dict(sp_entity_id)))
def _authn_statement(self):
return args2dict(authn_instant = instant(),
session_index = sid()),
def do_response(self, consumer_url, in_response_to,
sp_entity_id, identity=None, name_id=None,
status=None ):
# ------------------------------------------------------------------------
def _response(self, consumer_url, in_response_to, sp_entity_id,
identity=None, name_id=None, status=None, sign=False,
policy=Policy()):
""" Create a Response that adhers to the ??? profile.
:param consumer_url: The URL which should receive the response
@@ -279,117 +203,87 @@ class Server(object):
:param sp_entity_id: The entity identifier of the SP
:param identity: A dictionary with attributes and values that are
expected to be the bases for the assertion in the response.
:param name_id: The identifier of the subject
:param name_id: The identifier of the subject
:param status: The status of the response
:param sign: Whether the assertion should be signed or not
:param policy: The attribute release policy for this instance
:return: A Response instance
"""
if not status:
status = success_status_factory()
tmp = response_factory(
response = response_factory(
issuer=self.issuer(),
in_response_to = in_response_to,
destination = consumer_url,
status = status,
)
if identity:
attr_statement = do_ava_statement(identity,
self.conf["am_backward"])
if identity:
ast = Assertion(identity)
try:
ast.apply_policy(sp_entity_id, policy, self.metadata)
except MissingValue, exc:
response = self.error_response(consumer_url, in_response_to,
sp_entity_id, exc, name_id)
return make_instance(samlp.Response, response)
# temporary identifier or ??
if not name_id:
name_id = args2dict(sid(),format=saml.NAMEID_FORMAT_TRANSIENT)
assertion = ast.construct(sp_entity_id, in_response_to, name_id,
self.conf.attribute_converters(),
policy)
if sign:
assertion["signature"] = pre_signature_part(assertion["id"])
# start using now and for a hour
conds = self._conditions(sp_entity_id)
assertion = assertion_factory(
attribute_statement = attr_statement,
authn_statement = self._authn_statement(),
conditions = conds,
subject=args2dict(
name_id=name_id,
method=saml.SUBJECT_CONFIRMATION_METHOD_BEARER,
subject_confirmation=args2dict(
subject_confirmation_data = \
args2dict(in_response_to=in_response_to))),
),
# Store which assertion that has been sent to which SP about which
# subject.
self.cache.set(name_id["text"], sp_entity_id, assertion,
conds["not_on_or_after"])
print assertion
tmp.update({"assertion":assertion})
self.cache.set(assertion["subject"]["name_id"]["text"],
sp_entity_id, assertion,
assertion["conditions"]["not_on_or_after"])
return make_instance(samlp.Response, tmp)
response.update({"assertion":assertion})
return make_instance(samlp.Response, response)
# ----------------------------
# ------------------------------------------------------------------------
def do_response(self, consumer_url, in_response_to,
sp_entity_id, identity=None, name_id=None,
status=None, sign=False ):
return self._response(consumer_url, in_response_to,
sp_entity_id, identity, name_id,
status, sign, self.conf.idp_policy())
# ------------------------------------------------------------------------
def error_response(self, destination, in_response_to, spid, exc,
name_id = None):
return self.do_response(
name_id=None):
return self._response(
destination, # consumer_url
in_response_to, # in_response_to
spid, # sp_entity_id
None, # identity
name_id,
status = status_from_exception_factory(exc)
)
def do_aa_response(self, consumer_url, in_response_to,
sp_entity_id, identity,
name_id=None, ip_address="", issuer=None, sign=False):
# ------------------------------------------------------------------------
def do_aa_response(self, consumer_url, in_response_to, sp_entity_id,
identity=None, name_id=None, ip_address="",
issuer=None, status=None, sign=False):
try:
identity = self.restrict_ava(identity, sp_entity_id)
except MissingValue, exc:
tmp = self.error_response( consumer_url, in_response_to,
sp_entity_id, exc, name_id)
return make_instance(samlp.Response, tmp)
attr_statement = do_ava_statement(identity, self.conf["am_backward"])
# temporary identifier or ??
if not name_id:
name_id = args2dict(sid(), format=saml.NAMEID_FORMAT_TRANSIENT)
return self._response(consumer_url, in_response_to,
sp_entity_id, identity, name_id,
status, sign, policy=self.conf.aa_policy())
conds = self._conditions(sp_entity_id)
assertion = assertion_factory(
subject = args2dict(
name_id = name_id,
method = saml.SUBJECT_CONFIRMATION_METHOD_BEARER,
subject_confirmation = args2dict(
subject_confirmation_data = \
args2dict(
in_response_to = in_response_to,
not_on_or_after = self._not_on_or_after(sp_entity_id),
address = ip_address,
recipient = consumer_url))),
attribute_statement = attr_statement,
authn_statement = self._authn_statement(),
conditions = conds
)
if sign:
assertion["signature"] = pre_signature_part(assertion["id"])
#print name_id
#print conds
self.cache.set(name_id["text"], sp_entity_id, assertion,
conds["not_on_or_after"])
tmp = response_factory(
issuer=issuer,
in_response_to=in_response_to,
destination=consumer_url,
status=success_status_factory(),
assertion=assertion,
)
return make_instance(samlp.Response, tmp)
# ------------------------------------------------------------------------
def authn_response(self, identity, in_response_to, destination, spid,
name_id_policy, userid):
@@ -408,14 +302,13 @@ class Server(object):
name_id = None
if name_id_policy.sp_name_qualifier:
try:
vo_conf = self.conf["virtual_organization"][
name_id_policy.sp_name_qualifier]
vo_conf = self.conf.vo_conf(name_id_policy.sp_name_qualifier)
subj_id = identity[vo_conf["common_identifier"]]
except KeyError:
self.log.info(
"Get persistent ID (%s,%s)" % (
name_id_policy.sp_name_qualifier,userid))
subj_id = self.persistent_id(name_id_policy.sp_name_qualifier,
subj_id = self.id.persistent(name_id_policy.sp_name_qualifier,
userid)
self.log.info("=> %s" % subj_id)
@@ -423,9 +316,7 @@ class Server(object):
format=saml.NAMEID_FORMAT_PERSISTENT,
sp_name_qualifier=name_id_policy.sp_name_qualifier)
# Do attribute-value filtering
try:
identity = self.restrict_ava(identity, spid)
resp = self.do_response(
destination, # consumer_url
in_response_to, # in_response_to

View File

@@ -26,6 +26,7 @@ class OtherError(Exception):
class MissingValue(Exception):
pass
EXCEPTION2STATUS = {
VersionMismatch: samlp.STATUS_VERSION_MISMATCH,
@@ -117,36 +118,6 @@ def parse_attribute_map(filenames):
backward[friendly_name] = (name, name_format)
return (forward, backward)
def filter_attribute_value_assertions(ava, attribute_restrictions=None):
""" Will weed out attribute values and values according to the
rules defined in the attribute restrictions. If filtering results in
an attribute without values, then the attribute is removed from the
assertion.
:param ava: The incoming attribute value assertion
:param attribute_restrictions: The rules that govern which attributes
and values that are allowed.
:return: A attribute value assertion
"""
if not attribute_restrictions:
return ava
resava = {}
for attr, vals in ava.items():
if attr in attribute_restrictions:
if attribute_restrictions[attr] == None:
resava[attr] = vals
else:
rvals = []
for restr in attribute_restrictions[attr]:
for val in vals:
if restr.match(val):
rvals.append(val)
if rvals:
resava[attr] = list(set(rvals))
return resava
def identity_attribute(form, attribute, forward_map=None):
if form == "friendly":
@@ -158,85 +129,7 @@ def identity_attribute(form, attribute, forward_map=None):
except KeyError:
return attribute.name
# default is name
return attribute.name
def _filter_values(vals, required=None, optional=None):
""" Removes values from *val* that does not appear in *attributes*.
:param val: The values that are to be filtered
:param required: The required values
:param optional: The optional values
:return: The set of values after filtering
"""
if not required and not optional:
return vals
valr = []
valo = []
if required:
rvals = [v.text for v in required]
else:
rvals = []
if optional:
ovals = [v.text for v in optional]
else:
ovals = []
for val in vals:
if val in rvals:
valr.append(val)
elif val in ovals:
valo.append(val)
valo.extend(valr)
if rvals:
if len(rvals) == len(valr):
return valo
else:
raise MissingValue("Required attribute value missing")
else:
return valo
def _combine(required=None, optional=None):
res = {}
if not required:
required = []
if not optional:
optional = []
for attr in required:
part = None
for oat in optional:
if attr.name == oat.name:
part = (attr.attribute_value, oat.attribute_value)
break
if part:
res[(attr.name, attr.friendly_name)] = part
else:
res[(attr.name, attr.friendly_name)] = (attr.attribute_value, [])
for oat in optional:
tag = (oat.name, oat.friendly_name)
if tag not in res:
res[tag] = ([], oat.attribute_value)
return res
def filter_on_attributes(ava, required=None, optional=None):
""" Filter
:param required: list of RequestedAttribute instances
"""
res = {}
comb = _combine(required, optional)
for attr, vals in comb.items():
if attr[0] in ava:
res[attr[0]] = _filter_values(ava[attr[0]], vals[0], vals[1])
elif attr[1] in ava:
res[attr[1]] = _filter_values(ava[attr[1]], vals[0], vals[1])
else:
raise MissingValue("Required attribute missing")
return res
return attribute.name
#----------------------------------------------------------------------------
@@ -325,43 +218,7 @@ def _basic_val(val):
raise OtherError("strange value type on: %s" % val)
return attrval
def basic_attribute(attribute, value):
"""<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="cn">
<saml:AttributeValue xsi:type="xs:string">Andreas Solberg</saml:AttributeValue>
</saml:Attribute>
"""
attr = {}
attr["name_format"] = NAME_FORMAT_BASIC
attr["name"] = attribute
return attr
def ava_to_attributes(ava, bmap=None):
attrs = []
for friendly_name, val in ava.items():
dic = {}
attrval = _attrval(val)
if attrval:
dic["attribute_value"] = attrval
dic["friendly_name"] = friendly_name
if bmap:
try:
(dic["name"], dic["name_format"]) = bmap[friendly_name]
except KeyError:
pass
attrs.append(args2dict(**dic))
return attrs
def do_ava_statement(identity, bmap):
"""
:param identity: A dictionary with fiendly names as keys
:return:
"""
return args2dict(attribute=ava_to_attributes(identity, bmap))
def do_attributes(identity):
attrs = []
if not identity:

View File

@@ -6,4 +6,24 @@ def pytest_funcarg__xmlsec(request):
if os.access(fil,os.X_OK):
return fil
raise Exception("Can't find xmlsec1")
raise Exception("Can't find xmlsec1")
def pytest_funcarg__AVA(request):
return [
{
"surName": ["Jeter"],
"givenName": ["Derek"],
},
{
"surName": ["Howard"],
"givenName": ["Ryan"],
},
{
"surName": ["Suzuki"],
"givenName": ["Ischiro"],
},
{
"surName": ["Hedberg"],
"givenName": ["Roland"],
},
]

View File

@@ -4,10 +4,11 @@
"idp": {
"name" : "Rolands IdP",
"url": "http://localhost:8088/sso",
"assertions": {
"policy": {
"default": {
"lifetime": {"minutes":15},
"attribute_restrictions": None # means all I have
"attribute_restrictions": None, # means all I have
"name_form": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
},
"urn:mace:example.com:saml:roland:sp": {
"lifetime": {"minutes": 5},
@@ -27,5 +28,5 @@
"local": ["metadata.xml", "vo_metadata.xml"],
},
"subject_data": "subject_data.db",
"attribute_maps" : ["attribute.map"]
"attribute_map_dir" : "attributemaps",
}

View File

@@ -7,6 +7,26 @@
"assertions": {
"default": {
"lifetime": {"minutes":15},
"name_form": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
},
"urn:mace:example.com:saml:roland:sp": {
"lifetime": {"minutes": 5},
"attribute_restrictions":{
"givenName": None,
"surName": None,
"mail": [".*@example.com"],
"eduPersonAffiliation": ["(employee|staff|faculty)"],
}
}
}
},
"aa": {
"name" : "Rolands restrictied AA",
"url": "http://localhost:8089/sso",
"assertions": {
"default": {
"lifetime": {"minutes":15},
"name_form": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
},
"urn:mace:example.com:saml:roland:sp": {
"lifetime": {"minutes": 5},
@@ -27,5 +47,5 @@
"local": ["sp_0.metadata"],
},
"subject_data": "subject_data.db",
"attribute_maps" : ["attribute.map"]
"attribute_map_dir" : "attributemaps",
}

View File

@@ -24,4 +24,5 @@
},
"subject_data": "subject_data.db",
"accept_time_diff": 60,
"attribute_map_dir" : "attributemaps",
}

View File

@@ -8,7 +8,6 @@ import gzip
from saml2 import utils, saml, samlp, md, make_instance
from saml2.utils import do_attribute_statement
from saml2.sigver import make_temp
from saml2.config import do_assertions
from saml2.saml import Attribute, NAME_FORMAT_URI, AttributeValue
from py.test import raises
@@ -278,104 +277,6 @@ def test_subject():
AVA = [
{
"surName": ["Jeter"],
"givenName": ["Derek"],
},
{
"surName": ["Howard"],
"givenName": ["Ryan"],
},
{
"surName": ["Suzuki"],
"givenName": ["Ischiro"],
},
{
"surName": ["Hedberg"],
"givenName": ["Roland"],
},
]
def test_filter_attribute_value_assertions_0():
assertion = {
"default": {
"attribute_restrictions": {
"surName": [".*berg"],
}
}
}
ass = do_assertions(assertion)
print ass
ava = utils.filter_attribute_value_assertions(AVA[3],
ass["default"]["attribute_restrictions"])
print ava
assert ava.keys() == ["surName"]
assert ava["surName"] == ["Hedberg"]
def test_filter_attribute_value_assertions_1():
assertion = {
"default": {
"attribute_restrictions": {
"surName": None,
"givenName": [".*er.*"],
}
}
}
ass = do_assertions(assertion)
print ass
ava = utils.filter_attribute_value_assertions(AVA[0],
ass["default"]["attribute_restrictions"])
print ava
assert _eq(ava.keys(), ["givenName","surName"])
assert ava["surName"] == ["Jeter"]
assert ava["givenName"] == ["Derek"]
ava = utils.filter_attribute_value_assertions(AVA[1],
ass["default"]["attribute_restrictions"])
print ava
assert _eq(ava.keys(), ["surName"])
assert ava["surName"] == ["Howard"]
def test_filter_attribute_value_assertions_2():
assertion = {
"default": {
"attribute_restrictions": {
"givenName": ["^R.*"],
}
}
}
ass = do_assertions(assertion)
print ass
ava = utils.filter_attribute_value_assertions(AVA[0],
ass["default"]["attribute_restrictions"])
print ava
assert _eq(ava.keys(), [])
ava = utils.filter_attribute_value_assertions(AVA[1],
ass["default"]["attribute_restrictions"])
print ava
assert _eq(ava.keys(), ["givenName"])
assert ava["givenName"] == ["Ryan"]
ava = utils.filter_attribute_value_assertions(AVA[3],
ass["default"]["attribute_restrictions"])
print ava
assert _eq(ava.keys(), ["givenName"])
assert ava["givenName"] == ["Roland"]
def test_parse_attribute_map():
(forward, backward) = utils.parse_attribute_map(["attribute.map"])
@@ -441,129 +342,7 @@ def test_identity_attribute_4():
assert utils.identity_attribute("name",a) == "urn:oid:2.5.4.5"
# if there would be a map it would be serialNumber
assert utils.identity_attribute("friendly",a) == "serialNumber"
def test_combine_0():
r = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber")
o = Attribute(name="urn:oid:2.5.4.4", name_format=NAME_FORMAT_URI,
friendly_name="surName")
comb = utils._combine([r],[o])
print comb
assert _eq(comb.keys(), [('urn:oid:2.5.4.5', 'serialNumber'),
('urn:oid:2.5.4.4', 'surName')])
assert comb[('urn:oid:2.5.4.5', 'serialNumber')] == ([], [])
assert comb[('urn:oid:2.5.4.4', 'surName')] == ([], [])
def test_filter_on_attributes_0():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber")
required = [a]
ava = { "serialNumber": ["12345"]}
ava = utils.filter_on_attributes(ava, required)
assert ava.keys() == ["serialNumber"]
assert ava["serialNumber"] == ["12345"]
def test_filter_on_attributes_1():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber")
required = [a]
ava = { "serialNumber": ["12345"], "givenName":["Lars"]}
ava = utils.filter_on_attributes(ava, required)
assert ava.keys() == ["serialNumber"]
assert ava["serialNumber"] == ["12345"]
def test_filter_values_req_2():
a1 = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber")
a2 = Attribute(name="urn:oid:2.5.4.4", name_format=NAME_FORMAT_URI,
friendly_name="surName")
required = [a1,a2]
ava = { "serialNumber": ["12345"], "givenName":["Lars"]}
raises(utils.MissingValue, utils.filter_on_attributes, ava, required)
def test_filter_values_req_3():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[
AttributeValue(text="12345")])
required = [a]
ava = { "serialNumber": ["12345"]}
ava = utils.filter_on_attributes(ava, required)
assert ava.keys() == ["serialNumber"]
assert ava["serialNumber"] == ["12345"]
def test_filter_values_req_4():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[
AttributeValue(text="54321")])
required = [a]
ava = { "serialNumber": ["12345"]}
raises(utils.MissingValue, utils.filter_on_attributes, ava, required)
def test_filter_values_req_5():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[
AttributeValue(text="12345")])
required = [a]
ava = { "serialNumber": ["12345", "54321"]}
ava = utils.filter_on_attributes(ava, required)
assert ava.keys() == ["serialNumber"]
assert ava["serialNumber"] == ["12345"]
def test_filter_values_req_6():
a = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[
AttributeValue(text="54321")])
required = [a]
ava = { "serialNumber": ["12345", "54321"]}
ava = utils.filter_on_attributes(ava, required)
assert ava.keys() == ["serialNumber"]
assert ava["serialNumber"] == ["54321"]
def test_filter_values_req_opt_0():
r = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[
AttributeValue(text="54321")])
o = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[
AttributeValue(text="12345")])
ava = { "serialNumber": ["12345", "54321"]}
ava = utils.filter_on_attributes(ava, [r], [o])
assert ava.keys() == ["serialNumber"]
assert _eq(ava["serialNumber"], ["12345","54321"])
def test_filter_values_req_opt_1():
r = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[
AttributeValue(text="54321")])
o = Attribute(name="urn:oid:2.5.4.5", name_format=NAME_FORMAT_URI,
friendly_name="serialNumber", attribute_value=[
AttributeValue(text="12345"),
AttributeValue(text="abcd0")])
ava = { "serialNumber": ["12345", "54321"]}
ava = utils.filter_on_attributes(ava, [r], [o])
assert ava.keys() == ["serialNumber"]
assert _eq(ava["serialNumber"], ["12345","54321"])
def _givenName(a):
assert a["name"] == "urn:oid:2.5.4.42"
assert a["friendly_name"] == "givenName"
@@ -575,22 +354,6 @@ def _surName(a):
assert a["friendly_name"] == "surName"
assert len(a["attribute_value"]) == 1
assert a["attribute_value"] == [{"text":"Jeter"}]
def test_ava_to_attributes():
(forward, backward) = utils.parse_attribute_map(["attribute.map"])
attrs = utils.ava_to_attributes(AVA[0], backward)
assert len(attrs) == 2
a = attrs[0]
if a["name"] == "urn:oid:2.5.4.42":
_givenName(a)
_surName(attrs[1])
elif a["name"] == "urn:oid:2.5.4.4":
_surName(a)
_givenName(attrs[1])
else:
print a
assert False
def test_nameformat_email():
assert utils.valid_email("foo@example.com")

View File

@@ -33,7 +33,7 @@ def _read_lines(name):
def test_swami_1():
md = metadata.MetaData()
md.import_metadata(_read_file(SWAMI_METADATA))
md.import_metadata(_read_file(SWAMI_METADATA),"-")
print len(md.entity)
assert len(md.entity)
idps = dict([(id,ent["idp_sso"]) for id,ent in md.entity.items() \
@@ -52,7 +52,7 @@ def test_swami_1():
def test_incommon_1():
md = metadata.MetaData()
md.import_metadata(_read_file(INCOMMON_METADATA))
md.import_metadata(_read_file(INCOMMON_METADATA),"-")
print len(md.entity)
assert len(md.entity) == 442
idps = dict([
@@ -67,7 +67,7 @@ def test_incommon_1():
def test_example():
md = metadata.MetaData()
md.import_metadata(_read_file(EXAMPLE_METADATA))
md.import_metadata(_read_file(EXAMPLE_METADATA), "-")
print len(md.entity)
assert len(md.entity) == 1
idps = dict([(id,ent["idp_sso"]) for id,ent in md.entity.items() \
@@ -82,7 +82,7 @@ def test_example():
def test_switch_1():
md = metadata.MetaData()
md.import_metadata(_read_file(SWITCH_METADATA))
md.import_metadata(_read_file(SWITCH_METADATA), "-")
print len(md.entity)
assert len(md.entity) == 90
idps = dict([(id,ent["idp_sso"]) for id,ent in md.entity.items() \
@@ -110,13 +110,13 @@ def test_switch_1():
def test_sp_metadata():
md = metadata.MetaData()
md.import_metadata(_read_file(SP_METADATA))
md.import_metadata(_read_file(SP_METADATA), "-")
print md.entity
assert len(md.entity) == 1
assert md.entity.keys() == ['urn:mace:umu.se:saml:roland:sp']
assert md.entity['urn:mace:umu.se:saml:roland:sp'].keys() == [
"organization","sp_sso"]
'valid_until',"organization","sp_sso"]
print md.entity['urn:mace:umu.se:saml:roland:sp']["sp_sso"][0].keyswv()
(req,opt) = md.attribute_consumer('urn:mace:umu.se:saml:roland:sp')
print req

View File

@@ -156,17 +156,9 @@ def test_idp():
c = Config().load(IDP1)
print c
service = c["service"]
assert service.keys() == ["idp"]
idp = service["idp"]
assert _eq(idp.keys(),['url', 'name', 'assertions'])
assert idp["url"] == "http://localhost:8088/"
assert c.services() == ["idp"]
assert c.idp_url() == "http://localhost:8088/"
default_attribute_restrictions = idp["assertions"]["default"][
"attribute_restrictions"]
assert default_attribute_restrictions[
"eduPersonAffiliation"][0].match("staff")
attribute_restrictions = c.idp_policy().get_attribute_restriction("")
assert attribute_restrictions["eduPersonAffiliation"][0].match("staff")

View File

@@ -161,8 +161,8 @@ def test_6():
assert dr.uri == "#invoice34"
assert len(dr.extension_elements) == 1
ee = dr.extension_elements[0]
assert ee.c_tag == "Transforms"
assert ee.c_namespace == "http://www.w3.org/2000/09/xmldsig#"
assert ee.tag == "Transforms"
assert ee.namespace == "http://www.w3.org/2000/09/xmldsig#"
trs = saml2.extension_element_to_element(ee, xmldsig.ELEMENT_FROM_STRING,
namespace=xmldsig.NAMESPACE)

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from saml2.server import Server
from saml2.server import Server, IdentifierMap
from saml2 import server, make_instance
from saml2 import samlp, saml, client, utils
from saml2.utils import OtherError
@@ -13,6 +13,14 @@ import re
def _eq(l1,l2):
return set(l1) == set(l2)
def test_persistence_0():
id = IdentifierMap("subject_data.db")
pid1 = id.persistent("urn:mace:example.com:saml:roland:sp", "jeter")
pid2 = id.persistent("urn:mace:example.com:saml:roland:sp", "jeter")
print pid1, pid2
assert pid1 == pid2
class TestServer1():
def setup_class(self):
@@ -171,7 +179,7 @@ class TestServer1():
"http://localhost:8087/", # consumer_url
"12", # in_response_to
"urn:mace:example.com:saml:roland:sp", # sp_entity_id
{ "eduPersonEntitlement": "Bat"}
{ "eduPersonEntitlement": "Short stop"}
)
print resp.keyswv()
@@ -197,7 +205,7 @@ class TestServer1():
assert attribute.name == "urn:oid:1.3.6.1.4.1.5923.1.1.1.7"
assert attribute.name_format == "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
value = attribute.attribute_value[0]
assert value.text.strip() == "Bat"
assert value.text.strip() == "Short stop"
assert value.type == "xs:string"
assert assertion.subject
assert assertion.subject.name_id
@@ -244,107 +252,7 @@ class TestServer1():
assert resp.issuer.text == "urn:mace:example.com:saml:roland:idp"
assert not resp.assertion
def test_persistence_0(self):
pid1 = self.server.persistent_id(
"urn:mace:example.com:saml:roland:sp", "jeter")
pid2 = self.server.persistent_id(
"urn:mace:example.com:saml:roland:sp", "jeter")
print pid1, pid2
assert pid1 == pid2
def test_filter_ava_0(self):
ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]}
# No restrictions apply
ava = self.server.filter_ava(ava,
"urn:mace:example.com:saml:roland:sp",
[], [])
assert _eq(ava.keys(), ["givenName", "surName", "mail"])
assert ava["givenName"] == ["Derek"]
assert ava["surName"] == ["Jeter"]
assert ava["mail"] == ["derek@nyy.mlb.com"]
def test_filter_ava_1(self):
""" No mail address returned """
self.server.conf["service"]["idp"]["assertions"][
"urn:mace:example.com:saml:roland:sp"] = {
"lifetime": {"minutes": 5},
"attribute_restrictions":{
"givenName": None,
"surName": None,
}
}
print self.server.conf["service"]["idp"]["assertions"]
ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]}
# No restrictions apply
ava = self.server.filter_ava(ava,
"urn:mace:example.com:saml:roland:sp",
[], [])
assert _eq(ava.keys(), ["givenName", "surName"])
assert ava["givenName"] == ["Derek"]
assert ava["surName"] == ["Jeter"]
def test_filter_ava_2(self):
""" Only mail returned """
self.server.conf["service"]["idp"]["assertions"][
"urn:mace:example.com:saml:roland:sp"] = {
"lifetime": {"minutes": 5},
"attribute_restrictions":{
"mail": None,
}
}
print self.server.conf["service"]["idp"]["assertions"]
ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]}
# No restrictions apply
ava = self.server.filter_ava(ava,
"urn:mace:example.com:saml:roland:sp",
[], [])
assert _eq(ava.keys(), ["mail"])
assert ava["mail"] == ["derek@nyy.mlb.com"]
def test_filter_ava_3(self):
""" Only example.com mail addresses returned """
self.server.conf["service"]["idp"]["assertions"][
"urn:mace:example.com:saml:roland:sp"] = {
"lifetime": {"minutes": 5},
"attribute_restrictions":{
"mail": [re.compile(".*@example\.com$")],
}
}
print self.server.conf["service"]["idp"]["assertions"]
ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com", "dj@example.com"]}
# No restrictions apply
ava = self.server.filter_ava(ava,
"urn:mace:example.com:saml:roland:sp",
[], [])
assert _eq(ava.keys(), ["mail"])
assert ava["mail"] == ["dj@example.com"]
def test_authn_response_0(self):
# reset
del self.server.conf["service"]["idp"]["assertions"][
"urn:mace:example.com:saml:roland:sp"]
ava = { "givenName": ["Derek"], "surName": ["Jeter"],
"mail": ["derek@nyy.mlb.com"]}
@@ -384,25 +292,11 @@ class TestServer2():
self.server = Server("restrictive_idp.config")
except IOError, e:
self.server = Server("tests/restrictive_idp.config")
def test_0(self):
ident = self.server.restrict_ava(IDENTITY.copy(),
"urn:mace:example.com:saml:roland:sp")
assert len(ident) == 3
assert ident == {'eduPersonAffiliation': ['staff'],
'givenName': ['Derek'], 'surName': ['Jeter']}
print self.server.conf.keys()
attr = utils.ava_to_attributes(ident, self.server.conf["am_backward"])
assert len(attr) == 3
assert {'attribute_value': [{'text': 'staff'}],
'friendly_name': 'eduPersonAffiliation',
'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'} in attr
def test_do_aa_reponse(self):
aa_policy = self.server.conf.aa_policy()
print aa_policy.__dict__
print self.server.conf["service"]
response = self.server.do_aa_response( "http://example.com/sp/", "aaa",
"urn:mace:example.com:sp:1", IDENTITY.copy(),
issuer = self.server.conf["entityid"])