Second IDP example
This commit is contained in:
		
							
								
								
									
										28
									
								
								example/idp2/htdocs/login.mako
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								example/idp2/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>
 | 
				
			||||||
							
								
								
									
										479
									
								
								example/idp2/idp.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										479
									
								
								example/idp2/idp.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,479 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import urllib
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from urlparse import parse_qs
 | 
				
			||||||
 | 
					from saml2 import server, BINDING_SOAP
 | 
				
			||||||
 | 
					from saml2 import BINDING_HTTP_REDIRECT, BINDING_HTTP_POST
 | 
				
			||||||
 | 
					from saml2 import time_util
 | 
				
			||||||
 | 
					from Cookie import SimpleCookie
 | 
				
			||||||
 | 
					from saml2.httputil import Response, Redirect, Unauthorized
 | 
				
			||||||
 | 
					from saml2.pack import http_form_post_message
 | 
				
			||||||
 | 
					from saml2.pack import http_soap_message
 | 
				
			||||||
 | 
					from saml2.s_utils import rndstr
 | 
				
			||||||
 | 
					from saml2.saml import AUTHN_PASSWORD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 get_post(environ):
 | 
				
			||||||
 | 
					    # the environment variable CONTENT_LENGTH may be empty or missing
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        request_body_size = int(environ.get('CONTENT_LENGTH', 0))
 | 
				
			||||||
 | 
					    except ValueError:
 | 
				
			||||||
 | 
					        request_body_size = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # When the method is POST the query string will be sent
 | 
				
			||||||
 | 
					    # in the HTTP request body which is passed by the WSGI server
 | 
				
			||||||
 | 
					    # in the file like wsgi.input environment variable.
 | 
				
			||||||
 | 
					    return environ['wsgi.input'].read(request_body_size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# -----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					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>"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _sso(environ, start_response, query, binding, user):
 | 
				
			||||||
 | 
					    if not query:
 | 
				
			||||||
 | 
					        logger.info("Missing QUERY")
 | 
				
			||||||
 | 
					        start_response('401 Unauthorized', [('Content-Type', 'text/plain')])
 | 
				
			||||||
 | 
					        return ['Unknown user']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # base 64 encoded request
 | 
				
			||||||
 | 
					    req_info = IDP.parse_authn_request(query["SAMLRequest"][0], binding=binding)
 | 
				
			||||||
 | 
					    resp_args = IDP.response_args(req_info.message, [BINDING_HTTP_POST],
 | 
				
			||||||
 | 
					                                  descr_type="spsso")
 | 
				
			||||||
 | 
					    logger.info("parsed OK")
 | 
				
			||||||
 | 
					    logger.info("%s" % req_info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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:
 | 
				
			||||||
 | 
					        if logger: logger.error("Exception: %s" % (excp,))
 | 
				
			||||||
 | 
					        raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if logger: logger.info("AuthNResponse: %s" % authn_resp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http_args = http_form_post_message(authn_resp, resp_args["destination"],
 | 
				
			||||||
 | 
					                                       relay_state=query["RelayState"][0],
 | 
				
			||||||
 | 
					                                       typ="SAMLResponse")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resp = Response(http_args["data"], headers=http_args["headers"])
 | 
				
			||||||
 | 
					    return resp(environ, start_response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def sso(environ, start_response, user):
 | 
				
			||||||
 | 
					    """ Supposted to return a POST """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.info("--- In SSO ---")
 | 
				
			||||||
 | 
					    logger.debug("user: %s" % user)
 | 
				
			||||||
 | 
					    logger.info("Query string: %s" % environ["QUERY_STRING"])
 | 
				
			||||||
 | 
					    extra = parse_qs(environ["QUERY_STRING"])
 | 
				
			||||||
 | 
					    logger.info("EXTRA: %s" % extra)
 | 
				
			||||||
 | 
					    logger.debug("keys: %s" % IDP.ticket.keys())
 | 
				
			||||||
 | 
					    query = parse_qs(IDP.ticket[extra["key"][0]])
 | 
				
			||||||
 | 
					    del IDP.ticket[extra["key"][0]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return _sso(environ, start_response, query, BINDING_HTTP_REDIRECT, user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def sso_post(environ, start_response, user):
 | 
				
			||||||
 | 
					    logger.info("--- In SSO POST ---")
 | 
				
			||||||
 | 
					    logger.debug("user: %s" % user)
 | 
				
			||||||
 | 
					    logger.info("Query string: %s" % environ["QUERY_STRING"])
 | 
				
			||||||
 | 
					    extra = parse_qs(environ["QUERY_STRING"])
 | 
				
			||||||
 | 
					    logger.info("EXTRA: %s" % extra)
 | 
				
			||||||
 | 
					    logger.debug("keys: %s" % IDP.ticket.keys())
 | 
				
			||||||
 | 
					    query = parse_qs(IDP.ticket[extra["key"][0]])
 | 
				
			||||||
 | 
					    del IDP.ticket[extra["key"][0]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return _sso(environ, start_response, query, BINDING_HTTP_POST, user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def not_authn(environ, start_response):
 | 
				
			||||||
 | 
					    # redirect to login page
 | 
				
			||||||
 | 
					    logger.info("not_authn ENV: %s" % environ)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    loc = "http://%s/login" % (environ["HTTP_HOST"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    headers = [('Content-Type', 'text/plain')]
 | 
				
			||||||
 | 
					    if environ["REQUEST_METHOD"] == "GET":
 | 
				
			||||||
 | 
					        if "QUERY_STRING" in environ:
 | 
				
			||||||
 | 
					            query = environ["QUERY_STRING"]
 | 
				
			||||||
 | 
					            logger.info("query: %s" % query)
 | 
				
			||||||
 | 
					            key = hash(query)
 | 
				
			||||||
 | 
					            IDP.ticket[str(key)] = query
 | 
				
			||||||
 | 
					            loc += "?%s" % urllib.urlencode({"came_from": environ["PATH_INFO"],
 | 
				
			||||||
 | 
					                                             "key": key})
 | 
				
			||||||
 | 
					    elif environ["REQUEST_METHOD"] == "POST":
 | 
				
			||||||
 | 
					        query = get_post(environ)
 | 
				
			||||||
 | 
					        logger.info("query: %s" % query)
 | 
				
			||||||
 | 
					        key = hash(query)
 | 
				
			||||||
 | 
					        IDP.ticket[str(key)] = query
 | 
				
			||||||
 | 
					        loc += "?%s" % urllib.urlencode({"came_from": environ["PATH_INFO"],
 | 
				
			||||||
 | 
					                                         "key": key})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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, sid, cookie=None):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Put up 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PASSWD = [("roland", "dianakra"),
 | 
				
			||||||
 | 
					          ("babs", "howes"),
 | 
				
			||||||
 | 
					          ("upper", "crust")]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ===========================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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, query, user):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        req_info = IDP.parse_logout_request(query["SAMLRequest"][0],
 | 
				
			||||||
 | 
					                                            BINDING_HTTP_REDIRECT)
 | 
				
			||||||
 | 
					        relay_state = query["SAMLRequest"][0]
 | 
				
			||||||
 | 
					        logger.info("LOGOUT request parsed OK")
 | 
				
			||||||
 | 
					        logger.info("REQ_INFO: %s" % req_info.message)
 | 
				
			||||||
 | 
					    except KeyError, exc:
 | 
				
			||||||
 | 
					        if logger: logger.info("logout request error: %s" % (exc,))
 | 
				
			||||||
 | 
					        start_response('400 Bad request', [('Content-Type', 'text/plain')])
 | 
				
			||||||
 | 
					        return ['Request parse error']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    subject, sp_entity_id = _subject_sp_info(req_info)
 | 
				
			||||||
 | 
					    logger.info("Logout subject: %s" % (subject,))
 | 
				
			||||||
 | 
					    logger.info("local identifier: %s" % IDP.ident.local_name(sp_entity_id, 
 | 
				
			||||||
 | 
					                                                                subject))
 | 
				
			||||||
 | 
					    # remove the authentication
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    status = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Either HTTP-Post or HTTP-redirect is possible
 | 
				
			||||||
 | 
					    bindings = [BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]
 | 
				
			||||||
 | 
					    logger.debug("logout response to %s" % sp_entity_id)
 | 
				
			||||||
 | 
					    logger.debug("entity info: %s" % IDP.metadata.entity[sp_entity_id]["spsso"][0])
 | 
				
			||||||
 | 
					    (resp, headers, message) = IDP.create_logout_response(req_info.message,
 | 
				
			||||||
 | 
					                                                          bindings)
 | 
				
			||||||
 | 
					    #headers.append(session.cookie(expire="now"))
 | 
				
			||||||
 | 
					    logger.info("Response code: %s" % (resp,))
 | 
				
			||||||
 | 
					    logger.info("Header: %s" % (headers,))
 | 
				
			||||||
 | 
					    delco = delete_cookie(environ, "idpauthn")
 | 
				
			||||||
 | 
					    if delco:
 | 
				
			||||||
 | 
					        headers.append(delco)
 | 
				
			||||||
 | 
					    start_response(resp, headers)
 | 
				
			||||||
 | 
					    return message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def slo(environ, start_response, user):
 | 
				
			||||||
 | 
					    """ Expects a HTTP-redirect logout request """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    query = None
 | 
				
			||||||
 | 
					    if "QUERY_STRING" in environ:
 | 
				
			||||||
 | 
					        logger.info("Query string: %s" % environ["QUERY_STRING"])
 | 
				
			||||||
 | 
					        query = parse_qs(environ["QUERY_STRING"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not query:
 | 
				
			||||||
 | 
					        start_response('401 Unauthorized', [('Content-Type', 'text/plain')])
 | 
				
			||||||
 | 
					        return ['Unknown user']
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return _slo(environ, start_response, query, user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def slo_post(environ, start_response, user):
 | 
				
			||||||
 | 
					    """ Expects a HTTP-POST logout request """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    query = parse_qs(get_post(environ))
 | 
				
			||||||
 | 
					    return _slo(environ, start_response, query, user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def slo_soap(environ, start_response, user):
 | 
				
			||||||
 | 
					    soap_message = get_post(environ)
 | 
				
			||||||
 | 
					    #logger.debug("info type: %s" % type(soap_message))
 | 
				
			||||||
 | 
					    #logger.debug("SLO_SOAP: %s" % soap_message)
 | 
				
			||||||
 | 
					    req_info = IDP.parse_logout_request("%s" % soap_message, BINDING_SOAP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    subject, sp_entity_id = _subject_sp_info(req_info)
 | 
				
			||||||
 | 
					    logger.info("Logout subject: %s" % (subject,))
 | 
				
			||||||
 | 
					    logger.info("local identifier: %s" % IDP.ident.local_name(sp_entity_id,
 | 
				
			||||||
 | 
					                                                              subject))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    response = IDP.create_logout_response(req_info.message, [BINDING_SOAP])
 | 
				
			||||||
 | 
					    args = http_soap_message(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delco = delete_cookie(environ, "idpauthn")
 | 
				
			||||||
 | 
					    if delco:
 | 
				
			||||||
 | 
					        args["headers"].append(delco)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resp = Response(args["data"], headers=args["headers"])
 | 
				
			||||||
 | 
					    return resp(environ, start_response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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),
 | 
				
			||||||
 | 
					    (r'post_sso$', sso_post),
 | 
				
			||||||
 | 
					    (r'post_sso/(.*)$', sso_post),
 | 
				
			||||||
 | 
					    (r'sso$', sso),
 | 
				
			||||||
 | 
					    (r'sso/(.*)$', sso),
 | 
				
			||||||
 | 
					    (r'logout$', slo),
 | 
				
			||||||
 | 
					    (r'logout/(.*)$', slo),
 | 
				
			||||||
 | 
					    (r'logout_post$', slo_post),
 | 
				
			||||||
 | 
					    (r'logout_post/(.*)$', slo_post),
 | 
				
			||||||
 | 
					    (r'logout_soap$', slo_soap),
 | 
				
			||||||
 | 
					    (r'logout_soap/(.*)$', slo_soap),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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()
 | 
				
			||||||
							
								
								
									
										74
									
								
								example/idp2/idp_conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								example/idp2/idp_conf.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					from saml2 import BINDING_HTTP_REDIRECT
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#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": {
 | 
				
			||||||
 | 
					        "idp": {
 | 
				
			||||||
 | 
					            "name" : "Rolands IdP",
 | 
				
			||||||
 | 
					            "endpoints" : {
 | 
				
			||||||
 | 
					                "single_sign_on_service":[(BASE+"/sso",BINDING_HTTP_REDIRECT),
 | 
				
			||||||
 | 
					                                          (BASE+"/post_sso", BINDING_HTTP_POST)],
 | 
				
			||||||
 | 
					                "single_logout_service":[(BASE+"/logout",
 | 
				
			||||||
 | 
					                                          BINDING_HTTP_REDIRECT),
 | 
				
			||||||
 | 
					                                         (BASE+"/logout_post",
 | 
				
			||||||
 | 
					                                             BINDING_HTTP_POST),
 | 
				
			||||||
 | 
					                                         (BASE+"/logout_soap",
 | 
				
			||||||
 | 
					                                             BINDING_SOAP)],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "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": "/usr/local/bin/xmlsec1",
 | 
				
			||||||
 | 
					    "attribute_map_dir" : "../attributemaps",
 | 
				
			||||||
 | 
					    "logger": {
 | 
				
			||||||
 | 
					        "rotating": {
 | 
				
			||||||
 | 
					            "filename": "idp.log",
 | 
				
			||||||
 | 
					            "maxBytes": 500000,
 | 
				
			||||||
 | 
					            "backupCount": 5,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        "loglevel": "debug",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								example/idp2/idp_user.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								example/idp2/idp_user.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					USERS = {
 | 
				
			||||||
 | 
					    "roland": {
 | 
				
			||||||
 | 
					        "surname": "Hedberg",
 | 
				
			||||||
 | 
					        "givenName": "Roland",
 | 
				
			||||||
 | 
					        "eduPersonAffiliation": "staff",
 | 
				
			||||||
 | 
					        "uid": "rohe0002"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					     "ozzie": {
 | 
				
			||||||
 | 
					         "surname": "Guillen",
 | 
				
			||||||
 | 
					         "givenName": "Ozzie",
 | 
				
			||||||
 | 
					         "eduPersonAffiliation": "affiliate"
 | 
				
			||||||
 | 
					     },
 | 
				
			||||||
 | 
					     "derek": {
 | 
				
			||||||
 | 
					         "surname": "Jeter",
 | 
				
			||||||
 | 
					         "givenName": "Derek",
 | 
				
			||||||
 | 
					         "eduPersonAffiliation": "affiliate"
 | 
				
			||||||
 | 
					     },
 | 
				
			||||||
 | 
					     "ichiro": {
 | 
				
			||||||
 | 
					         "surname": "Suzuki",
 | 
				
			||||||
 | 
					         "givenName": "Ischiro",
 | 
				
			||||||
 | 
					         "eduPersonAffiliation": "affiliate"
 | 
				
			||||||
 | 
					     },
 | 
				
			||||||
 | 
					     "ryan": {
 | 
				
			||||||
 | 
					         "surname": "Howard",
 | 
				
			||||||
 | 
					         "givenName": "Ryan",
 | 
				
			||||||
 | 
					         "eduPersonAffiliation": "affiliate"
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								example/idp2/pki/mycert.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								example/idp2/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/pki/mykey.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								example/idp2/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
									
								
								example/idp2/templates/root.mako
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								example/idp2/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>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user