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"]
except KeyError:
try:
vorg_name = self.saml_client.vorg.keys()[1]
except (IndexError, AttributeError):
vorg_name = self.saml_client.vorg._name
except AttributeError:
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)
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
member. It is in SamlBase so that it can be inherited but it should
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()
tree.append(new_child)
node.append(new_child)
def _to_element_tree(self):
"""

View File

@@ -17,7 +17,6 @@
import logging
import re
import sys
import xmlenc
from saml2 import saml
@@ -60,6 +59,19 @@ def _filter_values(vals, vlist=None, must=False):
else:
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):
""" Filter
@@ -75,38 +87,40 @@ def filter_on_attributes(ava, required=None, optional=None):
if required is None:
required = []
for attr in required:
if attr.friendly_name in ava:
values = [av.text for av in attr.attribute_value]
res[attr.friendly_name] = _filter_values(ava[attr.friendly_name],
values, True)
elif attr.name in ava:
values = [av.text for av in attr.attribute_value]
res[attr.name] = _filter_values(ava[attr.name], values, True)
else:
_name = attr.friendly_name or attr.name
print >> sys.stderr, ava.keys()
raise MissingValue("Required attribute missing: '%s'" % (_name,))
found = False
for nform in ["friendly_name", "name"]:
if nform in attr :
_fn = _match(attr[nform], ava)
if _fn:
try:
values = [av["text"] for av in attr["attribute_value"]]
except KeyError:
values = []
res[_fn] = _filter_values(ava[_fn], values, True)
found = True
break
if not found:
raise MissingValue("Required attribute missing: '%s'" % (attr[nform],))
if optional is None:
optional = []
for attr in optional:
if attr.friendly_name in ava:
values = [av.text for av in attr.attribute_value]
try:
res[attr.friendly_name].extend(_filter_values(ava[attr.friendly_name],
values))
except KeyError:
res[attr.friendly_name] = _filter_values(ava[attr.friendly_name],
values)
elif attr.name in ava:
values = [av.text for av in attr.attribute_value]
try:
res[attr.name].extend(_filter_values(ava[attr.name], values))
except KeyError:
res[attr.name] = _filter_values(ava[attr.name], values)
for nform in ["friendly_name", "name"]:
if nform in attr :
_fn = _match(attr[nform], ava)
if _fn:
try:
values = [av["text"] for av in attr["attribute_value"]]
except KeyError:
values = []
try:
res[_fn].extend(_filter_values(ava[_fn],values))
except KeyError:
res[_fn] = _filter_values(ava[_fn],values)
return res
@@ -123,12 +137,15 @@ def filter_on_demands(ava, required=None, optional=None):
# Is all what's required there:
if required is None:
required = {}
lava = dict([(k.lower(), k) for k in ava.keys()])
for attr, vals in required.items():
if attr in ava:
attr = attr.lower()
if attr in lava:
if vals:
for val in vals:
if val not in ava[attr]:
if val not in ava[lava[attr]]:
raise MissingValue(
"Required attribute value missing: %s,%s" % (attr,
val))
@@ -137,12 +154,15 @@ def filter_on_demands(ava, required=None, optional=None):
if optional is None:
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
# but not attributes
for attr, vals in ava.items():
if attr not in required and attr not in optional:
del ava[attr]
# but not attributes that are not asked for.
for attr in lava.keys():
if attr not in oka:
del ava[lava[attr]]
return ava
@@ -383,12 +403,14 @@ class Policy(object):
If the requirements can't be met an exception is raised.
"""
if metadata:
(required, optional) = metadata.attribute_requirement(sp_entity_id)
else:
required = optional = None
spec = metadata.attribute_requirement(sp_entity_id)
if spec:
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):
""" Return a saml.Condition instance
@@ -510,4 +532,6 @@ class Assertion(dict):
:param metadata: Metadata to use
: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 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):
""" Converts from an attribute statement to a key,value dictionary and
vice-versa """
@@ -165,28 +182,15 @@ class AttributeConverter(object):
self._to = 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):
""" If one of the transformations is not defined it is expected to
be the mirror image of the other.
"""
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:
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):
""" Import the attribute map from a dictionary
@@ -196,11 +200,11 @@ class AttributeConverter(object):
self.name_format = mapdict["identifier"]
try:
self._fro = mapdict["fro"]
self._fro = dict([(k.lower(),v) for k,v in mapdict["fro"].items()])
except KeyError:
pass
try:
self._to = mapdict["to"]
self._to = dict([(k.lower(),v) for k,v in mapdict["to"].items()])
except KeyError:
pass
@@ -230,12 +234,12 @@ class AttributeConverter(object):
def ava_from(self, attribute):
try:
attr = self._fro[attribute.name.strip()]
attr = self._fro[attribute.name.strip().lower()]
except (AttributeError, KeyError):
try:
attr = attribute.friendly_name.strip()
attr = attribute.friendly_name.strip().lower()
except AttributeError:
attr = attribute.name.strip()
attr = attribute.name.strip().lower()
val = []
for value in attribute.attribute_value:
@@ -306,17 +310,37 @@ class AttributeConverter(object):
if attr.name_format:
if self.name_format == attr.name_format:
try:
return self._fro[attr.name]
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]
return self._fro[attr.name.lower()]
except KeyError:
pass
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):
""" Create a list of Attribute instances.
@@ -325,6 +349,7 @@ class AttributeConverter(object):
"""
attributes = []
for key, value in attrvals.items():
key = key.lower()
try:
attributes.append(factory(saml.Attribute,
name=self._to[key],

View File

@@ -50,7 +50,7 @@ class AttributeResolver(object):
"""
result = []
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:
logger.info(
"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
to conclude its tasks.
"""
import saml2
from saml2.saml import AssertionIDRef, NAMEID_FORMAT_PERSISTENT
try:
from urlparse import parse_qs
@@ -29,17 +27,17 @@ except ImportError:
from cgi import parse_qs
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 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 signed_instance_factory
from saml2.binding import send_using_soap
from saml2.binding import http_redirect_message
from saml2.binding import http_post_message
from saml2.client_base import Base, LogoutError
from saml2.client_base import Base
from saml2.client_base import LogoutError
from saml2.client_base import NoServiceDefined
from saml2.mdstore import destinations
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_POST
@@ -79,16 +77,13 @@ class Saml2Client(Base):
logger.info("AuthNReq: %s" % _req_str)
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")
(head, response) = http_post_message(_req_str, location,
response = self.send_using_http_post(_req_str, location,
relay_state)
elif binding == saml2.BINDING_HTTP_REDIRECT:
logger.info("HTTP REDIRECT")
(head, _body) = http_redirect_message(_req_str, location,
relay_state)
response = head[0]
response = self.send_using_http_get(_req_str, location,
relay_state)
else:
raise Exception("Unknown binding type: %s" % binding)
@@ -123,7 +118,7 @@ class Saml2Client(Base):
"""
: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
:param reason: The reason for doing the logout
: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
not_done = entity_ids[:]
response = False
responses = {}
for entity_id in entity_ids:
response = False
for binding in [BINDING_SOAP, BINDING_HTTP_POST,
BINDING_HTTP_REDIRECT]:
destinations = self.config.single_logout_services(entity_id,
binding)
if not destinations:
srvs = self.metadata.single_logout_service(entity_id, "idpsso",
binding=binding)
if not srvs:
continue
destination = destinations[0]
destination = destinations(srvs)[0]
logger.info("destination to provider: %s" % destination)
request = self.create_logout_request(subject_id,
@@ -170,20 +165,18 @@ class Saml2Client(Base):
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:
response = send_using_soap(request, destination,
self.config.key_file,
self.config.cert_file,
ca_certs=self.config.ca_certs)
response = self.send_using_soap(srequest, destination)
if response:
logger.info("Verifying response")
response = self.logout_response(response)
response = self.logout_request_response(response)
if response:
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)
@@ -202,23 +195,27 @@ class Saml2Client(Base):
if binding == BINDING_HTTP_POST:
(head, body) = http_post_message(request,
destination,
rstate)
code = "200 OK"
response = self.send_using_http_post(srequest,
destination,
rstate)
else:
(head, body) = http_redirect_message(request,
destination,
response = self.send_using_http_get(srequest,
destination,
rstate)
code = "302 Found"
return session_id, code, head, body
if response:
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:
# upstream should try later
raise LogoutError("%s" % (entity_ids,))
return 0, "", [], response
return responses
def local_logout(self, subject_id):
""" Remove the user from the cache, equals local logout
@@ -251,65 +248,66 @@ class Saml2Client(Base):
status["reason"], status["not_on_or_after"],
status["sign"])
def do_http_redirect_logout(self, get, subject_id):
""" Deal with a LogoutRequest received through HTTP redirect
:param get: The request as a dictionary
:param subject_id: the id of the current logged user
:return: a tuple with a list of header tuples (presently only location)
and a status which will be True in case of success or False
otherwise.
"""
headers = []
success = False
try:
saml_request = get['SAMLRequest']
except KeyError:
return None
if saml_request:
xml = decode_base64_and_inflate(saml_request)
request = samlp.logout_request_from_string(xml)
logger.debug(request)
if request.name_id.text == subject_id:
status = samlp.STATUS_SUCCESS
success = self.local_logout(subject_id)
else:
status = samlp.STATUS_REQUEST_DENIED
destination, (id, response) = self.create_logout_response(
request.issuer.text,
request.id,
status)
logger.info("RESPONSE: {0:>s}".format(response))
if 'RelayState' in get:
rstate = get['RelayState']
else:
rstate = ""
(headers, _body) = http_redirect_message(str(response),
destination,
rstate, 'SAMLResponse')
return headers, success
def handle_logout_request(self, request, subject_id,
binding=BINDING_HTTP_REDIRECT):
""" Deal with a LogoutRequest
:param request: The request. The format 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)
# def do_http_redirect_logout(self, get, subject_id):
# """ 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
# :return: a tuple with a list of header tuples (presently only location)
# and a status which will be True in case of success or False
# otherwise.
# """
# headers = []
# success = False
#
# try:
# saml_request = get['SAMLRequest']
# except KeyError:
# return None
#
# if saml_request:
# xml = decode_base64_and_inflate(saml_request)
#
# request = samlp.logout_request_from_string(xml)
# logger.debug(request)
#
# if request.name_id.text == subject_id:
# status = samlp.STATUS_SUCCESS
# success = self.local_logout(subject_id)
# else:
# status = samlp.STATUS_REQUEST_DENIED
#
# destination, (id, response) = self.create_logout_response(
# request.issuer.text,
# request.id,
# status)
#
# logger.info("RESPONSE: {0:>s}".format(response))
#
# if 'RelayState' in get:
# rstate = get['RelayState']
# else:
# rstate = ""
#
# (headers, _body) = http_redirect_message(str(response),
# destination,
# rstate, 'SAMLResponse')
#
# return headers, success
#
# def handle_logout_request(self, request, subject_id,
# binding=BINDING_HTTP_REDIRECT):
# """ Deal with a LogoutRequest
#
# :param request: The request. The format 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)
# MUST use SOAP for
# AssertionIDRequest, SubjectQuery,
@@ -326,10 +324,7 @@ class Saml2Client(Base):
query = _create_func(destination, **kwargs)
response = send_using_soap(query, destination,
self.config.key_file,
self.config.cert_file,
ca_certs=self.config.ca_certs)
response = self.send_using_soap(query, destination)
if response:
logger.info("Verifying response")
@@ -361,11 +356,11 @@ class Saml2Client(Base):
sp_name_qualifier=sp_name_qualifier,
name_qualifier=name_qualifier))
for destination in self.config.authz_service_endpoints(entity_id,
BINDING_SOAP):
resp = self.use_soap(destination, "authz_decision_query",
action=action, evidence=evidence,
resource=resource, subject=subject)
srvs = self.metadata.authz_service(entity_id, BINDING_SOAP)
for dest in destinations(srvs):
resp = self.use_soap(dest, "authz_decision_query",
action=action, evidence=evidence,
resource=resource, subject=subject)
if resp:
return resp
@@ -374,26 +369,39 @@ class Saml2Client(Base):
def do_assertion_id_request(self, assertion_ids, entity_id,
consent=None, extensions=None, sign=False):
destination = self.metadata.assertion_id_request_service(entity_id,
BINDING_SOAP)[0]
srvs = self.metadata.assertion_id_request_service(entity_id,
BINDING_SOAP)
if not srvs:
raise NoServiceDefined("%s: %s" % (entity_id,
"assertion_id_request_service"))
if isinstance(assertion_ids, basestring):
assertion_ids = [assertion_ids]
_id_refs = [AssertionIDRef(_id) for _id in assertion_ids]
return self.use_soap(destination, "assertion_id_request",
assertion_id_refs=_id_refs, consent=consent,
extensions=extensions, sign=sign)
for destination in destinations(srvs):
res = self.use_soap(destination, "assertion_id_request",
assertion_id_refs=_id_refs, consent=consent,
extensions=extensions, sign=sign)
if res:
return res
return None
def do_authn_query(self, entity_id,
consent=None, extensions=None, sign=False):
destination = self.metadata.authn_request_service(entity_id,
BINDING_SOAP)[0]
srvs = self.metadata.authn_request_service(entity_id, BINDING_SOAP)
return self.use_soap(destination, "authn_query",
consent=consent, extensions=extensions, sign=sign)
for destination in destinations(srvs):
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,
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
to conclude its tasks.
"""
from saml2.httpbase import HTTPBase
from saml2.mdstore import destinations
from saml2.saml import AssertionIDRef, NAMEID_FORMAT_TRANSIENT
from saml2.samlp import AuthnQuery
from saml2.samlp import LogoutRequest
@@ -82,7 +85,10 @@ class VerifyError(Exception):
class LogoutError(Exception):
pass
class Base(object):
class NoServiceDefined(Exception):
pass
class Base(HTTPBase):
""" The basic pySAML2 service provider class """
def __init__(self, config=None, identity_cache=None, state_cache=None,
@@ -109,6 +115,10 @@ class Base(object):
else:
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:
for vo in self.config.vorg.values():
vo.sp = self
@@ -129,7 +139,7 @@ class Base(object):
elif isinstance(virtual_organization, VirtualOrg):
self.vorg = virtual_organization
else:
self.vorg = {}
self.vorg = None
for foo in ["allow_unsolicited", "authn_requests_signed",
"logout_requests_signed"]:
@@ -170,23 +180,22 @@ class Base(object):
def _sso_location(self, entityid=None, binding=BINDING_HTTP_REDIRECT):
if entityid:
# verify that it's in the metadata
try:
return self.config.single_sign_on_services(entityid, binding)[0]
except IndexError:
logger.info("_sso_location: %s, %s" % (entityid,
binding))
srvs = self.metadata.single_sign_on_service(entityid, binding)
if srvs:
return destinations(srvs)[0]
else:
logger.info("_sso_location: %s, %s" % (entityid, binding))
raise IdpUnspecified("No IdP to send to given the premises")
# get the idp location from the configuration alternative the
# metadata. If there is more than one IdP in the configuration
# raise exception
eids = self.config.idps()
# get the idp location from the metadata. If there is more than one
# IdP in the configuration raise exception
eids = self.metadata.with_descriptor("idpsso")
if len(eids) > 1:
raise IdpUnspecified("Too many IdPs to choose from: %s" % eids)
try:
loc = self.config.single_sign_on_services(eids.keys()[0],
binding)[0]
return loc
srvs = self.metadata.single_sign_on_service(eids.keys()[0], binding)
return destinations(srvs)[0]
except IndexError:
raise IdpUnspecified("No IdP to send to given the premises")
@@ -424,8 +433,9 @@ class Base(object):
:return: A LogoutResponse instance
"""
destination = self.config.single_logout_services(idp_entity_id,
binding)[0]
srvs = self.metadata.single_logout_services(idp_entity_id, "idpsso",
binding=binding)
destination = destinations(srvs)[0]
status = samlp.Status(
status_code=samlp.StatusCode(value=status_code))

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python
from saml2.virtual_org import VirtualOrg
__author__ = 'rolandh'
@@ -11,19 +10,42 @@ import logging.handlers
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.attribute_converter import ac_factory
from saml2.assertion import Policy
from saml2.sigver import get_xmlsec_binary
from saml2.mdstore import MetadataStore
from saml2.virtual_org import VirtualOrg
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",
"secret", "accepted_time_diff", "name", "ca_certs",
"description", "valid_for",
"description", "valid_for", "verify_ssl_cert",
"organization",
"contact_person",
"name_form",
@@ -107,6 +129,7 @@ class Config(object):
self.accepted_time_diff=None
self.name=None
self.ca_certs=None
self.verify_ssl_cert = False
self.description=None
self.valid_for=None
self.organization=None
@@ -254,28 +277,16 @@ class Config(object):
except:
ca_certs = None
try:
disable_ssl_certificate_validation = self.disable_ssl_certificate_validation
disable_validation = self.disable_ssl_certificate_validation
except:
disable_ssl_certificate_validation = False
disable_validation = False
metad = metadata.MetaData(xmlsec_binary, acs, ca_certs,
disable_ssl_certificate_validation)
if "local" in metadata_conf:
for mdfile in metadata_conf["local"]:
metad.import_metadata(open(mdfile).read(), mdfile)
if "inline" in metadata_conf:
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
mds = MetadataStore(ONTS.values(), acs, xmlsec_binary, ca_certs,
disable_ssl_certificate_validation=disable_validation)
mds.imp(metadata_conf)
return mds
def endpoint(self, service, binding=None, context=None):
""" Goes through the list of endpoint specifications for the
@@ -361,73 +372,12 @@ class Config(object):
root_logger.info("Logging started")
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):
def_context = "sp"
def __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):
try:
return self.virtual_organization[vo_name]
@@ -455,12 +405,6 @@ class IdPConfig(Config):
def __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):
if typ == "sp":
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>'
_status = '302 Found'
def __call__(self, environ, start_response):
def __call__(self, environ, start_response, **kwargs):
location = self.message
self.headers.append(('location', location))
start_response(self.status, self.headers)
@@ -62,7 +62,7 @@ class SeeOther(Response):
'</body>\n</html>'
_status = '303 See Other'
def __call__(self, environ, start_response):
def __call__(self, environ, start_response, **kwargs):
location = self.message
self.headers.append(('location', location))
start_response(self.status, self.headers)

View File

@@ -83,14 +83,14 @@ def _kwa(val, onts):
:param onts: Schemas to use in the conversion
: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 onts: Schemas to use in the conversion
:return: The pysaml2 metadata object
:return: The pysaml2 object instance
"""
if isinstance(val, dict):
if "__class__" in val:
@@ -115,22 +115,12 @@ def _x(val, onts):
else:
res = {}
for key, v in val.items():
res[key] = _x(v, onts)
res[key] = from_dict(v, onts)
return res
elif isinstance(val, basestring):
return val
elif isinstance(val, list):
return [_x(v, onts) for v in val]
return [from_dict(v, onts) for v in val]
else:
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 urllib
from saml2.s_utils import deflate_and_base64_encode
from saml2.soap import SOAPClient, HTTPClient
import logging
logger = logging.getLogger(__name__)
try:
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:
try:
import cElementTree as ElementTree
@@ -48,7 +52,7 @@ FORM_SPEC = """<form method="post" action="%s">
<input type="submit" value="Submit" />
</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
messages may be transmitted within the base64-encoded content of a
HTML form control.
@@ -73,9 +77,20 @@ def http_post_message(message, location, relay_state="", typ="SAMLRequest"):
response.append("</body>")
return [("Content-type", "text/html")], response
def http_redirect_message(message, location, relay_state="",
typ="SAMLRequest"):
#noinspection PyUnresolvedReferences
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
messages can be transmitted within URL parameters.
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
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 = {
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 ):

View File

@@ -23,6 +23,11 @@ import logging
import shelve
import sys
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 class_name
@@ -45,10 +50,6 @@ from saml2.s_utils import error_status_factory
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 signed_instance_factory
from saml2.sigver import pre_signature_part
@@ -218,9 +219,10 @@ class Identifier(object):
except KeyError:
return None
class Server(object):
class Server(HTTPBase):
""" A class that does things that IdPs or AAs do """
def __init__(self, config_file="", config=None, _cache="", stype="idp"):
self.ident = None
if config_file:
self.load_config(config_file, stype)
@@ -229,6 +231,10 @@ class Server(object):
else:
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.metadata = self.conf.metadata
@@ -277,7 +283,12 @@ class Server(object):
(dbspec,))
except AttributeError:
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):
""" Return an Issuer precursor """
if entityid:
@@ -351,12 +362,13 @@ class Server(object):
_binding = authn_request.message.protocol_binding
try:
consumer_url = self.metadata.assertion_consumer_service(sp_entity_id,
binding=_binding)[0]
srvs = self.metadata.assertion_consumer_service(sp_entity_id,
binding=_binding)
consumer_url = destinations(srvs)[0]
except (KeyError, IndexError):
_log_info("Failed to find consumer URL for %s" % sp_entity_id)
_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)
if not consumer_url: # what to do ?
@@ -609,8 +621,9 @@ class Server(object):
name_id = None
try:
nid_formats = []
for _sp in self.metadata.entity[sp_entity_id]["spsso"]:
nid_formats.extend([n.text for n in _sp.name_id_format])
for _sp in self.metadata[sp_entity_id]["spsso_descriptor"]:
if "name_id_format" in _sp:
nid_formats.extend([n.text for n in _sp["name_id_format"]])
policy = self.conf.getattr("policy", "idp")
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()
binding = None
destinations = []
dests = []
for binding in bindings:
destinations = self.conf.single_logout_services(sp_entity_id,
binding)
if destinations:
srvs = self.metadata.single_logout_service(sp_entity_id, "spsso",
binding=binding)
if srvs:
dests = destinations(srvs)
break
if not destinations:
if not dests:
logger.error("No way to return a response !!!")
return ("412 Precondition Failed",
[("Content-type", "text/html")],
["No return way defined"])
# Pick the first
destination = destinations[0]
destination = dests[0]
logger.info("Logout Destination: %s, binding: %s" % (destination,
binding))
logger.info("Logout Destination: %s, binding: %s" % (dests, binding))
if not status:
status = success_status_factory()

View File

@@ -232,7 +232,14 @@ def _instance(klass, ava, seccont, base64encode=False, elements_to_sign=None):
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:
signed_xml = "%s" % instance
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 "%s" % signed_xml
#print "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
return create_class_from_xml_string(instance.__class__, signed_xml)
return signed_xml
else:
return instance
@@ -632,7 +639,16 @@ class SecurityContext(object):
# More trust in certs from metadata then certs in the XML document
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:
certs = []
@@ -677,7 +693,7 @@ class SecurityContext(object):
def check_signature(self, item, node_name=NODE_NAME, origdoc=None,
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)
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
from httplib2 import Http
from saml2 import httplib2cookie
from saml2 import create_class_from_element_tree
from saml2.samlp import NAMESPACE as SAMLP_NAMESPACE
#from saml2 import element_to_extension_element
from saml2.schema import soapenv
from saml2 import class_name
try:
from xml.etree import cElementTree as ElementTree
@@ -44,9 +39,6 @@ logger = logging.getLogger(__name__)
class XmlParseError(Exception):
pass
#NAMESPACE = "http://schemas.xmlsoap.org/soap/envelope/"
def parse_soap_enveloped_saml_response(text):
tags = ['{%s}Response' % SAMLP_NAMESPACE,
'{%s}LogoutResponse' % SAMLP_NAMESPACE]
@@ -198,140 +190,3 @@ def soap_fault(message=None, actor=None, code=None, detail=None):
)
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,
"key_file" : "test.key",
"cert_file" : "test.pem",
#"xmlsec_binary" : xmlsec_path,
"xmlsec_binary" : xmlsec_path,
"metadata": {
"local": ["sp_slo_redirect.xml"],
},

View File

@@ -1,17 +1,74 @@
<?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
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
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 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>
<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
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
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"?>
<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:KeyDescriptor>
<ns1:KeyInfo xmlns:ns1="http://www.w3.org/2000/09/xmldsig#">

View File

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

View File

@@ -40,7 +40,7 @@ CONFIG = {
},
"key_file" : "test.key",
"cert_file" : "test.pem",
#"xmlsec_binary" : xmlsec_path,
"xmlsec_binary" : xmlsec_path,
"metadata": {
"local": ["sp_0.metadata"],
},

View File

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

View File

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

View File

@@ -53,11 +53,11 @@ class TestAC():
except attribute_converter.UnknownNameFormat:
pass
print ava.keys()
assert _eq(ava.keys(),['uid', 'swissEduPersonUniqueID',
'swissEduPersonHomeOrganizationType',
'eduPersonEntitlement',
'eduPersonAffiliation', 'sn', 'mail',
'swissEduPersonHomeOrganization', 'givenName'])
assert _eq(ava.keys(),['uid', 'swissedupersonuniqueid',
'swissedupersonhomeorganizationtype',
'eduPersonEntitlement', 'eduPersonAffiliation',
'sn', 'mail', 'swissedupersonhomeorganization',
'givenName'])
def test_to_attrstat_1(self):
ava = { "givenName": "Roland", "sn": "Hedberg" }
@@ -74,7 +74,7 @@ class TestAC():
assert a1.friendly_name == "givenName"
assert a1.name == 'urn:mace:dir:attribute-def:givenName'
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_format == BASIC_NF
assert a1.friendly_name == "sn"
@@ -97,7 +97,7 @@ class TestAC():
assert a1.friendly_name == "givenName"
assert a1.name == 'urn:oid:2.5.4.42'
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_format == URI_NF
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 logging
from saml2.mdstore import MetadataStore, name
from saml2 import BINDING_HTTP_REDIRECT, BINDING_SOAP, BINDING_HTTP_POST
from saml2.config import SPConfig, IdPConfig, Config
from saml2.metadata import MetaData
from py.test import raises
from saml2 import root_logger
@@ -167,7 +167,7 @@ def test_1():
assert c._sp_name
assert c._sp_idp
md = c.metadata
assert isinstance(md, MetaData)
assert isinstance(md, MetadataStore)
assert len(c._sp_idp) == 1
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.context = "sp"
idps = c.idps()
assert idps == {'urn:mace:example.com:saml:roland:idp': 'Example Co.'}
idps = c.idps(["se","en"])
assert idps == {'urn:mace:example.com:saml:roland:idp': 'Exempel AB'}
idps = c.metadata.with_descriptor("idpsso")
ent = idps.values()[0]
assert name(ent) == 'Example Co.'
assert name(ent, "se") == 'Exempel AB'
c.setup_logger()
@@ -306,11 +306,8 @@ def test_3():
def test_sp():
cnf = SPConfig()
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") == \
["http://lingon.catalogix.se:8087/"]
assert len(cnf.idps()) == 1
def test_dual():
cnf = Config().load_file("idp_sp_conf")
@@ -336,12 +333,9 @@ def test_assertion_consumer_service():
c.load_file("idp_conf")
c.context = "idp"
xml_src = open("InCommon-metadata.xml").read()
# A trick so outdated data is allowed
c.metadata.import_metadata(xml_src, "-")
c.metadata.load("local", "InCommon-metadata.xml")
print c.metadata.entity.keys()
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 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
import base64
from saml2.saml import assertion_from_string
from saml2.samlp import response_from_string
from saml2 import sigver
from saml2 import class_name
@@ -125,7 +127,7 @@ class TestSecurity():
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)
@@ -141,17 +143,17 @@ class TestSecurity():
assert s_response is not None
print s_response
print
sass = s_response.assertion[0]
response = response_from_string(s_response)
sass = response.assertion[0]
print sass
assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant',
'version', 'signature', 'id'])
assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant',
'version', 'signature', 'id'])
assert sass.version == "2.0"
assert sass.id == "11111"
item = self.sec.check_signature(s_response,
node_name=class_name(s_response))
item = self.sec.check_signature(response, class_name(response),
s_response)
assert isinstance(item, samlp.Response)
assert item.id == "22222"
@@ -177,14 +179,16 @@ class TestSecurity():
s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
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',
'version', 'signature', 'id'])
assert sass.version == "2.0"
assert sass.id == "11122"
item = self.sec.check_signature(s_response,
node_name=class_name(s_response))
item = self.sec.check_signature(response2, class_name(response),
s_response)
assert isinstance(item, samlp.Response)
@@ -217,24 +221,21 @@ class TestSecurity():
s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
print s_response.keyswv()
print s_response.signature.keyswv()
print s_response.signature.key_info.keyswv()
ci = "".join(sigver.cert_from_instance(s_response)[0].split())
print ci
print self.sec.my_cert
response2 = response_from_string(s_response)
ci = "".join(sigver.cert_from_instance(response2)[0].split())
assert ci == self.sec.my_cert
res = self.sec.verify_signature("%s" % s_response,
node_name=class_name(samlp.Response()))
assert res
res = self.sec._check_signature("%s" % s_response, s_response,
class_name(s_response))
assert res == s_response
res = self.sec._check_signature(s_response, response2,
class_name(response2), s_response)
assert res == response2
def test_sign_verify_assertion_with_cert_from_instance(self):
assertion = factory( saml.Assertion,
@@ -251,16 +252,15 @@ class TestSecurity():
to_sign = [(class_name(assertion), assertion.id)]
s_assertion = sigver.signed_instance_factory(assertion, self.sec, to_sign)
print s_assertion
ci = "".join(sigver.cert_from_instance(s_assertion)[0].split())
ass = assertion_from_string(s_assertion)
ci = "".join(sigver.cert_from_instance(ass)[0].split())
assert ci == self.sec.my_cert
res = self.sec.verify_signature("%s" % s_assertion,
node_name=class_name(s_assertion))
res = self.sec.verify_signature("%s" % s_assertion,
node_name=class_name(ass))
assert res
res = self.sec._check_signature("%s" % s_assertion, s_assertion,
class_name(s_assertion))
res = self.sec._check_signature(s_assertion, ass, class_name(ass))
assert res
@@ -285,8 +285,9 @@ class TestSecurity():
s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
response2 = response_from_string(s_response)
# Change something that should make everything fail
s_response.id = "23456"
response2.id = "23456"
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 StatusResponse
from saml2.response import AuthnResponse
from saml2.sigver import security_context
from saml2.sigver import MissingKey
from saml2.sigver import security_context, MissingKey
from pytest import raises
XML_RESPONSE_FILE = "saml_signed.xml"
XML_RESPONSE_FILE2 = "saml2_response.xml"
def _eq(l1,l2):
return set(l1) == set(l2)
IDENTITY = {"eduPersonAffiliation": ["staff", "member"],
"surName": ["Jeter"], "givenName": ["Derek"],
"mail": ["foo@gmail.com"]}
"mail": ["foo@gmail.com"],
"title": ["shortstop"]}
class TestResponse:
def setup_class(self):
@@ -62,7 +61,7 @@ class TestResponse:
self.conf = conf
def test_1(self):
xml_response = ("%s" % (self._resp_,)).split("\n")[1]
xml_response = ("%s" % (self._resp_,))
resp = response_factory(xml_response, self.conf,
return_addr="http://lingon.catalogix.se:8087/",
outstanding_queries={"id12": "http://localhost:8088/sso"},
@@ -72,7 +71,7 @@ class TestResponse:
assert isinstance(resp, AuthnResponse)
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,
return_addr="http://lingon.catalogix.se:8087/",
outstanding_queries={"id12": "http://localhost:8088/sso"},

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
__author__ = 'rolandh'
from saml2.virtual_org import VirtualOrg
from saml2 import config
from saml2.client import Saml2Client
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))
session_info = SESSION_INFO_PATTERN.copy()
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["not_on_or_after"] = not_on_or_after
# subject_id, entity_id, info, timestamp
@@ -31,14 +30,13 @@ class TestVirtualOrg():
def test_mta(self):
aas = self.vo.members_to_ask("abcdefgh")
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:idp' in aas
def test_unknown_subject(self):
aas = self.vo.members_to_ask("01234567")
print aas
assert len(aas) == 0
assert len(aas) == 2
def test_id(self):
id = self.vo.get_common_identifier("abcdefgh")
@@ -60,14 +58,13 @@ class TestVirtualOrg_2():
def test_mta(self):
aas = self.sp.vorg.members_to_ask("abcdefgh")
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:idp' in aas
def test_unknown_subject(self):
aas = self.sp.vorg.members_to_ask("01234567")
print aas
assert len(aas) == 0
assert len(aas) == 2
def test_id(self):
id = self.sp.vorg.get_common_identifier("abcdefgh")

View File

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