Complete rewrite of the metadata handling package.
Switched from using httplib2 to requests.
This commit is contained in:
@@ -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)
|
||||
|
@@ -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):
|
||||
"""
|
||||
|
@@ -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
|
@@ -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],
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
|
@@ -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))
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
@@ -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)
|
||||
|
@@ -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
@@ -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 ):
|
@@ -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()
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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"],
|
||||
},
|
||||
|
@@ -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>
|
||||
|
@@ -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#">
|
||||
|
@@ -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
|
@@ -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"],
|
||||
},
|
||||
|
@@ -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"],
|
||||
},
|
||||
|
@@ -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',
|
||||
|
@@ -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
47
tests/test_22_mdie.py
Normal 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
463
tests/test_30_mdstore.py
Normal 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"
|
@@ -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"
|
@@ -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'
|
||||
|
@@ -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))
|
||||
|
||||
|
@@ -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"},
|
||||
|
@@ -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"
|
||||
|
@@ -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__
|
||||
|
@@ -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()
|
@@ -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']}
|
@@ -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")
|
||||
|
@@ -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()
|
||||
|
||||
|
Reference in New Issue
Block a user