Starting point for an example IdP using repoze.who .
This commit is contained in:
29
example/idp2_repoze/htdocs/login.mako
Normal file
29
example/idp2_repoze/htdocs/login.mako
Normal file
@@ -0,0 +1,29 @@
|
||||
<%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="authn_reference" value="${authn_reference}"/>
|
||||
<input type="hidden" name="redirect_uri" value="${redirect_uri}"/>
|
||||
|
||||
<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>
|
||||
989
example/idp2_repoze/idp.py
Executable file
989
example/idp2_repoze/idp.py
Executable file
@@ -0,0 +1,989 @@
|
||||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import base64
|
||||
|
||||
import re
|
||||
import logging
|
||||
import time
|
||||
from hashlib import sha1
|
||||
|
||||
from urlparse import parse_qs
|
||||
from Cookie import SimpleCookie
|
||||
import os
|
||||
|
||||
from saml2 import server
|
||||
from saml2 import BINDING_HTTP_ARTIFACT
|
||||
from saml2 import BINDING_URI
|
||||
from saml2 import BINDING_PAOS
|
||||
from saml2 import BINDING_SOAP
|
||||
from saml2 import BINDING_HTTP_REDIRECT
|
||||
from saml2 import BINDING_HTTP_POST
|
||||
from saml2 import time_util
|
||||
|
||||
from saml2.authn_context import AuthnBroker
|
||||
from saml2.authn_context import PASSWORD
|
||||
from saml2.authn_context import UNSPECIFIED
|
||||
from saml2.authn_context import authn_context_class_ref
|
||||
from saml2.httputil import Response
|
||||
from saml2.httputil import NotFound
|
||||
from saml2.httputil import geturl
|
||||
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.metadata import create_metadata_string
|
||||
from saml2.s_utils import rndstr, exception_trace
|
||||
from saml2.s_utils import UnknownPrincipal
|
||||
from saml2.s_utils import UnsupportedBinding
|
||||
from saml2.s_utils import PolicyError
|
||||
from saml2.sigver import verify_redirect_signature
|
||||
|
||||
logger = logging.getLogger("saml2.idp")
|
||||
|
||||
|
||||
class Cache(object):
|
||||
def __init__(self):
|
||||
self.user2uid = {}
|
||||
self.uid2user = {}
|
||||
|
||||
|
||||
def _expiration(timeout, tformat="%a, %d-%b-%Y %H:%M:%S GMT"):
|
||||
"""
|
||||
|
||||
:param timeout:
|
||||
:param tformat:
|
||||
:return:
|
||||
"""
|
||||
if timeout == "now":
|
||||
return time_util.instant(tformat)
|
||||
elif timeout == "dawn":
|
||||
return time.strftime(tformat, time.gmtime(0))
|
||||
else:
|
||||
# validity time should match lifetime of assertions
|
||||
return time_util.in_a_while(minutes=timeout, format=tformat)
|
||||
|
||||
|
||||
def get_eptid(idp, req_info, session):
|
||||
return idp.eptid.get(idp.config.entityid,
|
||||
req_info.sender(), session["permanent_id"],
|
||||
session["authn_auth"])
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def dict2list_of_tuples(d):
|
||||
return [(k, v) for k, v in d.items()]
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Service(object):
|
||||
def __init__(self, environ, start_response, user=None):
|
||||
self.environ = environ
|
||||
logger.debug("ENVIRON: %s" % environ)
|
||||
self.start_response = start_response
|
||||
self.user = user
|
||||
|
||||
def unpack_redirect(self):
|
||||
if "QUERY_STRING" in self.environ:
|
||||
_qs = self.environ["QUERY_STRING"]
|
||||
return dict([(k, v[0]) for k, v in parse_qs(_qs).items()])
|
||||
else:
|
||||
return None
|
||||
|
||||
def unpack_post(self):
|
||||
_dict = parse_qs(get_post(self.environ))
|
||||
logger.debug("unpack_post:: %s" % _dict)
|
||||
try:
|
||||
return dict([(k, v[0]) for k, v in _dict.items()])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def unpack_soap(self):
|
||||
try:
|
||||
query = get_post(self.environ)
|
||||
return {"SAMLRequest": query, "RelayState": ""}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def unpack_either(self):
|
||||
if self.environ["REQUEST_METHOD"] == "GET":
|
||||
_dict = self.unpack_redirect()
|
||||
elif self.environ["REQUEST_METHOD"] == "POST":
|
||||
_dict = self.unpack_post()
|
||||
else:
|
||||
_dict = None
|
||||
logger.debug("_dict: %s" % _dict)
|
||||
return _dict
|
||||
|
||||
def operation(self, _dict, binding):
|
||||
logger.debug("_operation: %s" % _dict)
|
||||
if not _dict or not 'SAMLRequest' in _dict:
|
||||
resp = BadRequest('Error parsing request or no request')
|
||||
return resp(self.environ, self.start_response)
|
||||
else:
|
||||
try:
|
||||
return self.do(_dict["SAMLRequest"], binding,
|
||||
_dict["RelayState"])
|
||||
except KeyError:
|
||||
# Can live with no relay state
|
||||
return self.do(_dict["SAMLRequest"], binding)
|
||||
|
||||
def artifact_operation(self, _dict):
|
||||
if not _dict:
|
||||
resp = BadRequest("Missing query")
|
||||
return resp(self.environ, self.start_response)
|
||||
else:
|
||||
# exchange artifact for request
|
||||
request = IDP.artifact2message(_dict["SAMLart"], "spsso")
|
||||
try:
|
||||
return self.do(request, BINDING_HTTP_ARTIFACT,
|
||||
_dict["RelayState"])
|
||||
except KeyError:
|
||||
return self.do(request, BINDING_HTTP_ARTIFACT)
|
||||
|
||||
def response(self, binding, http_args):
|
||||
if binding == BINDING_HTTP_ARTIFACT:
|
||||
resp = Redirect()
|
||||
else:
|
||||
resp = Response(http_args["data"], headers=http_args["headers"])
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
def do(self, query, binding, relay_state=""):
|
||||
pass
|
||||
|
||||
def redirect(self):
|
||||
""" Expects a HTTP-redirect request """
|
||||
|
||||
_dict = self.unpack_redirect()
|
||||
return self.operation(_dict, BINDING_HTTP_REDIRECT)
|
||||
|
||||
def post(self):
|
||||
""" Expects a HTTP-POST request """
|
||||
|
||||
_dict = self.unpack_post()
|
||||
return self.operation(_dict, BINDING_HTTP_POST)
|
||||
|
||||
def artifact(self):
|
||||
# Can be either by HTTP_Redirect or HTTP_POST
|
||||
_dict = self.unpack_either()
|
||||
return self.artifact_operation(_dict)
|
||||
|
||||
def soap(self):
|
||||
"""
|
||||
Single log out using HTTP_SOAP binding
|
||||
"""
|
||||
logger.debug("- SOAP -")
|
||||
_dict = self.unpack_soap()
|
||||
logger.debug("_dict: %s" % _dict)
|
||||
return self.operation(_dict, BINDING_SOAP)
|
||||
|
||||
def uri(self):
|
||||
_dict = self.unpack_either()
|
||||
return self.operation(_dict, BINDING_SOAP)
|
||||
|
||||
# def not_authn(self, key):
|
||||
# """
|
||||
#
|
||||
#
|
||||
# :return:
|
||||
# """
|
||||
# loc = "http://%s/login" % (self.environ["HTTP_HOST"])
|
||||
# loc += "?%s" % urllib.urlencode({"came_from": self.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(self.environ, self.start_response)
|
||||
|
||||
def not_authn(self, key, requested_authn_context):
|
||||
ruri = geturl(self.environ, query=False)
|
||||
return do_authentication(self.environ, self.start_response,
|
||||
authn_context=requested_authn_context,
|
||||
key=key, redirect_uri=ruri)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
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 ====
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AuthenticationNeeded(Exception):
|
||||
def __init__(self, authn_context=None, *args, **kwargs):
|
||||
Exception.__init__(*args, **kwargs)
|
||||
self.authn_context = authn_context
|
||||
|
||||
|
||||
class SSO(Service):
|
||||
def __init__(self, environ, start_response, user=None):
|
||||
Service.__init__(self, environ, start_response, user)
|
||||
self.binding = ""
|
||||
self.response_bindings = None
|
||||
self.resp_args = {}
|
||||
self.binding_out = None
|
||||
self.destination = None
|
||||
self.req_info = None
|
||||
|
||||
def verify_request(self, query, binding):
|
||||
"""
|
||||
:param query: The SAML query, transport encoded
|
||||
:param binding: Which binding the query came in over
|
||||
"""
|
||||
resp_args = {}
|
||||
if not query:
|
||||
logger.info("Missing QUERY")
|
||||
resp = Unauthorized('Unknown user')
|
||||
return resp_args, resp(self.environ, self.start_response)
|
||||
|
||||
if not self.req_info:
|
||||
self.req_info = IDP.parse_authn_request(query, binding)
|
||||
|
||||
logger.info("parsed OK")
|
||||
_authn_req = self.req_info.message
|
||||
logger.debug("%s" % _authn_req)
|
||||
|
||||
self.binding_out, self.destination = IDP.pick_binding(
|
||||
"assertion_consumer_service",
|
||||
bindings=self.response_bindings,
|
||||
entity_id=_authn_req.issuer.text)
|
||||
|
||||
logger.debug("Binding: %s, destination: %s" % (self.binding_out,
|
||||
self.destination))
|
||||
|
||||
resp_args = {}
|
||||
try:
|
||||
resp_args = IDP.response_args(_authn_req)
|
||||
_resp = None
|
||||
except UnknownPrincipal, excp:
|
||||
_resp = IDP.create_error_response(_authn_req.id,
|
||||
self.destination, excp)
|
||||
except UnsupportedBinding, excp:
|
||||
_resp = IDP.create_error_response(_authn_req.id,
|
||||
self.destination, excp)
|
||||
|
||||
return resp_args, _resp
|
||||
|
||||
def do(self, query, binding_in, relay_state=""):
|
||||
try:
|
||||
resp_args, _resp = self.verify_request(query, binding_in)
|
||||
except UnknownPrincipal, excp:
|
||||
logger.error("UnknownPrincipal: %s" % (excp,))
|
||||
resp = ServiceError("UnknownPrincipal: %s" % (excp,))
|
||||
return resp(self.environ, self.start_response)
|
||||
except UnsupportedBinding, excp:
|
||||
logger.error("UnsupportedBinding: %s" % (excp,))
|
||||
resp = ServiceError("UnsupportedBinding: %s" % (excp,))
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
if not _resp:
|
||||
identity = USERS[self.user].copy()
|
||||
#identity["eduPersonTargetedID"] = get_eptid(IDP, query, session)
|
||||
logger.info("Identity: %s" % (identity,))
|
||||
|
||||
if REPOZE_ID_EQUIVALENT:
|
||||
identity[REPOZE_ID_EQUIVALENT] = self.user
|
||||
try:
|
||||
sign_assertion = IDP.config.getattr("sign_assertion", "idp")
|
||||
if sign_assertion is None:
|
||||
sign_assertion = False
|
||||
_resp = IDP.create_authn_response(
|
||||
identity, userid=self.user,
|
||||
authn=AUTHN_BROKER[self.environ["idp.authn_ref"]], sign_assertion=sign_assertion,
|
||||
sign_response=False, **resp_args)
|
||||
except Exception, excp:
|
||||
logging.error(exception_trace(excp))
|
||||
resp = ServiceError("Exception: %s" % (excp,))
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
logger.info("AuthNResponse: %s" % _resp)
|
||||
http_args = IDP.apply_binding(self.binding_out,
|
||||
"%s" % _resp, self.destination,
|
||||
relay_state, response=True)
|
||||
logger.debug("HTTPargs: %s" % http_args)
|
||||
return self.response(self.binding_out, http_args)
|
||||
|
||||
def _store_request(self, _dict):
|
||||
logger.debug("_store_request: %s" % _dict)
|
||||
key = sha1(_dict["SAMLRequest"]).hexdigest()
|
||||
# store the AuthnRequest
|
||||
IDP.ticket[key] = _dict
|
||||
return key
|
||||
|
||||
def redirect(self):
|
||||
""" This is the HTTP-redirect endpoint """
|
||||
|
||||
logger.info("--- In SSO Redirect ---")
|
||||
_info = self.unpack_redirect()
|
||||
|
||||
try:
|
||||
_key = _info["key"]
|
||||
_info = IDP.ticket[_key]
|
||||
self.req_info = _info["req_info"]
|
||||
del IDP.ticket[_key]
|
||||
except KeyError:
|
||||
try:
|
||||
self.req_info = IDP.parse_authn_request(_info["SAMLRequest"],
|
||||
BINDING_HTTP_REDIRECT)
|
||||
except KeyError:
|
||||
resp = BadRequest("Message signature verification failure")
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
_req = self.req_info.message
|
||||
|
||||
if "SigAlg" in _info and "Signature" in _info: # Signed request
|
||||
issuer = _req.issuer.text
|
||||
_certs = IDP.metadata.certs(issuer, "any", "signing")
|
||||
verified_ok = False
|
||||
for cert in _certs:
|
||||
if verify_redirect_signature(_info, cert):
|
||||
verified_ok = True
|
||||
break
|
||||
if not verified_ok:
|
||||
resp = BadRequest("Message signature verification failure")
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
if self.user:
|
||||
if _req.force_authn:
|
||||
_info["req_info"] = self.req_info
|
||||
key = self._store_request(_info)
|
||||
return self.not_authn(key, _req.requested_authn_context)
|
||||
else:
|
||||
return self.operation(_info, BINDING_HTTP_REDIRECT)
|
||||
else:
|
||||
_info["req_info"] = self.req_info
|
||||
key = self._store_request(_info)
|
||||
return self.not_authn(key, _req.requested_authn_context)
|
||||
else:
|
||||
return self.operation(_info, BINDING_HTTP_REDIRECT)
|
||||
|
||||
def post(self):
|
||||
"""
|
||||
The HTTP-Post endpoint
|
||||
"""
|
||||
logger.info("--- In SSO POST ---")
|
||||
_info = self.unpack_either()
|
||||
self.req_info = IDP.parse_authn_request(
|
||||
_info["SAMLRequest"], BINDING_HTTP_POST)
|
||||
_req = self.req_info.message
|
||||
if self.user:
|
||||
if _req.force_authn:
|
||||
_info["req_info"] = self.req_info
|
||||
key = self._store_request(_info)
|
||||
return self.not_authn(key, _req.requested_authn_context)
|
||||
else:
|
||||
return self.operation(_info, BINDING_HTTP_POST)
|
||||
else:
|
||||
_info["req_info"] = self.req_info
|
||||
key = self._store_request(_info)
|
||||
return self.not_authn(key, _req.requested_authn_context)
|
||||
|
||||
# def artifact(self):
|
||||
# # Can be either by HTTP_Redirect or HTTP_POST
|
||||
# _req = self._store_request(self.unpack_either())
|
||||
# if isinstance(_req, basestring):
|
||||
# return self.not_authn(_req)
|
||||
# return self.artifact_operation(_req)
|
||||
|
||||
def ecp(self):
|
||||
# The ECP interface
|
||||
logger.info("--- ECP SSO ---")
|
||||
resp = None
|
||||
|
||||
try:
|
||||
authz_info = self.environ["HTTP_AUTHORIZATION"]
|
||||
if authz_info.startswith("Basic "):
|
||||
_info = base64.b64decode(authz_info[6:])
|
||||
logger.debug("Authz_info: %s" % _info)
|
||||
try:
|
||||
(user, passwd) = _info.split(":")
|
||||
if PASSWD[user] != passwd:
|
||||
resp = Unauthorized()
|
||||
self.user = user
|
||||
except ValueError:
|
||||
resp = Unauthorized()
|
||||
else:
|
||||
resp = Unauthorized()
|
||||
except KeyError:
|
||||
resp = Unauthorized()
|
||||
|
||||
if resp:
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
_dict = self.unpack_soap()
|
||||
self.response_bindings = [BINDING_PAOS]
|
||||
# Basic auth ?!
|
||||
return self.operation(_dict, BINDING_SOAP)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# === Authentication ====
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def do_authentication(environ, start_response, authn_context, key,
|
||||
redirect_uri):
|
||||
"""
|
||||
Display the login form
|
||||
"""
|
||||
logger.debug("Do authentication")
|
||||
auth_info = AUTHN_BROKER.pick(authn_context)
|
||||
|
||||
if len(auth_info):
|
||||
method, reference = auth_info[0]
|
||||
logger.debug("Authn chosen: %s (ref=%s)" % (method, reference))
|
||||
return method(environ, start_response, reference, key, redirect_uri)
|
||||
else:
|
||||
resp = Unauthorized("No usable authentication method")
|
||||
return resp(environ, start_response)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
PASSWD = {"haho0032": "qwerty",
|
||||
"roland": "dianakra",
|
||||
"babs": "howes",
|
||||
"upper": "crust"}
|
||||
|
||||
|
||||
def username_password_authn(environ, start_response, reference, key,
|
||||
redirect_uri):
|
||||
"""
|
||||
Display the login form
|
||||
"""
|
||||
logger.info("The login page")
|
||||
headers = []
|
||||
|
||||
resp = Response(mako_template="login.mako", template_lookup=LOOKUP,
|
||||
headers=headers)
|
||||
|
||||
argv = {
|
||||
"action": "/verify",
|
||||
"login": "",
|
||||
"password": "",
|
||||
"key": key,
|
||||
"authn_reference": reference,
|
||||
"redirect_uri": redirect_uri
|
||||
}
|
||||
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
|
||||
if PASSWD[dic["login"][0]] == dic["password"][0]:
|
||||
return True, dic["login"][0]
|
||||
else:
|
||||
return False, ""
|
||||
|
||||
|
||||
def do_verify(environ, start_response, _):
|
||||
query = parse_qs(get_post(environ))
|
||||
|
||||
logger.debug("do_verify: %s" % query)
|
||||
|
||||
try:
|
||||
_ok, user = verify_username_and_password(query)
|
||||
except KeyError:
|
||||
_ok = False
|
||||
user = None
|
||||
|
||||
if not _ok:
|
||||
resp = Unauthorized("Unknown user or wrong password")
|
||||
else:
|
||||
uid = rndstr(24)
|
||||
IDP.cache.uid2user[uid] = user
|
||||
IDP.cache.user2uid[user] = uid
|
||||
logger.debug("Register %s under '%s'" % (user, uid))
|
||||
|
||||
kaka = set_cookie("idpauthn", "/", uid, query["authn_reference"][0])
|
||||
|
||||
lox = "%s?id=%s&key=%s" % (query["redirect_uri"][0], uid,
|
||||
query["key"][0])
|
||||
logger.debug("Redirect => %s" % lox)
|
||||
resp = Redirect(lox, headers=[kaka], content="text/html")
|
||||
|
||||
return resp(environ, start_response)
|
||||
|
||||
|
||||
def not_found(environ, start_response):
|
||||
"""Called if no URL matches."""
|
||||
resp = NotFound()
|
||||
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
|
||||
|
||||
class SLO(Service):
|
||||
def do(self, request, binding, relay_state=""):
|
||||
logger.info("--- Single Log Out Service ---")
|
||||
try:
|
||||
_, body = request.split("\n")
|
||||
logger.debug("req: '%s'" % body)
|
||||
req_info = IDP.parse_logout_request(body, binding)
|
||||
except Exception, exc:
|
||||
logger.error("Bad request: %s" % exc)
|
||||
resp = BadRequest("%s" % exc)
|
||||
return resp(self.environ, self.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)
|
||||
if lid in IDP.cache.user2uid:
|
||||
uid = IDP.cache.user2uid[lid]
|
||||
if uid in IDP.cache.uid2user:
|
||||
del IDP.cache.uid2user[uid]
|
||||
del IDP.cache.user2uid[lid]
|
||||
# remove the authentication
|
||||
try:
|
||||
IDP.session_db.remove_authn_statements(msg.name_id)
|
||||
except KeyError, exc:
|
||||
logger.error("ServiceError: %s" % exc)
|
||||
resp = ServiceError("%s" % exc)
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
resp = IDP.create_logout_response(msg, [binding])
|
||||
|
||||
try:
|
||||
hinfo = IDP.apply_binding(binding, "%s" % resp, "", relay_state)
|
||||
except Exception, exc:
|
||||
logger.error("ServiceError: %s" % exc)
|
||||
resp = ServiceError("%s" % exc)
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
#_tlh = dict2list_of_tuples(hinfo["headers"])
|
||||
delco = delete_cookie(self.environ, "idpauthn")
|
||||
if delco:
|
||||
hinfo["headers"].append(delco)
|
||||
logger.info("Header: %s" % (hinfo["headers"],))
|
||||
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Manage Name ID service
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class NMI(Service):
|
||||
|
||||
def do(self, query, binding, relay_state=""):
|
||||
logger.info("--- Manage Name ID Service ---")
|
||||
req = IDP.parse_manage_name_id_request(query, binding)
|
||||
request = req.message
|
||||
|
||||
# Do the necessary stuff
|
||||
name_id = IDP.ident.handle_manage_name_id_request(
|
||||
request.name_id, request.new_id, request.new_encrypted_id,
|
||||
request.terminate)
|
||||
|
||||
logger.debug("New NameID: %s" % name_id)
|
||||
|
||||
_resp = IDP.create_manage_name_id_response(request)
|
||||
|
||||
# It's using SOAP binding
|
||||
hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % _resp, "",
|
||||
relay_state, response=True)
|
||||
|
||||
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# === Assertion ID request ===
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Only URI binding
|
||||
class AIDR(Service):
|
||||
def do(self, aid, binding, relay_state=""):
|
||||
logger.info("--- Assertion ID Service ---")
|
||||
|
||||
try:
|
||||
assertion = IDP.create_assertion_id_request_response(aid)
|
||||
except Unknown:
|
||||
resp = NotFound(aid)
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
hinfo = IDP.apply_binding(BINDING_URI, "%s" % assertion, response=True)
|
||||
|
||||
logger.debug("HINFO: %s" % hinfo)
|
||||
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
def operation(self, _dict, binding, **kwargs):
|
||||
logger.debug("_operation: %s" % _dict)
|
||||
if not _dict or "ID" not in _dict:
|
||||
resp = BadRequest('Error parsing request or no request')
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
return self.do(_dict["ID"], binding, **kwargs)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# === Artifact resolve service ===
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
class ARS(Service):
|
||||
def do(self, request, binding, relay_state=""):
|
||||
_req = IDP.parse_artifact_resolve(request, binding)
|
||||
|
||||
msg = IDP.create_artifact_response(_req, _req.artifact.text)
|
||||
|
||||
hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % msg, "", "",
|
||||
response=True)
|
||||
|
||||
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# === Authn query service ===
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Only SOAP binding
|
||||
class AQS(Service):
|
||||
def do(self, request, binding, relay_state=""):
|
||||
logger.info("--- Authn Query Service ---")
|
||||
_req = IDP.parse_authn_query(request, 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_SOAP, "%s" % msg, "", "",
|
||||
response=True)
|
||||
|
||||
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# === Attribute query service ===
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Only SOAP binding
|
||||
class ATTR(Service):
|
||||
def do(self, request, binding, relay_state=""):
|
||||
logger.info("--- Attribute Query Service ---")
|
||||
|
||||
_req = IDP.parse_attribute_query(request, binding)
|
||||
_query = _req.message
|
||||
|
||||
name_id = _query.subject.name_id
|
||||
uid = name_id.text
|
||||
logger.debug("Local uid: %s" % uid)
|
||||
identity = EXTRA[uid]
|
||||
|
||||
# Comes in over SOAP so only need to construct the response
|
||||
args = IDP.response_args(_query, [BINDING_SOAP])
|
||||
msg = IDP.create_attribute_response(identity,
|
||||
name_id=name_id, **args)
|
||||
|
||||
logger.debug("response: %s" % msg)
|
||||
hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % msg, "", "",
|
||||
response=True)
|
||||
|
||||
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||
return resp(self.environ, self.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.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class NIM(Service):
|
||||
def do(self, query, binding, relay_state=""):
|
||||
req = IDP.parse_name_id_mapping_request(query, binding)
|
||||
request = req.message
|
||||
# Do the necessary stuff
|
||||
try:
|
||||
name_id = IDP.ident.handle_name_id_mapping_request(
|
||||
request.name_id, request.name_id_policy)
|
||||
except Unknown:
|
||||
resp = BadRequest("Unknown entity")
|
||||
return resp(self.environ, self.start_response)
|
||||
except PolicyError:
|
||||
resp = BadRequest("Unknown entity")
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
info = IDP.response_args(request)
|
||||
_resp = IDP.create_name_id_mapping_response(name_id, **info)
|
||||
|
||||
# Only SOAP
|
||||
hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % _resp, "", "",
|
||||
response=True)
|
||||
|
||||
resp = Response(hinfo["data"], headers=hinfo["headers"])
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Cookie handling
|
||||
# ----------------------------------------------------------------------------
|
||||
def info_from_cookie(kaka):
|
||||
logger.debug("KAKA: %s" % kaka)
|
||||
if kaka:
|
||||
cookie_obj = SimpleCookie(kaka)
|
||||
morsel = cookie_obj.get("idpauthn", None)
|
||||
if morsel:
|
||||
try:
|
||||
key, ref = base64.b64decode(morsel.value).split(":")
|
||||
return IDP.cache.uid2user[key], ref
|
||||
except KeyError:
|
||||
return None, None
|
||||
else:
|
||||
logger.debug("No idpauthn cookie")
|
||||
return None, None
|
||||
|
||||
|
||||
def delete_cookie(environ, name):
|
||||
kaka = environ.get("HTTP_COOKIE", '')
|
||||
logger.debug("delete KAKA: %s" % kaka)
|
||||
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, _, *args):
|
||||
cookie = SimpleCookie()
|
||||
cookie[name] = base64.b64encode(":".join(args))
|
||||
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 = [
|
||||
# sso
|
||||
(r'sso/post$', (SSO, "post")),
|
||||
(r'sso/post/(.*)$', (SSO, "post")),
|
||||
(r'sso/redirect$', (SSO, "redirect")),
|
||||
(r'sso/redirect/(.*)$', (SSO, "redirect")),
|
||||
(r'sso/art$', (SSO, "artifact")),
|
||||
(r'sso/art/(.*)$', (SSO, "artifact")),
|
||||
# slo
|
||||
(r'slo/redirect$', (SLO, "redirect")),
|
||||
(r'slo/redirect/(.*)$', (SLO, "redirect")),
|
||||
(r'slo/post$', (SLO, "post")),
|
||||
(r'slo/post/(.*)$', (SLO, "post")),
|
||||
(r'slo/soap$', (SLO, "soap")),
|
||||
(r'slo/soap/(.*)$', (SLO, "soap")),
|
||||
#
|
||||
(r'airs$', (AIDR, "uri")),
|
||||
(r'ars$', (ARS, "soap")),
|
||||
# mni
|
||||
(r'mni/post$', (NMI, "post")),
|
||||
(r'mni/post/(.*)$', (NMI, "post")),
|
||||
(r'mni/redirect$', (NMI, "redirect")),
|
||||
(r'mni/redirect/(.*)$', (NMI, "redirect")),
|
||||
(r'mni/art$', (NMI, "artifact")),
|
||||
(r'mni/art/(.*)$', (NMI, "artifact")),
|
||||
(r'mni/soap$', (NMI, "soap")),
|
||||
(r'mni/soap/(.*)$', (NMI, "soap")),
|
||||
# nim
|
||||
(r'nim$', (NIM, "soap")),
|
||||
(r'nim/(.*)$', (NIM, "soap")),
|
||||
#
|
||||
(r'aqs$', (AQS, "soap")),
|
||||
(r'attr$', (ATTR, "soap"))
|
||||
]
|
||||
|
||||
NON_AUTHN_URLS = [
|
||||
#(r'login?(.*)$', do_authentication),
|
||||
(r'verify?(.*)$', do_verify),
|
||||
(r'sso/ecp$', (SSO, "ecp")),
|
||||
]
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def metadata(environ, start_response):
|
||||
try:
|
||||
path = args.path
|
||||
if path is None or len(path) == 0:
|
||||
path = os.path.dirname(os.path.abspath( __file__ ))
|
||||
if path[-1] != "/":
|
||||
path += "/"
|
||||
metadata = create_metadata_string(path+args.config, IDP.config,
|
||||
args.valid, args.cert, args.keyfile,
|
||||
args.id, args.name, args.sign)
|
||||
start_response('200 OK', [('Content-Type', "text/xml")])
|
||||
return metadata
|
||||
except Exception as ex:
|
||||
logger.error("An error occured while creating metadata:" + ex.message)
|
||||
return not_found(environ, start_response)
|
||||
|
||||
def staticfile(environ, start_response):
|
||||
try:
|
||||
path = args.path
|
||||
if path is None or len(path) == 0:
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
if path[-1] != "/":
|
||||
path += "/"
|
||||
path += environ.get('PATH_INFO', '').lstrip('/')
|
||||
start_response('200 OK', [('Content-Type', "text/xml")])
|
||||
return open(path, 'r').read()
|
||||
except Exception as ex:
|
||||
logger.error("An error occured while creating metadata:" + ex.message)
|
||||
return not_found(environ, start_response)
|
||||
|
||||
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('/')
|
||||
|
||||
if path == "metadata":
|
||||
return metadata(environ, start_response)
|
||||
|
||||
kaka = environ.get("HTTP_COOKIE", None)
|
||||
logger.info("<application> PATH: %s" % path)
|
||||
|
||||
if kaka:
|
||||
logger.info("= KAKA =")
|
||||
user, authn_ref = info_from_cookie(kaka)
|
||||
environ["idp.authn_ref"] = authn_ref
|
||||
else:
|
||||
try:
|
||||
query = parse_qs(environ["QUERY_STRING"])
|
||||
logger.debug("QUERY: %s" % query)
|
||||
user = IDP.cache.uid2user[query["id"][0]]
|
||||
except KeyError:
|
||||
user = None
|
||||
|
||||
url_patterns = AUTHN_URLS
|
||||
if not user:
|
||||
logger.info("-- No USER --")
|
||||
# insert NON_AUTHN_URLS first in case there is no user
|
||||
url_patterns = NON_AUTHN_URLS + url_patterns
|
||||
|
||||
for regex, callback in url_patterns:
|
||||
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.debug("Callback: %s" % (callback,))
|
||||
if isinstance(callback, tuple):
|
||||
cls = callback[0](environ, start_response, user)
|
||||
func = getattr(cls, callback[1])
|
||||
return func()
|
||||
return callback(environ, start_response, user)
|
||||
|
||||
if re.search(r'static/.*', path) is not None:
|
||||
return staticfile(environ, start_response)
|
||||
return not_found(environ, start_response)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# allow uwsgi or gunicorn mount
|
||||
# by moving some initialization out of __name__ == '__main__' section.
|
||||
# uwsgi -s 0.0.0.0:8088 --protocol http --callable application --module idp
|
||||
|
||||
args = type('Config', (object,), { })
|
||||
args.config = 'idp_conf'
|
||||
args.mako_root = './'
|
||||
args.path = None
|
||||
|
||||
import socket
|
||||
from idp_user import USERS
|
||||
from idp_user import EXTRA
|
||||
from mako.lookup import TemplateLookup
|
||||
|
||||
AUTHN_BROKER = AuthnBroker()
|
||||
AUTHN_BROKER.add(authn_context_class_ref(PASSWORD),
|
||||
username_password_authn, 10,
|
||||
"http://%s" % socket.gethostname())
|
||||
AUTHN_BROKER.add(authn_context_class_ref(UNSPECIFIED),
|
||||
"", 0, "http://%s" % socket.gethostname())
|
||||
|
||||
IDP = server.Server(args.config, cache=Cache())
|
||||
IDP.ticket = {}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
from wsgiref.simple_server import make_server
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-p', dest='path', help='Path to configuration file.')
|
||||
parser.add_argument('-v', dest='valid',
|
||||
help="How long, in days, the metadata is valid from the time of creation")
|
||||
parser.add_argument('-c', dest='cert', help='certificate')
|
||||
parser.add_argument('-i', dest='id',
|
||||
help="The ID of the entities descriptor")
|
||||
parser.add_argument('-k', dest='keyfile',
|
||||
help="A file with a key to sign the metadata with")
|
||||
parser.add_argument('-n', dest='name')
|
||||
parser.add_argument('-s', dest='sign', action='store_true',
|
||||
help="sign the metadata")
|
||||
parser.add_argument('-m', dest='mako_root', default="./")
|
||||
parser.add_argument(dest="config")
|
||||
args = parser.parse_args()
|
||||
|
||||
_rot = args.mako_root
|
||||
LOOKUP = TemplateLookup(directories=[_rot + 'templates', _rot + 'htdocs'],
|
||||
module_directory=_rot + 'modules',
|
||||
input_encoding='utf-8', output_encoding='utf-8')
|
||||
|
||||
PORT = 8088
|
||||
|
||||
SRV = make_server('', PORT, application)
|
||||
print "IdP listening on port: %s" % PORT
|
||||
SRV.serve_forever()
|
||||
else:
|
||||
_rot = args.mako_root
|
||||
LOOKUP = TemplateLookup(directories=[_rot + 'templates', _rot + 'htdocs'],
|
||||
module_directory=_rot + 'modules',
|
||||
input_encoding='utf-8', output_encoding='utf-8')
|
||||
BIN
example/idp2_repoze/idp.subject
Normal file
BIN
example/idp2_repoze/idp.subject
Normal file
Binary file not shown.
144
example/idp2_repoze/idp_conf.py.example
Normal file
144
example/idp2_repoze/idp_conf.py.example
Normal file
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
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
|
||||
import os.path
|
||||
|
||||
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'
|
||||
|
||||
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def full_path(local_file):
|
||||
return os.path.join(BASEDIR, local_file)
|
||||
|
||||
#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",
|
||||
"valid_for": 168,
|
||||
"service": {
|
||||
"aa": {
|
||||
"endpoints": {
|
||||
"attribute_service": [
|
||||
("%s/attr" % 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),
|
||||
("%s/sso/ecp" % BASE, BINDING_SOAP)
|
||||
],
|
||||
"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" % BASE, BINDING_SOAP),
|
||||
],
|
||||
},
|
||||
"policy": {
|
||||
"default": {
|
||||
"lifetime": {"minutes": 15},
|
||||
"attribute_restrictions": None, # means all I have
|
||||
"name_form": NAME_FORMAT_URI,
|
||||
"entity_categories": ["swamid", "edugain"]
|
||||
},
|
||||
},
|
||||
"subject_data": "./idp.subject",
|
||||
"name_id_format": [NAMEID_FORMAT_TRANSIENT,
|
||||
NAMEID_FORMAT_PERSISTENT]
|
||||
},
|
||||
},
|
||||
"debug": 1,
|
||||
"key_file": full_path("pki/mykey.pem"),
|
||||
"cert_file": full_path("pki/mycert.pem"),
|
||||
"metadata": {
|
||||
"local": [full_path("../sp-wsgi/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",
|
||||
}
|
||||
}
|
||||
|
||||
# Authentication contexts
|
||||
|
||||
#(r'verify?(.*)$', do_verify),
|
||||
|
||||
CAS_SERVER = "https://cas.umu.se"
|
||||
CAS_VERIFY = "%s/verify_cas" % BASE
|
||||
PWD_VERIFY = "%s/verify_pwd" % BASE
|
||||
|
||||
AUTHORIZATION = {
|
||||
"CAS" : {"ACR": "CAS", "WEIGHT": 1, "URL": CAS_VERIFY},
|
||||
"UserPassword" : {"ACR": "PASSWORD", "WEIGHT": 2, "URL": PWD_VERIFY}
|
||||
}
|
||||
54
example/idp2_repoze/idp_user.py
Normal file
54
example/idp2_repoze/idp_user.py
Normal file
@@ -0,0 +1,54 @@
|
||||
USERS = {
|
||||
"haho0032": {
|
||||
"sn": "Hoerberg",
|
||||
"givenName": "Hans",
|
||||
"eduPersonScopedAffiliation": "staff@example.com",
|
||||
"eduPersonPrincipalName": "haho@example.com",
|
||||
"uid": "haho",
|
||||
"eduPersonTargetedID": "one!for!all",
|
||||
"c": "SE",
|
||||
"o": "Example Co.",
|
||||
"ou": "IT",
|
||||
"initials": "P",
|
||||
"schacHomeOrganization": "example.com",
|
||||
"email": "hans@example.com",
|
||||
"displayName": "Hans Hoerberg",
|
||||
"labeledURL": "http://www.example.com/haho My homepage",
|
||||
"norEduPersonNIN": "SE199012315555"
|
||||
},
|
||||
"roland": {
|
||||
"sn": "Hedberg",
|
||||
"givenName": "Roland",
|
||||
"eduPersonScopedAffiliation": "staff@example.com",
|
||||
"eduPersonPrincipalName": "rohe@example.com",
|
||||
"uid": "rohe",
|
||||
"eduPersonTargetedID": "one!for!all",
|
||||
"c": "SE",
|
||||
"o": "Example Co.",
|
||||
"ou": "IT",
|
||||
"initials": "P",
|
||||
#"schacHomeOrganization": "example.com",
|
||||
"email": "roland@example.com",
|
||||
"displayName": "P. Roland Hedberg",
|
||||
"labeledURL": "http://www.example.com/rohe My homepage",
|
||||
"norEduPersonNIN": "SE197001012222"
|
||||
},
|
||||
"babs": {
|
||||
"surname": "Babs",
|
||||
"givenName": "Ozzie",
|
||||
"eduPersonAffiliation": "affiliate"
|
||||
},
|
||||
"upper": {
|
||||
"surname": "Jeter",
|
||||
"givenName": "Derek",
|
||||
"eduPersonAffiliation": "affiliate"
|
||||
},
|
||||
}
|
||||
|
||||
EXTRA = {
|
||||
"roland": {
|
||||
"eduPersonEntitlement": "urn:mace:swamid.se:foo:bar",
|
||||
"schacGender": "male",
|
||||
"schacUserPresenceID": "skype:pepe.perez"
|
||||
}
|
||||
}
|
||||
61
example/idp2_repoze/modules/login.mako.py
Normal file
61
example/idp2_repoze/modules/login.mako.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# -*- encoding:utf-8 -*-
|
||||
from mako import runtime, filters, cache
|
||||
UNDEFINED = runtime.UNDEFINED
|
||||
__M_dict_builtin = dict
|
||||
__M_locals_builtin = locals
|
||||
_magic_number = 6
|
||||
_modified_time = 1367126126.936375
|
||||
_template_filename='htdocs/login.mako'
|
||||
_template_uri='login.mako'
|
||||
_template_cache=cache.Cache(__name__, _modified_time)
|
||||
_source_encoding='utf-8'
|
||||
_exports = []
|
||||
|
||||
|
||||
def _mako_get_namespace(context, name):
|
||||
try:
|
||||
return context.namespaces[(__name__, name)]
|
||||
except KeyError:
|
||||
_mako_generate_namespaces(context)
|
||||
return context.namespaces[(__name__, name)]
|
||||
def _mako_generate_namespaces(context):
|
||||
pass
|
||||
def _mako_inherit(template, context):
|
||||
_mako_generate_namespaces(context)
|
||||
return runtime._inherit_from(context, u'root.mako', _template_uri)
|
||||
def render_body(context,**pageargs):
|
||||
context.caller_stack._push_frame()
|
||||
try:
|
||||
__M_locals = __M_dict_builtin(pageargs=pageargs)
|
||||
redirect_uri = context.get('redirect_uri', UNDEFINED)
|
||||
key = context.get('key', UNDEFINED)
|
||||
action = context.get('action', UNDEFINED)
|
||||
authn_reference = context.get('authn_reference', UNDEFINED)
|
||||
login = context.get('login', UNDEFINED)
|
||||
password = context.get('password', UNDEFINED)
|
||||
__M_writer = context.writer()
|
||||
# SOURCE LINE 1
|
||||
__M_writer(u'\n\n<h1>Please log in</h1>\n<p class="description">\n To register it\'s quite simple: enter a login and a password\n</p>\n\n<form action="')
|
||||
# SOURCE LINE 8
|
||||
__M_writer(unicode(action))
|
||||
__M_writer(u'" method="post">\n <input type="hidden" name="key" value="')
|
||||
# SOURCE LINE 9
|
||||
__M_writer(unicode(key))
|
||||
__M_writer(u'"/>\n <input type="hidden" name="authn_reference" value="')
|
||||
# SOURCE LINE 10
|
||||
__M_writer(unicode(authn_reference))
|
||||
__M_writer(u'"/>\n <input type="hidden" name="redirect_uri" value="')
|
||||
# SOURCE LINE 11
|
||||
__M_writer(unicode(redirect_uri))
|
||||
__M_writer(u'"/>\n\n <div class="label">\n <label for="login">Username</label>\n </div>\n <div>\n <input type="text" name="login" value="')
|
||||
# SOURCE LINE 17
|
||||
__M_writer(unicode(login))
|
||||
__M_writer(u'"/><br/>\n </div>\n\n <div class="label">\n <label for="password">Password</label>\n </div>\n <div>\n <input type="password" name="password"\n value="')
|
||||
# SOURCE LINE 25
|
||||
__M_writer(unicode(password))
|
||||
__M_writer(u'"/>\n </div>\n\n <input class="submit" type="submit" name="form.submitted" value="Log In"/>\n</form>\n')
|
||||
return ''
|
||||
finally:
|
||||
context.caller_stack._pop_frame()
|
||||
|
||||
|
||||
133
example/idp2_repoze/modules/root.mako.py
Normal file
133
example/idp2_repoze/modules/root.mako.py
Normal file
@@ -0,0 +1,133 @@
|
||||
# -*- encoding:utf-8 -*-
|
||||
from mako import runtime, filters, cache
|
||||
UNDEFINED = runtime.UNDEFINED
|
||||
__M_dict_builtin = dict
|
||||
__M_locals_builtin = locals
|
||||
_magic_number = 6
|
||||
_modified_time = 1357242050.211483
|
||||
_template_filename=u'templates/root.mako'
|
||||
_template_uri=u'root.mako'
|
||||
_template_cache=cache.Cache(__name__, _modified_time)
|
||||
_source_encoding='utf-8'
|
||||
_exports = ['css_link', 'pre', 'post', 'css']
|
||||
|
||||
|
||||
def render_body(context,**pageargs):
|
||||
context.caller_stack._push_frame()
|
||||
try:
|
||||
__M_locals = __M_dict_builtin(pageargs=pageargs)
|
||||
def pre():
|
||||
return render_pre(context.locals_(__M_locals))
|
||||
self = context.get('self', UNDEFINED)
|
||||
set = context.get('set', UNDEFINED)
|
||||
def post():
|
||||
return render_post(context.locals_(__M_locals))
|
||||
next = context.get('next', UNDEFINED)
|
||||
__M_writer = context.writer()
|
||||
# SOURCE LINE 1
|
||||
self.seen_css = set()
|
||||
|
||||
__M_writer(u'\n')
|
||||
# SOURCE LINE 7
|
||||
__M_writer(u'\n')
|
||||
# SOURCE LINE 10
|
||||
__M_writer(u'\n')
|
||||
# SOURCE LINE 15
|
||||
__M_writer(u'\n')
|
||||
# SOURCE LINE 22
|
||||
__M_writer(u'\n')
|
||||
# SOURCE LINE 25
|
||||
__M_writer(u'<html>\n<head><title>IDP test login</title>\n ')
|
||||
# SOURCE LINE 27
|
||||
__M_writer(unicode(self.css()))
|
||||
__M_writer(u'\n <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\n</head>\n<body>\n ')
|
||||
# SOURCE LINE 31
|
||||
__M_writer(unicode(pre()))
|
||||
__M_writer(u'\n')
|
||||
# SOURCE LINE 34
|
||||
__M_writer(unicode(next.body()))
|
||||
__M_writer(u'\n')
|
||||
# SOURCE LINE 35
|
||||
__M_writer(unicode(post()))
|
||||
__M_writer(u'\n</body>\n</html>\n')
|
||||
return ''
|
||||
finally:
|
||||
context.caller_stack._pop_frame()
|
||||
|
||||
|
||||
def render_css_link(context,path,media=''):
|
||||
context.caller_stack._push_frame()
|
||||
try:
|
||||
context._push_buffer()
|
||||
self = context.get('self', UNDEFINED)
|
||||
__M_writer = context.writer()
|
||||
# SOURCE LINE 2
|
||||
__M_writer(u'\n')
|
||||
# SOURCE LINE 3
|
||||
if path not in self.seen_css:
|
||||
# SOURCE LINE 4
|
||||
__M_writer(u' <link rel="stylesheet" type="text/css" href="')
|
||||
__M_writer(filters.html_escape(unicode(path)))
|
||||
__M_writer(u'" media="')
|
||||
__M_writer(unicode(media))
|
||||
__M_writer(u'">\n')
|
||||
pass
|
||||
# SOURCE LINE 6
|
||||
__M_writer(u' ')
|
||||
self.seen_css.add(path)
|
||||
|
||||
__M_writer(u'\n')
|
||||
finally:
|
||||
__M_buf, __M_writer = context._pop_buffer_and_writer()
|
||||
context.caller_stack._pop_frame()
|
||||
__M_writer(filters.trim(__M_buf.getvalue()))
|
||||
return ''
|
||||
|
||||
|
||||
def render_pre(context):
|
||||
context.caller_stack._push_frame()
|
||||
try:
|
||||
context._push_buffer()
|
||||
__M_writer = context.writer()
|
||||
# SOURCE LINE 11
|
||||
__M_writer(u'\n <div class="header">\n <h1><a href="/">Login</a></h1>\n </div>\n')
|
||||
finally:
|
||||
__M_buf, __M_writer = context._pop_buffer_and_writer()
|
||||
context.caller_stack._pop_frame()
|
||||
__M_writer(filters.trim(__M_buf.getvalue()))
|
||||
return ''
|
||||
|
||||
|
||||
def render_post(context):
|
||||
context.caller_stack._push_frame()
|
||||
try:
|
||||
context._push_buffer()
|
||||
__M_writer = context.writer()
|
||||
# SOURCE LINE 16
|
||||
__M_writer(u'\n <div>\n <div class="footer">\n <p>© Copyright 2011 Umeå Universitet </p>\n </div>\n </div>\n')
|
||||
finally:
|
||||
__M_buf, __M_writer = context._pop_buffer_and_writer()
|
||||
context.caller_stack._pop_frame()
|
||||
__M_writer(filters.trim(__M_buf.getvalue()))
|
||||
return ''
|
||||
|
||||
|
||||
def render_css(context):
|
||||
context.caller_stack._push_frame()
|
||||
try:
|
||||
context._push_buffer()
|
||||
def css_link(path,media=''):
|
||||
return render_css_link(context,path,media)
|
||||
__M_writer = context.writer()
|
||||
# SOURCE LINE 8
|
||||
__M_writer(u'\n ')
|
||||
# SOURCE LINE 9
|
||||
__M_writer(unicode(css_link('/css/main.css', 'screen')))
|
||||
__M_writer(u'\n')
|
||||
finally:
|
||||
__M_buf, __M_writer = context._pop_buffer_and_writer()
|
||||
context.caller_stack._pop_frame()
|
||||
__M_writer(filters.trim(__M_buf.getvalue()))
|
||||
return ''
|
||||
|
||||
|
||||
18
example/idp2_repoze/pki/mycert.pem
Normal file
18
example/idp2_repoze/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
example/idp2_repoze/pki/mykey.pem
Normal file
15
example/idp2_repoze/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-----
|
||||
2
example/idp2_repoze/static/css/main.css
Normal file
2
example/idp2_repoze/static/css/main.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/* Sample css file */
|
||||
|
||||
37
example/idp2_repoze/templates/root.mako
Normal file
37
example/idp2_repoze/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('/static/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>
|
||||
Reference in New Issue
Block a user