Added necessary files for testing the setup against a local IdP
This commit is contained in:
@@ -112,6 +112,8 @@ class SAML2client(object):
|
|||||||
help="Return the SP metadata")
|
help="Return the SP metadata")
|
||||||
self._parser.add_argument("-l", dest="list", action="store_true",
|
self._parser.add_argument("-l", dest="list", action="store_true",
|
||||||
help="List all the test flows as a JSON object")
|
help="List all the test flows as a JSON object")
|
||||||
|
self._parser.add_argument("-c", dest="spconfig", default="config_file",
|
||||||
|
help="Configuration file for the SP")
|
||||||
self._parser.add_argument("oper", nargs="?", help="Which test to run")
|
self._parser.add_argument("oper", nargs="?", help="Which test to run")
|
||||||
|
|
||||||
self.interactions = None
|
self.interactions = None
|
||||||
@@ -126,7 +128,7 @@ class SAML2client(object):
|
|||||||
|
|
||||||
def sp_configure(self, metadata_construction=False):
|
def sp_configure(self, metadata_construction=False):
|
||||||
sys.path.insert(0, ".")
|
sys.path.insert(0, ".")
|
||||||
mod = import_module("config_file")
|
mod = import_module(self.args.spconfig)
|
||||||
if self.args.sp_id is None:
|
if self.args.sp_id is None:
|
||||||
if len(mod.CONFIG) == 1:
|
if len(mod.CONFIG) == 1:
|
||||||
self.args.sp_id = mod.CONFIG.keys()[0]
|
self.args.sp_id = mod.CONFIG.keys()[0]
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import inspect
|
import inspect
|
||||||
import urllib
|
import urllib
|
||||||
from saml2 import BINDING_HTTP_REDIRECT
|
from saml2 import BINDING_HTTP_REDIRECT, BINDING_URI
|
||||||
from saml2 import BINDING_HTTP_POST
|
from saml2 import BINDING_HTTP_POST
|
||||||
from saml2 import BINDING_SOAP
|
from saml2 import BINDING_SOAP
|
||||||
from saml2.client import Saml2Client
|
from saml2.client import Saml2Client
|
||||||
@@ -279,6 +279,10 @@ def do_query(client, oper, httpc, trace, interaction, entity_id, environ, cjar,
|
|||||||
htargs["data"] = data
|
htargs["data"] = data
|
||||||
htargs["headers"] = tuple_list2dict(htargs["headers"])
|
htargs["headers"] = tuple_list2dict(htargs["headers"])
|
||||||
res = httpc.send(loc, "POST", **htargs)
|
res = httpc.send(loc, "POST", **htargs)
|
||||||
|
elif args["binding"] == BINDING_URI:
|
||||||
|
response_args["binding"] = BINDING_URI
|
||||||
|
htargs = client.use_http_uri(_req_str, "SAMLRequest", loc)
|
||||||
|
res = httpc.send(htargs["url"], "GET")
|
||||||
else:
|
else:
|
||||||
res = None
|
res = None
|
||||||
|
|
||||||
@@ -288,11 +292,16 @@ def do_query(client, oper, httpc, trace, interaction, entity_id, environ, cjar,
|
|||||||
break
|
break
|
||||||
|
|
||||||
if res:
|
if res:
|
||||||
|
if args["binding"] == BINDING_URI:
|
||||||
|
response = res.text
|
||||||
|
else:
|
||||||
|
|
||||||
response_args["outstanding"] = {req.id: "/"}
|
response_args["outstanding"] = {req.id: "/"}
|
||||||
# deal with redirect, should in the end give me a response
|
# deal with redirect, should in the end give me a response
|
||||||
try:
|
try:
|
||||||
response = intermit(client, res, httpc, environ, trace, cjar,
|
response = intermit(client, res, httpc, environ, trace,
|
||||||
interaction, test_output, features)
|
cjar, interaction, test_output,
|
||||||
|
features)
|
||||||
except FatalError:
|
except FatalError:
|
||||||
environ["FatalError"] = True
|
environ["FatalError"] = True
|
||||||
response = None
|
response = None
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from saml2 import BINDING_HTTP_REDIRECT
|
from saml2 import BINDING_HTTP_REDIRECT, BINDING_URI
|
||||||
from saml2 import BINDING_SOAP
|
from saml2 import BINDING_SOAP
|
||||||
from saml2 import BINDING_HTTP_POST
|
from saml2 import BINDING_HTTP_POST
|
||||||
from saml2.saml import NAMEID_FORMAT_PERSISTENT
|
from saml2.saml import NAMEID_FORMAT_PERSISTENT
|
||||||
@@ -66,11 +66,7 @@ class LogOutRequest(Saml2IntRequest):
|
|||||||
|
|
||||||
class AssertionIDRequest(Request):
|
class AssertionIDRequest(Request):
|
||||||
request = "assertion_id_request"
|
request = "assertion_id_request"
|
||||||
_args = {"binding": BINDING_SOAP}
|
_args = {"binding": BINDING_URI}
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
Request.__init__(self)
|
|
||||||
self.tests["post"].append(VerifySuccessStatus)
|
|
||||||
|
|
||||||
def setup(self, environ):
|
def setup(self, environ):
|
||||||
resp = environ["response"][-1].response
|
resp = environ["response"][-1].response
|
||||||
@@ -92,7 +88,7 @@ class AuthnQuery(Request):
|
|||||||
|
|
||||||
class NameIDMappeingRequest(Request):
|
class NameIDMappeingRequest(Request):
|
||||||
request = "name_id_mapping_request"
|
request = "name_id_mapping_request"
|
||||||
_args = {"binding": BINDING_HTTP_REDIRECT}
|
_args = {"binding": BINDING_SOAP}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Request.__init__(self)
|
Request.__init__(self)
|
||||||
|
70
tests/config_file.py
Executable file
70
tests/config_file.py
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
from saml2 import BINDING_PAOS
|
||||||
|
from saml2 import BINDING_SOAP
|
||||||
|
from saml2 import BINDING_HTTP_ARTIFACT
|
||||||
|
from saml2 import BINDING_HTTP_POST
|
||||||
|
from saml2 import BINDING_HTTP_REDIRECT
|
||||||
|
from saml2.sigver import get_xmlsec_binary
|
||||||
|
|
||||||
|
try:
|
||||||
|
XMLSEC_BINARY = get_xmlsec_binary(["/opt/local/bin"])
|
||||||
|
except Exception:
|
||||||
|
XMLSEC_BINARY = ""
|
||||||
|
|
||||||
|
#BASE = "http://lingon.ladok.umu.se:8087"
|
||||||
|
BASE = "http://localhost:8087"
|
||||||
|
|
||||||
|
_CONFIG = {
|
||||||
|
"entityid" : "%s/sp.xml" % BASE,
|
||||||
|
"name" : "SAML2 test tool",
|
||||||
|
"description": "Simplest possible",
|
||||||
|
"service": {
|
||||||
|
"sp": {
|
||||||
|
"endpoints":{
|
||||||
|
"assertion_consumer_service": [
|
||||||
|
("%s/acs/post" % BASE, BINDING_HTTP_POST),
|
||||||
|
("%s/acs/redirect" % BASE, BINDING_HTTP_REDIRECT),
|
||||||
|
("%s/acs/artifact" % BASE, BINDING_HTTP_ARTIFACT),
|
||||||
|
("%s/ecp" % BASE, BINDING_PAOS)
|
||||||
|
],
|
||||||
|
"single_logout_service": [
|
||||||
|
("%s/sls" % BASE, BINDING_SOAP)
|
||||||
|
],
|
||||||
|
"artifact_resolution_service":[
|
||||||
|
("%s/ars" % BASE, BINDING_SOAP)
|
||||||
|
],
|
||||||
|
"manage_name_id_service":[
|
||||||
|
("%s/mni" % BASE, BINDING_HTTP_POST),
|
||||||
|
("%s/mni" % BASE, BINDING_HTTP_REDIRECT),
|
||||||
|
("%s/mni" % BASE, BINDING_SOAP),
|
||||||
|
("%s/acs/artifact" % BASE, BINDING_HTTP_ARTIFACT)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"key_file" : "keys/mykey.pem",
|
||||||
|
"cert_file" : "keys/mycert.pem",
|
||||||
|
"xmlsec_binary" : XMLSEC_BINARY,
|
||||||
|
"subject_data": "subject_data.db",
|
||||||
|
"accepted_time_diff": 60,
|
||||||
|
"attribute_map_dir" : "attributemaps",
|
||||||
|
"organization": {
|
||||||
|
"name": ("AB Exempel", "se"),
|
||||||
|
"display_name": ("AB Exempel", "se"),
|
||||||
|
"url": "http://www.example.org",
|
||||||
|
},
|
||||||
|
"contact_person": [{
|
||||||
|
"given_name": "Roland",
|
||||||
|
"sur_name": "Hedberg",
|
||||||
|
"telephone_number": "+46 70 100 0000",
|
||||||
|
"email_address": ["tech@eample.com",
|
||||||
|
"tech@example.org"],
|
||||||
|
"contact_type": "technical"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"secret": "0123456789",
|
||||||
|
"only_use_keys_in_metadata": False
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG = {
|
||||||
|
"1": _CONFIG
|
||||||
|
}
|
28
tests/idp/htdocs/login.mako
Normal file
28
tests/idp/htdocs/login.mako
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<%inherit file="root.mako"/>
|
||||||
|
|
||||||
|
<h1>Please log in</h1>
|
||||||
|
<p class="description">
|
||||||
|
To register it's quite simple: enter a login and a password
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form action="${action}" method="post">
|
||||||
|
<input type="hidden" name="key" value="${key}"/>
|
||||||
|
<input type="hidden" name="came_from" value="${came_from}"/>
|
||||||
|
|
||||||
|
<div class="label">
|
||||||
|
<label for="login">Username</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="text" name="login" value="${login}"/><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="label">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="password" name="password"
|
||||||
|
value="${password}"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input class="submit" type="submit" name="form.submitted" value="Log In"/>
|
||||||
|
</form>
|
782
tests/idp/idp.py
Executable file
782
tests/idp/idp.py
Executable file
@@ -0,0 +1,782 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
import urllib
|
||||||
|
import time
|
||||||
|
from hashlib import sha1
|
||||||
|
|
||||||
|
from urlparse import parse_qs
|
||||||
|
from Cookie import SimpleCookie
|
||||||
|
|
||||||
|
from saml2 import server, BINDING_HTTP_ARTIFACT, BINDING_URI
|
||||||
|
from saml2 import BINDING_SOAP
|
||||||
|
from saml2 import BINDING_HTTP_REDIRECT
|
||||||
|
from saml2 import BINDING_HTTP_POST
|
||||||
|
from saml2 import time_util
|
||||||
|
from saml2.httputil import Response, NotFound
|
||||||
|
from saml2.httputil import get_post
|
||||||
|
from saml2.httputil import Redirect
|
||||||
|
from saml2.httputil import Unauthorized
|
||||||
|
from saml2.httputil import BadRequest
|
||||||
|
from saml2.httputil import ServiceError
|
||||||
|
from saml2.ident import Unknown
|
||||||
|
from saml2.s_utils import rndstr
|
||||||
|
from saml2.s_utils import PolicyError
|
||||||
|
from saml2.saml import AUTHN_PASSWORD
|
||||||
|
from saml2.saml import NAMEID_FORMAT_PERSISTENT
|
||||||
|
from saml2.saml import NameID
|
||||||
|
|
||||||
|
logger = logging.getLogger("saml2.idp")
|
||||||
|
|
||||||
|
def _expiration(timeout, format="%a, %d-%b-%Y %H:%M:%S GMT"):
|
||||||
|
if timeout == "now":
|
||||||
|
return time_util.instant(format)
|
||||||
|
elif timeout == "dawn":
|
||||||
|
return time.strftime(format, time.gmtime(0))
|
||||||
|
else:
|
||||||
|
# validity time should match lifetime of assertions
|
||||||
|
return time_util.in_a_while(minutes=timeout, format=format)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
def dict_to_table(ava, lev=0, width=1):
|
||||||
|
txt = ['<table border=%s bordercolor="black">\n' % width]
|
||||||
|
for prop, valarr in ava.items():
|
||||||
|
txt.append("<tr>\n")
|
||||||
|
if isinstance(valarr, basestring):
|
||||||
|
txt.append("<th>%s</th>\n" % str(prop))
|
||||||
|
try:
|
||||||
|
txt.append("<td>%s</td>\n" % valarr.encode("utf8"))
|
||||||
|
except AttributeError:
|
||||||
|
txt.append("<td>%s</td>\n" % valarr)
|
||||||
|
elif isinstance(valarr, list):
|
||||||
|
index = 0
|
||||||
|
num = len(valarr)
|
||||||
|
for val in valarr:
|
||||||
|
if not index:
|
||||||
|
txt.append("<th rowspan=%d>%s</td>\n" % (len(valarr), prop))
|
||||||
|
else:
|
||||||
|
txt.append("<tr>\n")
|
||||||
|
if isinstance(val, dict):
|
||||||
|
txt.append("<td>\n")
|
||||||
|
txt.extend(dict_to_table(val, lev+1, width-1))
|
||||||
|
txt.append("</td>\n")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
txt.append("<td>%s</td>\n" % val.encode("utf8"))
|
||||||
|
except AttributeError:
|
||||||
|
txt.append("<td>%s</td>\n" % val)
|
||||||
|
if num > 1:
|
||||||
|
txt.append("</tr>\n")
|
||||||
|
num -= 1
|
||||||
|
index += 1
|
||||||
|
elif isinstance(valarr, dict):
|
||||||
|
txt.append("<th>%s</th>\n" % prop)
|
||||||
|
txt.append("<td>\n")
|
||||||
|
txt.extend(dict_to_table(valarr, lev+1, width-1))
|
||||||
|
txt.append("</td>\n")
|
||||||
|
txt.append("</tr>\n")
|
||||||
|
txt.append('</table>\n')
|
||||||
|
return txt
|
||||||
|
|
||||||
|
def unpack_redirect(environ):
|
||||||
|
if "QUERY_STRING" in environ:
|
||||||
|
_qs = environ["QUERY_STRING"]
|
||||||
|
return dict([(k,v[0]) for k,v in parse_qs(_qs).items()])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def unpack_post(environ):
|
||||||
|
try:
|
||||||
|
return dict([(k,v[0]) for k,v in parse_qs(get_post(environ))])
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def unpack_soap(environ):
|
||||||
|
try:
|
||||||
|
query = get_post(environ)
|
||||||
|
return {"SAMLRequest": query, "RelayState": ""}
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def unpack_artifact(environ):
|
||||||
|
if environ["REQUEST_METHOD"] == "GET":
|
||||||
|
_dict = unpack_redirect(environ)
|
||||||
|
elif environ["REQUEST_METHOD"] == "POST":
|
||||||
|
_dict = unpack_post(environ)
|
||||||
|
else:
|
||||||
|
_dict = None
|
||||||
|
return _dict
|
||||||
|
|
||||||
|
def dict2list_of_tuples(d):
|
||||||
|
return [(k,v) for k,v in d.items()]
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _operation(environ, start_response, user, _dict, func, binding):
|
||||||
|
logger.debug("_operation: %s" % _dict)
|
||||||
|
if not _dict:
|
||||||
|
resp = BadRequest('Error parsing request or no request')
|
||||||
|
return resp(environ, start_response)
|
||||||
|
else:
|
||||||
|
return func(environ, start_response, user, _dict["SAMLRequest"],
|
||||||
|
binding, _dict["RelayState"])
|
||||||
|
|
||||||
|
def _artifact_oper(environ, start_response, user, _dict, func):
|
||||||
|
if not _dict:
|
||||||
|
resp = BadRequest("Missing query")
|
||||||
|
return resp(environ, start_response)
|
||||||
|
else:
|
||||||
|
# exchange artifact for request
|
||||||
|
request = IDP.artifact2message(_dict["SAMLart"], "spsso")
|
||||||
|
|
||||||
|
return func(environ, start_response, user, request,
|
||||||
|
BINDING_HTTP_ARTIFACT, _dict["RelayState"])
|
||||||
|
|
||||||
|
def _response(environ, start_response, binding, http_args):
|
||||||
|
if binding == BINDING_HTTP_ARTIFACT:
|
||||||
|
resp = Redirect()
|
||||||
|
else:
|
||||||
|
resp = Response(http_args["data"], headers=http_args["headers"])
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
AUTHN = (AUTHN_PASSWORD, "http://lingon.catalogix.se/login")
|
||||||
|
|
||||||
|
REPOZE_ID_EQUIVALENT = "uid"
|
||||||
|
FORM_SPEC = """<form name="myform" method="post" action="%s">
|
||||||
|
<input type="hidden" name="SAMLResponse" value="%s" />
|
||||||
|
<input type="hidden" name="RelayState" value="%s" />
|
||||||
|
</form>"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# === Single log in ====
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _sso(environ, start_response, user, query, binding, relay_state=""):
|
||||||
|
logger.info("--- In SSO ---")
|
||||||
|
logger.debug("user: %s" % user)
|
||||||
|
|
||||||
|
if not query:
|
||||||
|
logger.info("Missing QUERY")
|
||||||
|
resp = Unauthorized('Unknown user')
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
# base 64 encoded request
|
||||||
|
req_info = IDP.parse_authn_request(query, binding=binding)
|
||||||
|
logger.info("parsed OK")
|
||||||
|
logger.info("%s" % req_info)
|
||||||
|
_authn_req = req_info.message
|
||||||
|
|
||||||
|
resp_args = IDP.response_args(_authn_req)
|
||||||
|
|
||||||
|
identity = USERS[user]
|
||||||
|
logger.info("Identity: %s" % (identity,))
|
||||||
|
|
||||||
|
if REPOZE_ID_EQUIVALENT:
|
||||||
|
identity[REPOZE_ID_EQUIVALENT] = user
|
||||||
|
try:
|
||||||
|
authn_resp = IDP.create_authn_response(identity, userid=user,
|
||||||
|
authn=AUTHN, **resp_args)
|
||||||
|
except Exception, excp:
|
||||||
|
logger.error("Exception: %s" % (excp,))
|
||||||
|
resp = ServiceError("Exception: %s" % (excp,))
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
logger.info("AuthNResponse: %s" % authn_resp)
|
||||||
|
binding, destination = IDP.pick_binding("assertion_consumer_service",
|
||||||
|
entity_id=_authn_req.issuer.text)
|
||||||
|
http_args = IDP.apply_binding(binding, "%s" % authn_resp, destination,
|
||||||
|
relay_state, response=True)
|
||||||
|
|
||||||
|
return _response(environ, start_response, binding, http_args)
|
||||||
|
|
||||||
|
def sso(environ, start_response, user):
|
||||||
|
""" This is the HTTP-redirect endpoint """
|
||||||
|
|
||||||
|
_dict = unpack_redirect(environ)
|
||||||
|
logger.debug("_dict: %s" % _dict)
|
||||||
|
# pick up the stored original query
|
||||||
|
logger.debug("keys: %s" % IDP.ticket.keys())
|
||||||
|
_req = IDP.ticket[_dict["key"]]
|
||||||
|
del IDP.ticket[_dict["key"]]
|
||||||
|
|
||||||
|
return _operation(environ, start_response, user, _req, _sso,
|
||||||
|
BINDING_HTTP_REDIRECT)
|
||||||
|
|
||||||
|
def sso_post(environ, start_response, user):
|
||||||
|
"""
|
||||||
|
The HTTP-Post endpoint
|
||||||
|
"""
|
||||||
|
logger.info("--- In SSO POST ---")
|
||||||
|
logger.debug("user: %s" % user)
|
||||||
|
|
||||||
|
_dict = unpack_post(environ)
|
||||||
|
|
||||||
|
logger.debug("message: %s" % _dict)
|
||||||
|
logger.debug("keys: %s" % IDP.ticket.keys())
|
||||||
|
_request = IDP.ticket[_dict["key"]]
|
||||||
|
del IDP.ticket[_dict["key"]]
|
||||||
|
|
||||||
|
return _operation(environ, start_response, user, _request, _sso,
|
||||||
|
BINDING_HTTP_POST)
|
||||||
|
|
||||||
|
def sso_art(environ, start_response, user):
|
||||||
|
# Can be either by HTTP_Redirect or HTTP_POST
|
||||||
|
_dict = unpack_artifact(environ)
|
||||||
|
_request = IDP.ticket[_dict["key"]]
|
||||||
|
del IDP.ticket[_dict["key"]]
|
||||||
|
return _artifact_oper(environ, start_response, user, _request, _sso)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# === Authentication ====
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def not_authn(environ, start_response):
|
||||||
|
# store the request and redirect to login page
|
||||||
|
logger.info("not_authn ENV: %s" % environ)
|
||||||
|
|
||||||
|
loc = "http://%s/login" % (environ["HTTP_HOST"])
|
||||||
|
|
||||||
|
if environ["REQUEST_METHOD"] == "GET":
|
||||||
|
_dict = unpack_redirect(environ)
|
||||||
|
elif environ["REQUEST_METHOD"] == "POST":
|
||||||
|
_dict = unpack_post(environ)
|
||||||
|
else:
|
||||||
|
_dict = None
|
||||||
|
|
||||||
|
if not _dict:
|
||||||
|
resp = BadRequest("Missing query")
|
||||||
|
else:
|
||||||
|
logger.info("query: %s" % _dict)
|
||||||
|
# store the original request
|
||||||
|
key = sha1("%s" % _dict).hexdigest()
|
||||||
|
IDP.ticket[str(key)] = _dict
|
||||||
|
|
||||||
|
loc += "?%s" % urllib.urlencode({"came_from": environ["PATH_INFO"],
|
||||||
|
"key": key})
|
||||||
|
headers = [('Content-Type', 'text/plain')]
|
||||||
|
|
||||||
|
logger.debug("location: %s" % loc)
|
||||||
|
logger.debug("headers: %s" % headers)
|
||||||
|
|
||||||
|
resp = Redirect(loc, headers=headers)
|
||||||
|
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
def do_authentication(environ, start_response, cookie=None):
|
||||||
|
"""
|
||||||
|
Display the login form
|
||||||
|
"""
|
||||||
|
query = parse_qs(environ["QUERY_STRING"])
|
||||||
|
|
||||||
|
logger.info("The login page")
|
||||||
|
if cookie:
|
||||||
|
headers = [cookie]
|
||||||
|
else:
|
||||||
|
headers = []
|
||||||
|
|
||||||
|
resp = Response(mako_template="login.mako", template_lookup=LOOKUP,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
argv = {
|
||||||
|
"action": "/verify",
|
||||||
|
"came_from": query["came_from"][0],
|
||||||
|
"login": "",
|
||||||
|
"password": "",
|
||||||
|
"key": query["key"][0]
|
||||||
|
}
|
||||||
|
logger.info("do_authentication argv: %s" % argv)
|
||||||
|
return resp(environ, start_response, **argv)
|
||||||
|
|
||||||
|
def verify_username_and_password(dic):
|
||||||
|
global PASSWD
|
||||||
|
# verify username and password
|
||||||
|
for user, pwd in PASSWD:
|
||||||
|
if user == dic["login"][0]:
|
||||||
|
if pwd == dic["password"][0]:
|
||||||
|
return True, user
|
||||||
|
|
||||||
|
return False, ""
|
||||||
|
|
||||||
|
|
||||||
|
def do_verify(environ, start_response, _user):
|
||||||
|
query = parse_qs(get_post(environ))
|
||||||
|
|
||||||
|
logger.debug("do_verify: %s" % query)
|
||||||
|
|
||||||
|
_ok, user = verify_username_and_password(query)
|
||||||
|
if not _ok:
|
||||||
|
resp = Unauthorized("Unknown user or wrong password")
|
||||||
|
else:
|
||||||
|
id = rndstr()
|
||||||
|
IDP.authn[id] = user
|
||||||
|
logger.debug("Register %s under '%s'" % (user, id))
|
||||||
|
kaka = set_cookie("idpauthn", "/", id)
|
||||||
|
lox = "http://%s%s?id=%s&key=%s" % (environ["HTTP_HOST"],
|
||||||
|
query["came_from"][0], id,
|
||||||
|
query["key"][0])
|
||||||
|
logger.debug("Redirect => %s" % lox)
|
||||||
|
resp = Redirect(lox, headers=[kaka], content="text/html")
|
||||||
|
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# === Single log out ===
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#def _subject_sp_info(req_info):
|
||||||
|
# # look for the subject
|
||||||
|
# subject = req_info.subject_id()
|
||||||
|
# subject = subject.text.strip()
|
||||||
|
# sp_entity_id = req_info.message.issuer.text.strip()
|
||||||
|
# return subject, sp_entity_id
|
||||||
|
|
||||||
|
def _slo(environ, start_response, _, request, binding, relay_state=""):
|
||||||
|
logger.info("--- Single Log Out Service ---")
|
||||||
|
try:
|
||||||
|
req_info = IDP.parse_logout_request(request, binding)
|
||||||
|
except Exception, exc:
|
||||||
|
resp = BadRequest("%s" % exc)
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
msg = req_info.message
|
||||||
|
if msg.name_id:
|
||||||
|
lid = IDP.ident.find_local_id(msg.name_id)
|
||||||
|
logger.info("local identifier: %s" % lid)
|
||||||
|
# remove the authentication
|
||||||
|
try:
|
||||||
|
IDP.remove_authn_statements(msg.name_id)
|
||||||
|
except KeyError,exc:
|
||||||
|
resp = ServiceError("%s" % exc)
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
resp = IDP.create_logout_response(msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
hinfo = IDP.apply_binding(binding, "%s" % resp, "", relay_state)
|
||||||
|
except Exception, exc:
|
||||||
|
resp = ServiceError("%s" % exc)
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
logger.info("Header: %s" % (hinfo["headers"],))
|
||||||
|
#_tlh = dict2list_of_tuples(hinfo["headers"])
|
||||||
|
delco = delete_cookie(environ, "idpauthn")
|
||||||
|
if delco:
|
||||||
|
hinfo["headers"].append(delco)
|
||||||
|
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
# -- bindings --
|
||||||
|
|
||||||
|
def slo(environ, start_response, user):
|
||||||
|
""" Expects a HTTP-redirect logout request """
|
||||||
|
|
||||||
|
_dict = unpack_redirect(environ)
|
||||||
|
return _operation(environ, start_response, user, _dict, _slo,
|
||||||
|
BINDING_HTTP_REDIRECT)
|
||||||
|
|
||||||
|
def slo_post(environ, start_response, user):
|
||||||
|
""" Expects a HTTP-POST logout request """
|
||||||
|
|
||||||
|
_dict = unpack_post(environ)
|
||||||
|
return _operation(environ, start_response, user, _dict, _slo,
|
||||||
|
BINDING_HTTP_POST)
|
||||||
|
|
||||||
|
def slo_art(environ, start_response, user):
|
||||||
|
# Can be either by HTTP_Redirect or HTTP_POST
|
||||||
|
_dict = unpack_artifact(environ)
|
||||||
|
return _artifact_oper(environ, start_response, user, _dict, _slo)
|
||||||
|
|
||||||
|
def slo_soap(environ, start_response, user=None):
|
||||||
|
"""
|
||||||
|
Single log out using HTTP_SOAP binding
|
||||||
|
"""
|
||||||
|
_dict = unpack_soap(environ)
|
||||||
|
return _operation(environ, start_response, user, _dict, _slo,
|
||||||
|
BINDING_SOAP)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def whoami(environ, start_response, user):
|
||||||
|
start_response('200 OK', [('Content-Type', 'text/html')])
|
||||||
|
identity = USERS[user].copy()
|
||||||
|
for prop in ["login", "password"]:
|
||||||
|
try:
|
||||||
|
del identity[prop]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
response = dict_to_table(identity)
|
||||||
|
return response[:]
|
||||||
|
|
||||||
|
def not_found(environ, start_response):
|
||||||
|
"""Called if no URL matches."""
|
||||||
|
start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
|
||||||
|
return ['Not Found']
|
||||||
|
|
||||||
|
|
||||||
|
PASSWD = [("roland", "dianakra"),
|
||||||
|
("babs", "howes"),
|
||||||
|
("upper", "crust")]
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def kaka2user(kaka):
|
||||||
|
logger.debug("KAKA: %s" % kaka)
|
||||||
|
if kaka:
|
||||||
|
cookie_obj = SimpleCookie(kaka)
|
||||||
|
morsel = cookie_obj.get("idpauthn", None)
|
||||||
|
if morsel:
|
||||||
|
return IDP.authn[morsel.value]
|
||||||
|
else:
|
||||||
|
logger.debug()
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Manage Name ID service
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _mni(environ, start_response, user, query, binding, relay_state=""):
|
||||||
|
logger.info("--- Manage Name ID Service ---")
|
||||||
|
req = IDP.parse_manage_name_id_response(query, binding)
|
||||||
|
|
||||||
|
# Do the necessary stuff
|
||||||
|
in_response_to = req.message.id
|
||||||
|
name_id = NameID(format=NAMEID_FORMAT_PERSISTENT, text="foobar")
|
||||||
|
|
||||||
|
info = IDP.response_args(req)
|
||||||
|
_resp = IDP.create_manage_name_id_response(name_id, **info)
|
||||||
|
|
||||||
|
# It's using SOAP binding
|
||||||
|
hinfo = IDP.apply_binding(binding, "%s" % _resp, "", relay_state,
|
||||||
|
response=True)
|
||||||
|
|
||||||
|
resp = Response(hinfo["data"],
|
||||||
|
headers=dict2list_of_tuples(hinfo["headers"]))
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
def mni(environ, start_response, user):
|
||||||
|
""" Expects a HTTP-redirect logout request """
|
||||||
|
|
||||||
|
_dict = unpack_redirect(environ)
|
||||||
|
return _operation(environ, start_response, user, _dict, _mni,
|
||||||
|
BINDING_HTTP_REDIRECT)
|
||||||
|
|
||||||
|
def mni_post(environ, start_response, user):
|
||||||
|
""" Expects a HTTP-POST logout request """
|
||||||
|
|
||||||
|
_dict = unpack_post(environ)
|
||||||
|
return _operation(environ, start_response, user, _dict, _mni,
|
||||||
|
BINDING_HTTP_POST)
|
||||||
|
|
||||||
|
def mni_soap(environ, start_response, user):
|
||||||
|
_dict = unpack_soap(environ)
|
||||||
|
return _operation(environ, start_response, user, _dict, _mni,
|
||||||
|
BINDING_SOAP)
|
||||||
|
|
||||||
|
def mni_art(environ, start_response, user):
|
||||||
|
# Could be by HTTP_REDIRECT or HTTP_POST
|
||||||
|
_dict = unpack_post(environ)
|
||||||
|
return _artifact_oper(environ, start_response, user, _dict, _mni)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# === Assertion ID request ===
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Only URI binding
|
||||||
|
def assertion_id_request(environ, start_response, user=None):
|
||||||
|
logger.info("--- Assertion ID Service ---")
|
||||||
|
_binding = BINDING_URI
|
||||||
|
|
||||||
|
_dict = unpack_artifact(environ)
|
||||||
|
logger.debug("INPUT: %s" % _dict)
|
||||||
|
# Presently only HTTP GET is supported
|
||||||
|
if "ID" in _dict:
|
||||||
|
aid = _dict["ID"]
|
||||||
|
else:
|
||||||
|
resp = BadRequest("Missing or faulty request")
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
try:
|
||||||
|
assertion = IDP.create_assertion_id_request_response(aid)
|
||||||
|
except Unknown:
|
||||||
|
resp = NotFound(aid)
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
hinfo = IDP.apply_binding(_binding, "%s" % assertion, response=True)
|
||||||
|
|
||||||
|
logger.debug("HINFO: %s" % hinfo)
|
||||||
|
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# === Artifact resolve service ===
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Only SOAP binding
|
||||||
|
def artifact_resolve_service(environ, start_response, user=None):
|
||||||
|
"""
|
||||||
|
:param environ: Execution environment
|
||||||
|
:param start_response: Function to start the response with
|
||||||
|
"""
|
||||||
|
logger.info("--- Artifact resolve Service ---")
|
||||||
|
_dict = unpack_soap(environ)
|
||||||
|
_binding = BINDING_SOAP
|
||||||
|
|
||||||
|
if not _dict:
|
||||||
|
resp = BadRequest("Missing or faulty request")
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
_req = IDP.parse_artifact_resolve("%s" % _dict["SAMLRequest"], _binding)
|
||||||
|
|
||||||
|
msg = IDP.create_artifact_response(_req, _req.artifact.text)
|
||||||
|
|
||||||
|
hinfo = IDP.apply_binding(_binding, "%s" % msg, "","",response=True)
|
||||||
|
|
||||||
|
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# === Authn query service ===
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Only SOAP binding
|
||||||
|
def authn_query_service(environ, start_response, user=None):
|
||||||
|
"""
|
||||||
|
:param environ: Execution environment
|
||||||
|
:param start_response: Function to start the response with
|
||||||
|
"""
|
||||||
|
logger.info("--- Authn Query Service ---")
|
||||||
|
_dict = unpack_soap(environ)
|
||||||
|
_binding = BINDING_SOAP
|
||||||
|
|
||||||
|
if not _dict:
|
||||||
|
resp = BadRequest("Missing or faulty request")
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
_req = IDP.parse_authn_query("%s" % _dict["SAMLRequest"], _binding)
|
||||||
|
_query = _req.message
|
||||||
|
|
||||||
|
msg = IDP.create_authn_query_response(_query.subject,
|
||||||
|
_query.requested_authn_context,
|
||||||
|
_query.session_index)
|
||||||
|
|
||||||
|
logger.debug("response: %s" % msg)
|
||||||
|
hinfo = IDP.apply_binding(_binding, "%s" % msg, "","",response=True)
|
||||||
|
|
||||||
|
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Name ID Mapping service
|
||||||
|
# When an entity that shares an identifier for a principal with an identity
|
||||||
|
# provider wishes to obtain a name identifier for the same principal in a
|
||||||
|
# particular format or federation namespace, it can send a request to
|
||||||
|
# the identity provider using this protocol.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _nim(environ, start_response, user, query, binding, relay_state=""):
|
||||||
|
req = IDP.parse_manage_name_id_response(query, binding)
|
||||||
|
|
||||||
|
# Do the necessary stuff
|
||||||
|
try:
|
||||||
|
name_id = IDP.ident.handle_name_id_mapping_request()
|
||||||
|
except Unknown:
|
||||||
|
resp = BadRequest("Unknown entity")
|
||||||
|
return resp(environ, start_response)
|
||||||
|
except PolicyError:
|
||||||
|
resp = BadRequest("Unknown entity")
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
info = IDP.response_args(req)
|
||||||
|
_resp = IDP.create_manage_name_id_response(name_id, **info)
|
||||||
|
|
||||||
|
# It's using SOAP binding
|
||||||
|
hinfo = IDP.apply_binding(binding, "%s" % _resp, "", "", response=True)
|
||||||
|
|
||||||
|
resp = Response(hinfo["data"],
|
||||||
|
headers=dict2list_of_tuples(hinfo["headers"]))
|
||||||
|
return resp(environ, start_response)
|
||||||
|
|
||||||
|
def nim(environ, start_response, user):
|
||||||
|
""" Expects a HTTP-redirect logout request """
|
||||||
|
|
||||||
|
_dict = unpack_redirect(environ)
|
||||||
|
return _operation(environ, start_response, user, _dict, _nim,
|
||||||
|
BINDING_HTTP_REDIRECT)
|
||||||
|
|
||||||
|
def nim_post(environ, start_response, user):
|
||||||
|
""" Expects a HTTP-POST logout request """
|
||||||
|
|
||||||
|
_dict = unpack_post(environ)
|
||||||
|
return _operation(environ, start_response, user, _dict, _nim,
|
||||||
|
BINDING_HTTP_POST)
|
||||||
|
|
||||||
|
def nim_soap(environ, start_response, user):
|
||||||
|
|
||||||
|
_dict = unpack_post(environ)
|
||||||
|
return _operation(environ, start_response, user, _dict, _nim,
|
||||||
|
BINDING_SOAP)
|
||||||
|
|
||||||
|
def nim_art(environ, start_response, user):
|
||||||
|
# Could be by HTTP_REDIRECT or HTTP_POST
|
||||||
|
|
||||||
|
_dict = unpack_artifact(environ)
|
||||||
|
return _artifact_oper(environ, start_response, user, _dict, _nim)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def delete_cookie(environ, name):
|
||||||
|
kaka = environ.get("HTTP_COOKIE", '')
|
||||||
|
if kaka:
|
||||||
|
cookie_obj = SimpleCookie(kaka)
|
||||||
|
morsel = cookie_obj.get(name, None)
|
||||||
|
cookie = SimpleCookie()
|
||||||
|
cookie[name] = ""
|
||||||
|
cookie[name]['path'] = "/"
|
||||||
|
logger.debug("Expire: %s" % morsel)
|
||||||
|
cookie[name]["expires"] = _expiration("dawn")
|
||||||
|
return tuple(cookie.output().split(": ", 1))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_cookie(name, path, value):
|
||||||
|
cookie = SimpleCookie()
|
||||||
|
cookie[name] = value
|
||||||
|
cookie[name]['path'] = "/"
|
||||||
|
cookie[name]["expires"] = _expiration(5) # 5 minutes from now
|
||||||
|
logger.debug("Cookie expires: %s" % cookie[name]["expires"])
|
||||||
|
return tuple(cookie.output().split(": ", 1))
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# map urls to functions
|
||||||
|
AUTHN_URLS = [
|
||||||
|
(r'whoami$', whoami),
|
||||||
|
(r'whoami/(.*)$', whoami),
|
||||||
|
# sso
|
||||||
|
(r'sso/post$', sso_post),
|
||||||
|
(r'sso/post/(.*)$', sso_post),
|
||||||
|
(r'sso/redirect$', sso),
|
||||||
|
(r'sso/redirect/(.*)$', sso),
|
||||||
|
(r'sso/art$', sso),
|
||||||
|
(r'sso/art/(.*)$', sso),
|
||||||
|
# slo
|
||||||
|
(r'slo/redirect$', slo),
|
||||||
|
(r'slo/redirect/(.*)$', slo),
|
||||||
|
(r'slo/post$', slo_post),
|
||||||
|
(r'slo/post/(.*)$', slo_post),
|
||||||
|
(r'slo/soap$', slo_soap),
|
||||||
|
(r'slo/soap/(.*)$', slo_soap),
|
||||||
|
#
|
||||||
|
(r'airs$', assertion_id_request),
|
||||||
|
(r'ars$', artifact_resolve_service),
|
||||||
|
# mni
|
||||||
|
(r'mni/post$', mni_post),
|
||||||
|
(r'mni/post/(.*)$', mni_post),
|
||||||
|
(r'mni/redirect$', mni),
|
||||||
|
(r'mni/redirect/(.*)$', mni),
|
||||||
|
(r'mni/art$', mni_art),
|
||||||
|
(r'mni/art/(.*)$', mni_art),
|
||||||
|
(r'mni/soap$', mni_soap),
|
||||||
|
(r'mni/soap/(.*)$', mni_soap),
|
||||||
|
# nim
|
||||||
|
(r'nim/post$', nim_post),
|
||||||
|
(r'nim/post/(.*)$', nim_post),
|
||||||
|
(r'nim/redirect$', nim),
|
||||||
|
(r'nim/redirect/(.*)$', nim),
|
||||||
|
(r'nim/art$', nim_art),
|
||||||
|
(r'nim/art/(.*)$', nim_art),
|
||||||
|
(r'nim/soap$', nim_soap),
|
||||||
|
(r'nim/soap/(.*)$', nim_soap),
|
||||||
|
#
|
||||||
|
(r'aqs$', authn_query_service)
|
||||||
|
]
|
||||||
|
|
||||||
|
NON_AUTHN_URLS = [
|
||||||
|
(r'login?(.*)$', do_authentication),
|
||||||
|
(r'verify?(.*)$', do_verify),
|
||||||
|
]
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def application(environ, start_response):
|
||||||
|
"""
|
||||||
|
The main WSGI application. Dispatch the current request to
|
||||||
|
the functions from above and store the regular expression
|
||||||
|
captures in the WSGI environment as `myapp.url_args` so that
|
||||||
|
the functions from above can access the url placeholders.
|
||||||
|
|
||||||
|
If nothing matches call the `not_found` function.
|
||||||
|
|
||||||
|
:param environ: The HTTP application environment
|
||||||
|
:param start_response: The application to run when the handling of the
|
||||||
|
request is done
|
||||||
|
:return: The response as a list of lines
|
||||||
|
"""
|
||||||
|
|
||||||
|
path = environ.get('PATH_INFO', '').lstrip('/')
|
||||||
|
kaka = environ.get("HTTP_COOKIE", None)
|
||||||
|
logger.info("<application> PATH: %s" % path)
|
||||||
|
|
||||||
|
if kaka:
|
||||||
|
logger.info("= KAKA =")
|
||||||
|
user = kaka2user(kaka)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
query = parse_qs(environ["QUERY_STRING"])
|
||||||
|
logger.debug("QUERY: %s" % query)
|
||||||
|
user = IDP.authn[query["id"][0]]
|
||||||
|
except KeyError:
|
||||||
|
user = None
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
logger.info("-- No USER --")
|
||||||
|
for regex, callback in NON_AUTHN_URLS:
|
||||||
|
match = re.search(regex, path)
|
||||||
|
if match is not None:
|
||||||
|
try:
|
||||||
|
environ['myapp.url_args'] = match.groups()[0]
|
||||||
|
except IndexError:
|
||||||
|
environ['myapp.url_args'] = path
|
||||||
|
logger.info("callback: %s" % (callback,))
|
||||||
|
return callback(environ, start_response, user)
|
||||||
|
for regex, callback in AUTHN_URLS:
|
||||||
|
match = re.search(regex, path)
|
||||||
|
if match is not None:
|
||||||
|
return not_authn(environ, start_response)
|
||||||
|
else:
|
||||||
|
for regex, callback in AUTHN_URLS:
|
||||||
|
match = re.search(regex, path)
|
||||||
|
if match is not None:
|
||||||
|
try:
|
||||||
|
environ['myapp.url_args'] = match.groups()[0]
|
||||||
|
except IndexError:
|
||||||
|
environ['myapp.url_args'] = path
|
||||||
|
logger.info("callback: %s" % (callback,))
|
||||||
|
return callback(environ, start_response, user)
|
||||||
|
return not_found(environ, start_response)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
from mako.lookup import TemplateLookup
|
||||||
|
ROOT = './'
|
||||||
|
LOOKUP = TemplateLookup(directories=[ROOT + 'templates', ROOT + 'htdocs'],
|
||||||
|
module_directory=ROOT + 'modules',
|
||||||
|
input_encoding='utf-8', output_encoding='utf-8')
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
from idp_user import USERS
|
||||||
|
from wsgiref.simple_server import make_server
|
||||||
|
|
||||||
|
PORT = 8088
|
||||||
|
|
||||||
|
IDP = server.Server(sys.argv[1])
|
||||||
|
IDP.ticket = {}
|
||||||
|
SRV = make_server('', PORT, application)
|
||||||
|
print "IdP listening on port: %s" % PORT
|
||||||
|
SRV.serve_forever()
|
122
tests/idp/idp_conf.py
Normal file
122
tests/idp/idp_conf.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
from saml2 import BINDING_HTTP_REDIRECT, BINDING_URI
|
||||||
|
from saml2 import BINDING_HTTP_ARTIFACT
|
||||||
|
from saml2 import BINDING_HTTP_POST
|
||||||
|
from saml2 import BINDING_SOAP
|
||||||
|
from saml2.saml import NAME_FORMAT_URI
|
||||||
|
from saml2.saml import NAMEID_FORMAT_TRANSIENT
|
||||||
|
from saml2.saml import NAMEID_FORMAT_PERSISTENT
|
||||||
|
|
||||||
|
try:
|
||||||
|
from saml2.sigver import get_xmlsec_binary
|
||||||
|
except ImportError:
|
||||||
|
get_xmlsec_binary = None
|
||||||
|
|
||||||
|
if get_xmlsec_binary:
|
||||||
|
xmlsec_path = get_xmlsec_binary(["/opt/local/bin"])
|
||||||
|
else:
|
||||||
|
xmlsec_path = '/usr/bin/xmlsec1'
|
||||||
|
|
||||||
|
#BASE = "http://lingon.ladok.umu.se:8088"
|
||||||
|
#BASE = "http://lingon.catalogix.se:8088"
|
||||||
|
BASE = "http://localhost:8088"
|
||||||
|
|
||||||
|
CONFIG={
|
||||||
|
"entityid" : "%s/idp.xml" % BASE,
|
||||||
|
"description": "My IDP",
|
||||||
|
"service": {
|
||||||
|
"aa": {
|
||||||
|
"endpoints" : {
|
||||||
|
"attribute_service": [
|
||||||
|
("%s/attr/post" % BASE, BINDING_HTTP_POST),
|
||||||
|
("%s/attr/soap" % BASE, BINDING_SOAP)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name_id_format": [NAMEID_FORMAT_TRANSIENT,
|
||||||
|
NAMEID_FORMAT_PERSISTENT]
|
||||||
|
},
|
||||||
|
"aq": {
|
||||||
|
"endpoints" : {
|
||||||
|
"authn_query_service": [
|
||||||
|
("%s/aqs" % BASE, BINDING_SOAP)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"idp": {
|
||||||
|
"name" : "Rolands IdP",
|
||||||
|
"endpoints" : {
|
||||||
|
"single_sign_on_service" : [
|
||||||
|
("%s/sso/redirect" % BASE, BINDING_HTTP_REDIRECT),
|
||||||
|
("%s/sso/post" % BASE, BINDING_HTTP_POST),
|
||||||
|
("%s/sso/art" % BASE, BINDING_HTTP_ARTIFACT)
|
||||||
|
],
|
||||||
|
"single_logout_service": [
|
||||||
|
("%s/slo/soap" % BASE, BINDING_SOAP),
|
||||||
|
("%s/slo/post" % BASE, BINDING_HTTP_POST),
|
||||||
|
("%s/slo/redirect" % BASE, BINDING_HTTP_REDIRECT)
|
||||||
|
],
|
||||||
|
"artifact_resolve_service":[
|
||||||
|
("%s/ars" % BASE, BINDING_SOAP)
|
||||||
|
],
|
||||||
|
"assertion_id_request_service": [
|
||||||
|
("%s/airs" % BASE, BINDING_URI)
|
||||||
|
],
|
||||||
|
"manage_name_id_service":[
|
||||||
|
("%s/mni/soap" % BASE, BINDING_SOAP),
|
||||||
|
("%s/mni/post" % BASE, BINDING_HTTP_POST),
|
||||||
|
("%s/mni/redirect" % BASE, BINDING_HTTP_REDIRECT),
|
||||||
|
("%s/mni/art" % BASE, BINDING_HTTP_ARTIFACT)
|
||||||
|
],
|
||||||
|
"name_id_mapping_service":[
|
||||||
|
("%s/nim/soap" % BASE, BINDING_SOAP),
|
||||||
|
("%s/nim/post" % BASE, BINDING_HTTP_POST),
|
||||||
|
("%s/nim/redirect" % BASE, BINDING_HTTP_REDIRECT),
|
||||||
|
("%s/nim/art" % BASE, BINDING_HTTP_ARTIFACT)
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"policy": {
|
||||||
|
"default": {
|
||||||
|
"lifetime": {"minutes":15},
|
||||||
|
"attribute_restrictions": None, # means all I have
|
||||||
|
"name_form": NAME_FORMAT_URI
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"subject_data": "./idp.subject.db",
|
||||||
|
"name_id_format": [NAMEID_FORMAT_TRANSIENT,
|
||||||
|
NAMEID_FORMAT_PERSISTENT]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"debug" : 1,
|
||||||
|
"key_file" : "pki/mykey.pem",
|
||||||
|
"cert_file" : "pki/mycert.pem",
|
||||||
|
"metadata" : {
|
||||||
|
"local": ["../sp.xml"],
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"display_name": "Rolands Identiteter",
|
||||||
|
"name": "Rolands Identiteter",
|
||||||
|
"url": "http://www.example.com",
|
||||||
|
},
|
||||||
|
"contact_person": [{
|
||||||
|
"contact_type": "technical",
|
||||||
|
"given_name": "Roland",
|
||||||
|
"sur_name": "Hedberg",
|
||||||
|
"email_address": "technical@example.com"
|
||||||
|
},{
|
||||||
|
"contact_type": "support",
|
||||||
|
"given_name": "Support",
|
||||||
|
"email_address": "support@example.com"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
# This database holds the map between a subjects local identifier and
|
||||||
|
# the identifier returned to a SP
|
||||||
|
"xmlsec_binary": xmlsec_path,
|
||||||
|
"attribute_map_dir" : "../attributemaps",
|
||||||
|
"logger": {
|
||||||
|
"rotating": {
|
||||||
|
"filename": "idp.log",
|
||||||
|
"maxBytes": 500000,
|
||||||
|
"backupCount": 5,
|
||||||
|
},
|
||||||
|
"loglevel": "debug",
|
||||||
|
}
|
||||||
|
}
|
18
tests/idp/idp_user.py
Normal file
18
tests/idp/idp_user.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
USERS = {
|
||||||
|
"roland": {
|
||||||
|
"surname": "Hedberg",
|
||||||
|
"givenName": "Roland",
|
||||||
|
"eduPersonAffiliation": "staff",
|
||||||
|
"uid": "rohe0002"
|
||||||
|
},
|
||||||
|
"babs": {
|
||||||
|
"surname": "Guillen",
|
||||||
|
"givenName": "Ozzie",
|
||||||
|
"eduPersonAffiliation": "affiliate"
|
||||||
|
},
|
||||||
|
"upper": {
|
||||||
|
"surname": "Jeter",
|
||||||
|
"givenName": "Derek",
|
||||||
|
"eduPersonAffiliation": "affiliate"
|
||||||
|
},
|
||||||
|
}
|
18
tests/idp/pki/mycert.pem
Normal file
18
tests/idp/pki/mycert.pem
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||||
|
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||||
|
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||||
|
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||||
|
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||||
|
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||||
|
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||||
|
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
||||||
|
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
||||||
|
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
||||||
|
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
||||||
|
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
||||||
|
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
||||||
|
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
||||||
|
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
||||||
|
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
||||||
|
-----END CERTIFICATE-----
|
15
tests/idp/pki/mykey.pem
Normal file
15
tests/idp/pki/mykey.pem
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXAIBAAKBgQDkJWP7bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr
|
||||||
|
6/ROgW96ZeQ57fzVy2MCFiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43
|
||||||
|
qCfLx+clUlOvtnsoMiiRmo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQAB
|
||||||
|
AoGAbx9rKH91DCw/ZEPhHsVXJ6cYHxGcMoAWvnMMC9WUN+bNo4gNL205DLfsxXA1
|
||||||
|
jqXFXZj3+38vSFumGPA6IvXrN+Wyp3+Lz3QGc4K5OdHeBtYlxa6EsrxPgvuxYDUB
|
||||||
|
vx3xdWPMjy06G/ML+pR9XHnRaPNubXQX3UxGBuLjwNXVmyECQQD2/D84tYoCGWoq
|
||||||
|
5FhUBxFUy2nnOLKYC/GGxBTX62iLfMQ3fbQcdg2pJsB5rrniyZf7UL+9FOsAO9k1
|
||||||
|
8DO7G12DAkEA7Hkdg1KEw4ZfjnnjEa+KqpyLTLRQ91uTVW6kzR+4zY719iUJ/PXE
|
||||||
|
PxJqm1ot7mJd1LW+bWtjLpxs7jYH19V+kQJBAIEpn2JnxdmdMuFlcy/WVmDy09pg
|
||||||
|
0z0imdexeXkFmjHAONkQOv3bWv+HzYaVMo8AgCOksfEPHGqN4eUMTfFeuUMCQF+5
|
||||||
|
E1JSd/2yCkJhYqKJHae8oMLXByNqRXTCyiFioutK4JPYIHfugJdLfC4QziD+Xp85
|
||||||
|
RrGCU+7NUWcIJhqfiJECQAIgUAzfzhdj5AyICaFPaOQ+N8FVMLcTyqeTXP0sIlFk
|
||||||
|
JStVibemTRCbxdXXM7OVipz1oW3PBVEO3t/VyjiaGGg=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
37
tests/idp/templates/root.mako
Normal file
37
tests/idp/templates/root.mako
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<% self.seen_css = set() %>
|
||||||
|
<%def name="css_link(path, media='')" filter="trim">
|
||||||
|
% if path not in self.seen_css:
|
||||||
|
<link rel="stylesheet" type="text/css" href="${path|h}" media="${media}">
|
||||||
|
% endif
|
||||||
|
<% self.seen_css.add(path) %>
|
||||||
|
</%def>
|
||||||
|
<%def name="css()" filter="trim">
|
||||||
|
${css_link('/css/main.css', 'screen')}
|
||||||
|
</%def>
|
||||||
|
<%def name="pre()" filter="trim">
|
||||||
|
<div class="header">
|
||||||
|
<h1><a href="/">Login</a></h1>
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
<%def name="post()" filter="trim">
|
||||||
|
<div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>© Copyright 2011 Umeå Universitet </p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
##<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN "
|
||||||
|
##"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html>
|
||||||
|
<head><title>IDP test login</title>
|
||||||
|
${self.css()}
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${pre()}
|
||||||
|
## ${comps.dict_to_table(pageargs)}
|
||||||
|
## <hr><hr>
|
||||||
|
${next.body()}
|
||||||
|
${post()}
|
||||||
|
</body>
|
||||||
|
</html>
|
18
tests/keys/mycert.pem
Normal file
18
tests/keys/mycert.pem
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||||
|
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||||
|
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||||
|
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||||
|
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||||
|
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||||
|
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||||
|
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
||||||
|
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
||||||
|
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
||||||
|
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
||||||
|
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
||||||
|
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
||||||
|
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
||||||
|
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
||||||
|
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
||||||
|
-----END CERTIFICATE-----
|
15
tests/keys/mykey.pem
Normal file
15
tests/keys/mykey.pem
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXAIBAAKBgQDkJWP7bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr
|
||||||
|
6/ROgW96ZeQ57fzVy2MCFiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43
|
||||||
|
qCfLx+clUlOvtnsoMiiRmo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQAB
|
||||||
|
AoGAbx9rKH91DCw/ZEPhHsVXJ6cYHxGcMoAWvnMMC9WUN+bNo4gNL205DLfsxXA1
|
||||||
|
jqXFXZj3+38vSFumGPA6IvXrN+Wyp3+Lz3QGc4K5OdHeBtYlxa6EsrxPgvuxYDUB
|
||||||
|
vx3xdWPMjy06G/ML+pR9XHnRaPNubXQX3UxGBuLjwNXVmyECQQD2/D84tYoCGWoq
|
||||||
|
5FhUBxFUy2nnOLKYC/GGxBTX62iLfMQ3fbQcdg2pJsB5rrniyZf7UL+9FOsAO9k1
|
||||||
|
8DO7G12DAkEA7Hkdg1KEw4ZfjnnjEa+KqpyLTLRQ91uTVW6kzR+4zY719iUJ/PXE
|
||||||
|
PxJqm1ot7mJd1LW+bWtjLpxs7jYH19V+kQJBAIEpn2JnxdmdMuFlcy/WVmDy09pg
|
||||||
|
0z0imdexeXkFmjHAONkQOv3bWv+HzYaVMo8AgCOksfEPHGqN4eUMTfFeuUMCQF+5
|
||||||
|
E1JSd/2yCkJhYqKJHae8oMLXByNqRXTCyiFioutK4JPYIHfugJdLfC4QziD+Xp85
|
||||||
|
RrGCU+7NUWcIJhqfiJECQAIgUAzfzhdj5AyICaFPaOQ+N8FVMLcTyqeTXP0sIlFk
|
||||||
|
JStVibemTRCbxdXXM7OVipz1oW3PBVEO3t/VyjiaGGg=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
59
tests/localhost.py
Executable file
59
tests/localhost.py
Executable file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
__author__ = 'rolandh'
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
BASE = "http://localhost:8088"
|
||||||
|
|
||||||
|
metadata = open("./idp/idp.xml").read()
|
||||||
|
|
||||||
|
info = {
|
||||||
|
"entity_id": "%s/idp.xml" % BASE,
|
||||||
|
"sp_config": "sp_local_conf.py",
|
||||||
|
"interaction": [
|
||||||
|
{
|
||||||
|
"matches": {
|
||||||
|
"url": "%s/login" % BASE,
|
||||||
|
"title": 'IDP test login'
|
||||||
|
},
|
||||||
|
"page-type": "login",
|
||||||
|
"control": {
|
||||||
|
"type": "form",
|
||||||
|
"set": {"login": "roland", "password": "dianakra"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matches": {
|
||||||
|
"url": "%s/sso/redirect" % BASE,
|
||||||
|
"title": "SAML 2.0 POST"
|
||||||
|
},
|
||||||
|
"control": {
|
||||||
|
"type": "response",
|
||||||
|
"pick": {"form": {"action":"%s/acs" % BASE}}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matches": {
|
||||||
|
"url": "%s/sso/post" % BASE,
|
||||||
|
"title": "SAML 2.0 POST"
|
||||||
|
},
|
||||||
|
"control": {
|
||||||
|
"type": "response",
|
||||||
|
"pick": {"form": {"action":"%s/acs" % BASE}}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matches": {
|
||||||
|
"url": "%s/slo/post" % BASE,
|
||||||
|
"title": "SAML 2.0 POST"
|
||||||
|
},
|
||||||
|
"control": {
|
||||||
|
"type": "response",
|
||||||
|
"pick": {"form": {"action":"%s/sls" % BASE}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
print json.dumps(info)
|
75
tests/sp.xml
Normal file
75
tests/sp.xml
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
xmlns:ns1="http://www.w3.org/2000/09/xmldsig#"
|
||||||
|
entityID="http://localhost:8087/sp.xml">
|
||||||
|
<ns0:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true"
|
||||||
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
<ns0:KeyDescriptor use="signing">
|
||||||
|
<ns1:KeyInfo>
|
||||||
|
<ns1:X509Data>
|
||||||
|
<ns1:X509Certificate>
|
||||||
|
MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||||
|
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||||
|
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||||
|
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||||
|
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||||
|
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||||
|
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||||
|
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
||||||
|
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
||||||
|
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
||||||
|
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
||||||
|
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
||||||
|
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
||||||
|
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
||||||
|
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
||||||
|
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
||||||
|
</ns1:X509Certificate>
|
||||||
|
</ns1:X509Data>
|
||||||
|
</ns1:KeyInfo>
|
||||||
|
</ns0:KeyDescriptor>
|
||||||
|
<ns0:ArtifactResolutionService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||||
|
Location="http://localhost:8087/ars" index="1"/>
|
||||||
|
<ns0:SingleLogoutService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||||
|
Location="http://localhost:8087/sls"/>
|
||||||
|
<ns0:ManageNameIDService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="http://localhost:8087/mni"/>
|
||||||
|
<ns0:ManageNameIDService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="http://localhost:8087/mni"/>
|
||||||
|
<ns0:ManageNameIDService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||||
|
Location="http://localhost:8087/mni"/>
|
||||||
|
<ns0:ManageNameIDService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
|
||||||
|
Location="http://localhost:8087/acs/artifact"/>
|
||||||
|
<ns0:AssertionConsumerService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="http://localhost:8087/acs/post" index="1"/>
|
||||||
|
<ns0:AssertionConsumerService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="http://localhost:8087/acs/redirect" index="2"/>
|
||||||
|
<ns0:AssertionConsumerService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
|
||||||
|
Location="http://localhost:8087/acs/artifact" index="3"/>
|
||||||
|
<ns0:AssertionConsumerService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
|
||||||
|
Location="http://localhost:8087/ecp" index="4"/>
|
||||||
|
</ns0:SPSSODescriptor>
|
||||||
|
<ns0:Organization>
|
||||||
|
<ns0:OrganizationName xml:lang="se">SAML2Test</ns0:OrganizationName>
|
||||||
|
<ns0:OrganizationDisplayName xml:lang="se">SAML2Test
|
||||||
|
</ns0:OrganizationDisplayName>
|
||||||
|
<ns0:OrganizationURL xml:lang="en">http://www.its.umu.se/
|
||||||
|
</ns0:OrganizationURL>
|
||||||
|
</ns0:Organization>
|
||||||
|
<ns0:ContactPerson contactType="technical">
|
||||||
|
<ns0:GivenName>Roland</ns0:GivenName>
|
||||||
|
<ns0:SurName>Hedberg</ns0:SurName>
|
||||||
|
<ns0:EmailAddress>roland.hedberg@adm.umu.se</ns0:EmailAddress>
|
||||||
|
<ns0:TelephoneNumber>+46 70 6966844</ns0:TelephoneNumber>
|
||||||
|
</ns0:ContactPerson>
|
||||||
|
</ns0:EntityDescriptor>
|
Reference in New Issue
Block a user