This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
pyoidc-ozwillo/oidc_example/rp3/rp3.py

216 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env python
import importlib
import argparse
import urllib
from jwkest.jws import alg2keytype
from mako.lookup import TemplateLookup
from urlparse import parse_qs
import logging
from oic.utils.http_util import NotFound
from oic.utils.http_util import Response
from oic.utils.http_util import Redirect
LOGGER = logging.getLogger("")
LOGFILE_NAME = 'rp.log'
hdlr = logging.FileHandler(LOGFILE_NAME)
base_formatter = logging.Formatter(
"%(asctime)s %(name)s:%(levelname)s %(message)s")
CPC = ('%(asctime)s %(name)s:%(levelname)s '
'[%(client)s,%(path)s,%(cid)s] %(message)s')
cpc_formatter = logging.Formatter(CPC)
hdlr.setFormatter(base_formatter)
LOGGER.addHandler(hdlr)
LOGGER.setLevel(logging.DEBUG)
LOOKUP = TemplateLookup(directories=['templates', 'htdocs'],
module_directory='modules',
input_encoding='utf-8',
output_encoding='utf-8')
SERVER_ENV = {}
# noinspection PyUnresolvedReferences
def static(environ, start_response, logger, path):
logger.info("[static]sending: %s" % (path,))
try:
text = open(path).read()
if path.endswith(".ico"):
start_response('200 OK', [('Content-Type', "image/x-icon")])
elif path.endswith(".html"):
start_response('200 OK', [('Content-Type', 'text/html')])
elif path.endswith(".json"):
start_response('200 OK', [('Content-Type', 'application/json')])
elif path.endswith(".txt"):
start_response('200 OK', [('Content-Type', 'text/plain')])
elif path.endswith(".css"):
start_response('200 OK', [('Content-Type', 'text/css')])
else:
start_response('200 OK', [('Content-Type', "text/xml")])
return [text]
except IOError:
resp = NotFound()
return resp(environ, start_response)
def opchoice(environ, start_response, clients):
resp = Response(mako_template="opchoice.mako",
template_lookup=LOOKUP,
headers=[])
argv = {
"op_list": clients.keys()
}
return resp(environ, start_response, **argv)
def opresult(environ, start_response, userinfo):
resp = Response(mako_template="opresult.mako",
template_lookup=LOOKUP,
headers=[])
argv = {
"userinfo": userinfo,
}
return resp(environ, start_response, **argv)
def operror(environ, start_response, error=None):
resp = Response(mako_template="operror.mako",
template_lookup=LOOKUP,
headers=[])
argv = {
"error": error
}
return resp(environ, start_response, **argv)
#
def get_id_token(client, session):
return client.grant[session["state"]].get_id_token()
# Produce a JWS, a signed JWT, containing a previously received ID token
def id_token_as_signed_jwt(client, id_token, alg="RS256"):
ckey = client.keyjar.get_signing_key(alg2keytype(alg), "")
_signed_jwt = id_token.to_jwt(key=ckey, algorithm=alg)
return _signed_jwt
def clear_session(session):
for key in session:
session.pop(key, None)
session.invalidate()
def application(environ, start_response):
session = environ['beaker.session']
path = environ.get('PATH_INFO', '').lstrip('/')
if path == "robots.txt":
return static(environ, start_response, LOGGER, "static/robots.txt")
if path.startswith("static/"):
return static(environ, start_response, LOGGER, path)
query = parse_qs(environ["QUERY_STRING"])
if path == "rp": # After having chosen which OP to authenticate at
if "uid" in query:
client = CLIENTS.dynamic_client(query["uid"][0])
session["op"] = client.provider_info["issuer"]
else:
client = CLIENTS[query["op"][0]]
session["op"] = query["op"][0]
try:
resp = client.create_authn_request(session, ACR_VALUES)
except Exception:
raise
else:
return resp(environ, start_response)
elif path == "authz_cb": # After having authenticated at the OP
client = CLIENTS[session["op"]]
try:
userinfo = client.callback(query)
except OIDCError as err:
return operror(environ, start_response, "%s" % err)
except Exception:
raise
else:
return opresult(environ, start_response, userinfo)
elif path == "logout": # After the user has pressed the logout button
client = CLIENTS[session["op"]]
logout_url = client.endsession_endpoint
try:
# Specify to which URL the OP should return the user after
# log out. That URL must be registered with the OP at client
# registration.
logout_url += "?" + urllib.urlencode(
{"post_logout_redirect_uri": client.registration_response[
"post_logout_redirect_uris"][0]})
except KeyError:
pass
else:
# If there is an ID token send it along as a id_token_hint
_idtoken = get_id_token(client, session)
if _idtoken:
logout_url += "&" + urllib.urlencode({
"id_token_hint": id_token_as_signed_jwt(client, _idtoken,
"HS256")})
# Also append the ACR values
logout_url += "&" + urllib.urlencode({"acr_values": ACR_VALUES},
True)
LOGGER.debug("Logout URL: %s" % str(logout_url))
LOGGER.debug("Logging out from session: %s" % str(session))
clear_session(session)
resp = Redirect(str(logout_url))
return resp(environ, start_response)
return opchoice(environ, start_response, CLIENTS)
if __name__ == '__main__':
from oidc import OIDCClients
from oidc import OIDCError
from beaker.middleware import SessionMiddleware
from cherrypy import wsgiserver
parser = argparse.ArgumentParser()
parser.add_argument(dest="config")
args = parser.parse_args()
conf = importlib.import_module(args.config)
global ACR_VALUES
ACR_VALUES = conf.ACR_VALUES
session_opts = {
'session.type': 'memory',
'session.cookie_expires': True,
'session.auto': True,
'session.timeout': 900
}
CLIENTS = OIDCClients(conf)
SERVER_ENV.update({"template_lookup": LOOKUP, "base_url": conf.BASE})
SRV = wsgiserver.CherryPyWSGIServer(('0.0.0.0', conf.PORT),
SessionMiddleware(application,
session_opts))
if conf.BASE.startswith("https"):
from cherrypy.wsgiserver import ssl_pyopenssl
SRV.ssl_adapter = ssl_pyopenssl.pyOpenSSLAdapter(
conf.SERVER_CERT, conf.SERVER_KEY, conf.CA_BUNDLE)
LOGGER.info("RP server starting listening on port:%s" % conf.PORT)
print "RP server starting listening on port:%s" % conf.PORT
try:
SRV.start()
except KeyboardInterrupt:
SRV.stop()