server.py 5.75 KB
Newer Older
Michael Völske's avatar
Michael Völske committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
#!/usr/bin/env python
# Webis OpenID Server
#
# This app messes with your pip. Run it in a container (tested with official
# python:3 image)
#
# Make sure these environment variables are defined:
ENV = ['OPENID_APP_ID',
       'OPENID_APP_SECRET',
       'OPENID_ISSUER',
       'OPENID_CALLBACK_URL']
########################################################################
import os
import logging
import sys
import json


L = logging.getLogger('Webis OpenID')
L.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
L.addHandler(handler)

if not(all(x in os.environ for x in ENV)):
    L.error("Make sure all evironment variables in %s are set", ENV)
    sys.exit(1)

ENV = dict((k, os.environ[k]) for k in ENV)
CB_ROUTE = "/".join(ENV['OPENID_CALLBACK_URL'].split('//')[1].split(':')[-1].split('/')[1:])
L.info("Will send callbacks to /%s", CB_ROUTE)


L.info("Setting up...")
retval = os.system('pip install bottle==0.12.* oic==0.15.*')
if retval != 0:
    L.error('Setup failed.')
    sys.exit(retval)
L.info("Done setting up...")



########################################################################

from oic import rndstr
from oic.oic import Client
from oic.oic.message import RegistrationResponse, AuthorizationResponse
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
from oic.utils.http_util import Redirect

from bottle import run, route, request, response, redirect, abort


k_key = rndstr()
sessions = {}

def get_session(sid=None):
    if sid is None:
        sid = request.get_cookie('session', secret=k_key)
    if sid is None:
        sid = rndstr()
        response.set_cookie('session', sid, secret=k_key)
    if sid not in sessions:
        sessions[sid] = {}
    return sid, sessions[sid]

def setv(k, v, sid=None):
    sid, s = get_session(sid)
    s[k] = v
    return sid

def getv(k, sid=None):
    sid, s = get_session(sid)
    return s.get(k, None)

def close_session(sid=None):
    if sid is None:
        sid = request.get_cookie('session', secret=k_key)
    if sid is not None and sid in sessions:
        del sessions[sid]

ALLOWED_OPS = ['k8s-login']

def get_client(session):
    if 'client' in session:
        L.info('Reusing client from session')
        return session['client']

    L.info('Building OIC client')

    client = Client(client_authn_method=CLIENT_AUTHN_METHOD)
    provider_info = client.provider_config(ENV['OPENID_ISSUER'])
    info = {"client_id": ENV['OPENID_APP_ID'],
            "client_secret": ENV['OPENID_APP_SECRET'],
            "redirect_uris": [ENV['OPENID_CALLBACK_URL']]}
    client_reg = RegistrationResponse(**info)
    client.store_registration_info(client_reg)

    session['client'] = client

    return client

@route('/')
def index():
    out = "<html><head><title>Choose Login op</title></head>" + \
          "<body><p>Click below to continue:</p><ol>{ops}</ol></body></html>"

    # maybe we'll have more of these someday.
    out = out.format(
        ops='\n'.join('<li><a href="{rt}">{name}</a></li>'.format(
            rt=x, name=x
        ) for x in ALLOWED_OPS)
    )
    response.content_type = 'text/html; charset=utf8'
    return out

@route('/k8s-login')
def k8s_login():
    sid = setv('op', 'k8s-login')
    setv('state', rndstr(), sid=sid)
    setv('nonce', rndstr(), sid=sid)
    sid, session = get_session(sid=sid)
    client = get_client(session)
    rq_args = {
        "client_id": client.client_id,
        "response_type": ['code'],
        "scope": ['openid', 'offline_access', 'email', 'groups', 'profile'],
        "nonce": session["nonce"],
        "redirect_uri": client.registration_response["redirect_uris"][0],
        "state": session["state"]
    }
    auth_req = client.construct_AuthorizationRequest(request_args=rq_args)

    login_url = auth_req.request(client.authorization_endpoint)
    redirect(login_url)


@route('/%s' % CB_ROUTE)
def callback():
    op = getv('op')
    #if op is None:
    #    abort(418, 'Nothing to see here')
    #if op not in ALLOWED_OPS:
    #    abort(501, 'Operation %s is not supported' % op)
    aresp = dict(request.query.decode())
    sid, session = get_session()
    if 'code' not in aresp or 'state' not in aresp or aresp['state'] != getv('state'):
        abort(400, "Bad auth response. Got: %s." % aresp)

    client = get_client(session)

    aresp = client.parse_response(AuthorizationResponse, json.dumps(aresp))

    L.info(aresp)
    L.info(session)
    L.info("Sending token request...")
    resp = client.do_access_token_request(
        state=aresp['state'], request_args={'code': aresp['code']},
        auth_method='client_secret_basic')

    itok = resp['id_token']
    close_session()


    response.content_type = 'text/plain; charset=utf8'
    return CMDLINE_TEMPLATE.format(
        u=itok['email'], url=itok['iss'], cid=ENV['OPENID_APP_ID'],
        cs=ENV['OPENID_APP_SECRET'], rtok=resp['refresh_token'])


CMDLINE_TEMPLATE = """\
### All done. Paste the following into your terminal:


{{
    kubectl config set-credentials "{u}" \\
        --auth-provider=oidc \\
        --auth-provider-arg=idp-issuer-url={url} \\
        --auth-provider-arg=client-id={cid} \\
        --auth-provider-arg=client-secret={cs} \\
        --auth-provider-arg=refresh-token={rtok}


    kubectl config set-cluster webis6 \\
        --server=https://webis6.medien.uni-weimar.de:6443 \\
        --insecure-skip-tls-verify=true

189 190 191 192
    kubectl config set-cluster betaweb \\
        --server=https://betaweb001.medien.uni-weimar.de:6443 \\
        --insecure-skip-tls-verify=true

Michael Völske's avatar
Michael Völske committed
193
    kubectl config set-context webis6 --cluster=webis6 --user="{u}"
194
    kubectl config set-context betaweb --cluster=betaweb --user="{u}"
Michael Völske's avatar
Michael Völske committed
195 196 197 198 199 200 201

    kubectl config use-context webis6
}}
"""


run(host='0.0.0.0', port=42001, reloader=True)