Commit 9db3f42a authored by Eric Pellegrini's avatar Eric Pellegrini

Added keycloak class directly in JupyterHubConfig

parent 0bdb18fb
# Configuration file for jupyterhub.
import os
import json
import os
import sys
import tempfile
import urllib
from tornado import gen, web
from tornado.auth import OAuth2Mixin
from tornado.httpclient import HTTPRequest, AsyncHTTPClient
from tornado.httputil import url_concat
from jupyterhub.auth import LocalAuthenticator
from jupyterhub.handlers import LogoutHandler
from jupyterhub.utils import url_path_join
from oauthenticator.oauth2 import OAuthLoginHandler, OAuthenticator
class KeycloakMixin(OAuth2Mixin):
_OAUTH_AUTHORIZE_URL = os.getenv("OAUTH2_AUTHORIZE_URL", "https://login.ill.fr/auth/realms/ILL/protocol/openid-connect/auth")
_OAUTH_ACCESS_TOKEN_URL = os.getenv("OAUTH2_TOKEN_URL" , "https://login.ill.fr/auth/realms/ILL/protocol/openid-connect/token")
_OAUTH_LOGOUT_URL = os.getenv("OAUTH2_LOGOUT_URL" , "https://login.ill.fr/auth/realms/ILL/protocol/openid-connect/logout")
_OAUTH_USERINFO_URL = os.getenv("OAUTH2_USERINFO_URL" , "https://login.ill.fr/auth/realms/ILL/protocol/openid-connect/userinfo")
class KeycloakLoginHandler(OAuthLoginHandler, KeycloakMixin):
pass
class KeycloakLogoutHandler(LogoutHandler, KeycloakMixin):
def get(self):
# Get the current user and clear its login cookie
user = self.get_current_user()
if user:
self.log.info("User logged out: %s", user.name)
self.clear_login_cookie()
self.statsd.incr('logout')
# The logout will access login.ill.fr keycloak logout which will further redirect to jupyterhub login page
params = dict(redirect_uri="%s://%s%slogin" % (self.request.protocol, self.request.host,self.hub.server.base_url))
logout_url = KeycloakMixin._OAUTH_LOGOUT_URL
logout_url = url_concat(logout_url, params)
self.redirect(logout_url, permanent=False)
class KeycloakOAuthenticator(OAuthenticator, KeycloakMixin):
login_service = "Keycloak"
login_handler = KeycloakLoginHandler
def logout_url(self, base_url):
return url_path_join(base_url, 'oauth_logout')
def get_handlers(self, app):
handlers = OAuthenticator.get_handlers(self, app)
# Override the logout handler with the keycloak logout handler
handlers.extend([(r'/logout', KeycloakLogoutHandler)])
handlers.extend([(r'/oauth_logout', KeycloakLogoutHandler)])
return handlers
@gen.coroutine
def authenticate(self, handler, data=None):
code = handler.get_argument("code", False)
if not code:
raise web.HTTPError(400, "oauth callback made without a token")
http_client = AsyncHTTPClient()
params = dict(
grant_type='authorization_code',
code=code,
redirect_uri=self.get_callback_url(handler),
)
tokenUrl = KeycloakMixin._OAUTH_ACCESS_TOKEN_URL
tokenReq = HTTPRequest(tokenUrl,
method="POST",
headers={"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"},
auth_username=self.client_id,
auth_password=self.client_secret,
body=urllib.parse.urlencode(params).encode(
'utf-8'),
)
tokenResp = yield http_client.fetch(tokenReq)
tokenResp_json = json.loads(tokenResp.body.decode('utf8', 'replace'))
access_token = tokenResp_json['access_token']
if not access_token:
raise web.HTTPError(400, "failed to get access token")
self.log.info('oauth token: %r', access_token)
userInfoUrl = KeycloakMixin._OAUTH_USERINFO_URL
userInfoReq = HTTPRequest(userInfoUrl,
method="GET",
headers={"Accept": "application/json",
"Authorization": "Bearer %s" % access_token},
)
userInfoResp = yield http_client.fetch(userInfoReq)
userInfoResp_json = json.loads(
userInfoResp.body.decode('utf8', 'replace'))
return userInfoResp_json['preferred_username']
class LocalKeycloakOAuthenticator(LocalAuthenticator, KeycloakOAuthenticator):
"""A version that mixes in local system user creation"""
pass
bin_dir = os.path.split(sys.executable)[0]
spawner = os.path.join(bin_dir, 'sudospawner')
......@@ -23,7 +129,6 @@ c.JupyterHub.db_url = database
c.JupyterHub.cookie_secret_file = cookie
c.SudoSpawner.sudospawner_path = spawner
import os
visa_jupyter_client_secret = os.environ.get("VISA_JUPYTER_CLIENT_SECRET",None)
if not visa_jupyter_client_secret:
raise RuntimeError("The VISA_JUPYTER_CLIENT_SECRET env var is not defined")
......@@ -31,15 +136,9 @@ if not visa_jupyter_client_secret:
import socket
hostname = socket.gethostname()
# Use the keycloak oauthenticator
from oauthenticator.generic import GenericOAuthenticator
c.JupyterHub.authenticator_class = GenericOAuthenticator
c.JupyterHub.authenticator_class = KeycloakOAuthenticator
c.OAuthenticator.client_id = hostname
c.OAuthenticator.client_secret = visa_jupyter_client_secret
c.OAuthenticator.logout_url = lambda base_url : "https://login.ill.fr/auth/realms/ILL/protocol/openid-connect/logout"
c.OAuthenticator.userdata_method = "GET"
c.OAuthenticator.userdata_params = {"state": "state"}
c.OAuthenticator.username_key = "preferred_username"
# Here are the jupyter infrastructure admins
c.Authenticator.admin_users = {"pellegrini","pinet","hall","caunt","perrin"}
......
......@@ -2,4 +2,8 @@ OAUTH2_AUTHORIZE_URL={{oauth2_authorize_url}}
OAUTH2_TOKEN_URL={{oauth2_token_url}}
OAUTH2_USERINFO_URL={{oauth2_userinfo_url}}
OAUTH2_LOGOUT_URL={{oauth2_logout_url}}
VISA_JUPYTER_CLIENT_SECRET={{visa_jupyter_client_secret}}
---
oauth2_authorize_url: "{{ lookup('env','OAUTH2_AUTHORIZE_URL') }}"
oauth2_token_url: "{{ lookup('env','OAUTH2_TOKEN_URL') }}"
oauth2_userinfo_url: "{{ lookup('env','OAUTH2_USERINFO_URL') }}"
oauth2_logout_url: "{{ lookup('env','OAUTH2_LOGOUT_URL') }}"
visa_jupyter_client_secret: "{{ lookup('env','VISA_JUPYTER_CLIENT_SECRET') }}"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment