2023-05-03 09:19:31 +00:00
|
|
|
hub:
|
|
|
|
config:
|
|
|
|
Authenticator:
|
|
|
|
auto_login: true
|
|
|
|
enable_auth_state: true
|
|
|
|
JupyterHub:
|
|
|
|
tornado_settings:
|
|
|
|
headers: { 'Content-Security-Policy': "frame-ancestors *;" }
|
2023-09-28 06:36:09 +00:00
|
|
|
db:
|
|
|
|
pvc:
|
|
|
|
storageClassName: csi-sc-cinderplugin
|
2023-05-03 09:19:31 +00:00
|
|
|
extraConfig:
|
|
|
|
oauthCode: |
|
2024-01-13 14:47:22 +00:00
|
|
|
import time
|
2024-01-15 11:29:56 +00:00
|
|
|
import requests
|
2024-01-15 09:14:07 +00:00
|
|
|
from datetime import datetime
|
2024-01-12 09:26:03 +00:00
|
|
|
from oauthenticator.generic import GenericOAuthenticator
|
2024-01-15 13:28:39 +00:00
|
|
|
token_url = 'https://' + os.environ['NEXTCLOUD_HOST'] + '/index.php/apps/oauth2/api/v1/token'
|
2024-01-13 15:31:06 +00:00
|
|
|
|
2024-01-15 11:29:56 +00:00
|
|
|
def get_nextcloud_access_token(refresh_token):
|
2024-01-15 12:43:55 +00:00
|
|
|
debug = 'NEXTCLOUD_DEBUG_OAUTH' in os.environ
|
2024-01-15 11:29:56 +00:00
|
|
|
client_id = os.environ['NEXTCLOUD_CLIENT_ID']
|
|
|
|
client_secret = os.environ['NEXTCLOUD_CLIENT_SECRET']
|
|
|
|
|
|
|
|
code = refresh_token
|
|
|
|
data = {
|
2024-01-15 13:25:57 +00:00
|
|
|
'grant_type': 'refresh_token',
|
2024-01-15 11:29:56 +00:00
|
|
|
'code': code,
|
2024-01-15 11:36:00 +00:00
|
|
|
'refresh_token': refresh_token,
|
2024-01-15 11:29:56 +00:00
|
|
|
'client_id': client_id,
|
|
|
|
'client_secret': client_secret
|
|
|
|
}
|
2024-01-15 13:28:39 +00:00
|
|
|
response = requests.post(token_url, data=data)
|
2024-01-15 12:43:55 +00:00
|
|
|
if debug:
|
|
|
|
print(response.text)
|
2024-01-15 11:29:56 +00:00
|
|
|
return response.json()
|
|
|
|
|
2024-01-12 13:04:13 +00:00
|
|
|
def post_auth_hook(authenticator, handler, authentication):
|
2024-01-13 14:30:12 +00:00
|
|
|
user = authentication['auth_state']['oauth_user']['ocs']['data']['id']
|
2024-01-13 15:31:06 +00:00
|
|
|
auth_state = authentication['auth_state']
|
|
|
|
auth_state['token_expires'] = time.time() + auth_state['token_response']['expires_in']
|
|
|
|
authentication['auth_state'] = auth_state
|
2024-01-13 14:30:12 +00:00
|
|
|
return authentication
|
2024-01-13 15:31:06 +00:00
|
|
|
|
2024-01-12 13:04:13 +00:00
|
|
|
class NextcloudOAuthenticator(GenericOAuthenticator):
|
|
|
|
def __init__(self, *args, **kwargs):
|
2024-01-13 14:30:12 +00:00
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.user_dict = {}
|
|
|
|
|
2024-01-13 15:20:19 +00:00
|
|
|
async def pre_spawn_start(self, user, spawner):
|
2024-01-13 14:30:12 +00:00
|
|
|
super().pre_spawn_start(user, spawner)
|
2024-01-13 15:20:19 +00:00
|
|
|
auth_state = await user.get_auth_state()
|
|
|
|
if not auth_state:
|
|
|
|
return
|
|
|
|
access_token = auth_state['access_token']
|
2024-01-13 14:30:12 +00:00
|
|
|
spawner.environment['NEXTCLOUD_ACCESS_TOKEN'] = access_token
|
2024-01-13 14:30:57 +00:00
|
|
|
|
|
|
|
async def refresh_user(self, user, handler=None):
|
2024-01-15 09:14:07 +00:00
|
|
|
debug = 'NEXTCLOUD_DEBUG_OAUTH' in os.environ
|
2024-01-13 15:20:19 +00:00
|
|
|
auth_state = await user.get_auth_state()
|
|
|
|
if not auth_state:
|
2024-01-15 09:14:07 +00:00
|
|
|
if debug:
|
|
|
|
print(f'auth_state missing for {user}')
|
2024-01-13 15:20:19 +00:00
|
|
|
return False
|
|
|
|
access_token = auth_state['access_token']
|
|
|
|
refresh_token = auth_state['refresh_token']
|
2024-01-13 15:24:28 +00:00
|
|
|
token_response = auth_state['token_response']
|
2024-01-13 15:21:55 +00:00
|
|
|
now = time.time()
|
2024-01-15 09:14:07 +00:00
|
|
|
now_hr = datetime.fromtimestamp(now)
|
2024-01-13 15:24:28 +00:00
|
|
|
expires = auth_state['token_expires']
|
2024-01-15 09:14:07 +00:00
|
|
|
expires_hr = datetime.fromtimestamp(expires)
|
2024-01-15 13:12:17 +00:00
|
|
|
expires = 0
|
2024-01-15 11:07:53 +00:00
|
|
|
if debug:
|
|
|
|
print(f'auth_state for {user}: {auth_state}')
|
2024-01-13 15:21:55 +00:00
|
|
|
if now >= expires:
|
2024-01-15 09:14:07 +00:00
|
|
|
if debug:
|
|
|
|
print(f'Time is: {now_hr}, token expired: {expires_hr}')
|
2024-01-15 11:29:56 +00:00
|
|
|
print(f'Refreshing token for {user}')
|
|
|
|
try:
|
|
|
|
token_response = get_nextcloud_access_token(refresh_token)
|
|
|
|
auth_state['access_token'] = token_response['access_token']
|
|
|
|
auth_state['refresh_token'] = token_response['refresh_token']
|
|
|
|
auth_state['token_expires'] = now + token_response['expires_in']
|
|
|
|
auth_state['token_response'] = token_response
|
|
|
|
if debug:
|
2024-01-15 13:44:33 +00:00
|
|
|
print(f'Successfully refreshed token for {user.name}')
|
|
|
|
print(f'auth_state for {user.name}: {auth_state}')
|
|
|
|
return {'name': user.name, 'auth_state': auth_state}
|
2024-01-15 11:29:56 +00:00
|
|
|
except Exception as e:
|
|
|
|
if debug:
|
|
|
|
print(f'Failed to refresh token for {user}')
|
|
|
|
return False
|
2024-01-13 14:47:22 +00:00
|
|
|
return False
|
2024-01-15 09:14:07 +00:00
|
|
|
if debug:
|
|
|
|
print(f'Time is: {now_hr}, token expires: {expires_hr}')
|
2024-01-13 15:21:55 +00:00
|
|
|
return True
|
2024-01-13 14:30:57 +00:00
|
|
|
|
2024-01-12 13:04:13 +00:00
|
|
|
c.JupyterHub.authenticator_class = NextcloudOAuthenticator
|
|
|
|
c.NextcloudOAuthenticator.client_id = os.environ['NEXTCLOUD_CLIENT_ID']
|
|
|
|
c.NextcloudOAuthenticator.client_secret = os.environ['NEXTCLOUD_CLIENT_SECRET']
|
|
|
|
c.NextcloudOAuthenticator.login_service = 'Sunet Drive'
|
|
|
|
c.NextcloudOAuthenticator.username_claim = lambda r: r.get('ocs', {}).get('data', {}).get('id')
|
|
|
|
c.NextcloudOAuthenticator.userdata_url = 'https://' + os.environ['NEXTCLOUD_HOST'] + '/ocs/v2.php/cloud/user?format=json'
|
|
|
|
c.NextcloudOAuthenticator.authorize_url = 'https://' + os.environ['NEXTCLOUD_HOST'] + '/index.php/apps/oauth2/authorize'
|
2024-01-15 13:28:39 +00:00
|
|
|
c.NextcloudOAuthenticator.token_url = token_url
|
2024-01-12 13:04:13 +00:00
|
|
|
c.NextcloudOAuthenticator.oauth_callback_url = 'https://' + os.environ['JUPYTER_HOST'] + '/hub/oauth_callback'
|
|
|
|
c.NextcloudOAuthenticator.allow_all = True
|
|
|
|
c.NextcloudOAuthenticator.refresh_pre_spawn = True
|
|
|
|
c.NextcloudOAuthenticator.enable_auth_state = True
|
2024-01-15 14:03:35 +00:00
|
|
|
c.NextcloudOAuthenticator.auth_refresh_age = 3600
|
2024-01-12 13:04:13 +00:00
|
|
|
c.NextcloudOAuthenticator.post_auth_hook = post_auth_hook
|
|
|
|
|
2024-01-15 14:03:35 +00:00
|
|
|
serviceCode: |
|
2024-01-15 15:03:11 +00:00
|
|
|
import sys
|
|
|
|
c.JupyterHub.load_roles = [
|
|
|
|
{
|
|
|
|
"name": "refresh-token",
|
2024-01-15 16:26:54 +00:00
|
|
|
"services": [
|
|
|
|
"refresh-token"
|
2024-01-15 16:28:53 +00:00
|
|
|
],
|
2024-01-15 15:03:11 +00:00
|
|
|
"scopes": [
|
|
|
|
"admin:auth_state"
|
|
|
|
]
|
2024-01-15 15:42:31 +00:00
|
|
|
},
|
2024-01-15 15:39:54 +00:00
|
|
|
{
|
2024-01-15 15:55:11 +00:00
|
|
|
"name": "user",
|
2024-01-15 15:42:31 +00:00
|
|
|
"scopes": [
|
2024-01-15 16:26:54 +00:00
|
|
|
"access:services!service=refresh-token",
|
|
|
|
"read:services!service=refresh-token",
|
2024-01-15 16:05:37 +00:00
|
|
|
"self",
|
2024-01-15 15:42:31 +00:00
|
|
|
],
|
2024-01-16 08:32:21 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"name": "server",
|
|
|
|
"scopes": [
|
|
|
|
"access:services!service=refresh-token",
|
|
|
|
"read:services!service=refresh-token",
|
|
|
|
"inherit",
|
|
|
|
],
|
2024-01-15 15:39:54 +00:00
|
|
|
}
|
2024-01-15 15:03:11 +00:00
|
|
|
]
|
|
|
|
c.JupyterHub.services = [
|
|
|
|
{
|
|
|
|
'name': 'refresh-token',
|
2024-01-16 11:37:12 +00:00
|
|
|
'url': 'http://0.0.0.0:8082',
|
2024-01-15 15:06:46 +00:00
|
|
|
'command': [sys.executable, '/usr/local/etc/jupyterhub/refresh-token.py']
|
2024-01-15 15:03:11 +00:00
|
|
|
}
|
|
|
|
]
|
2024-01-16 11:37:12 +00:00
|
|
|
service:
|
|
|
|
extraPorts:
|
|
|
|
- name: refresh-token8082
|
|
|
|
port: 8082
|
|
|
|
targetPort: 8082
|
2024-01-16 11:33:58 +00:00
|
|
|
extraFiles:
|
|
|
|
refresh-token.py:
|
|
|
|
mountPath: /usr/local/etc/jupyterhub/refresh-token.py
|
|
|
|
stringData: |
|
|
|
|
"""A token refresh service authenticating with the Hub.
|
|
|
|
|
|
|
|
This service serves `/services/refresh-token/`,
|
|
|
|
authenticated with the Hub,
|
|
|
|
showing the user their own info.
|
|
|
|
"""
|
|
|
|
import json
|
|
|
|
import os
|
|
|
|
from urllib.parse import urlparse
|
|
|
|
|
|
|
|
from tornado.httpserver import HTTPServer
|
|
|
|
from tornado.ioloop import IOLoop
|
|
|
|
from tornado.web import Application, RequestHandler, authenticated
|
|
|
|
|
|
|
|
from jupyterhub.services.auth import HubAuthenticated
|
|
|
|
|
|
|
|
|
|
|
|
class RefreshHandler(HubAuthenticated, RequestHandler):
|
|
|
|
def initialize(self, hub_auth):
|
|
|
|
self.hub_auth = hub_auth
|
|
|
|
|
|
|
|
@authenticated
|
|
|
|
def get(self):
|
|
|
|
user_model = self.get_current_user()
|
|
|
|
self.set_header('content-type', 'application/json')
|
|
|
|
self.write(json.dumps(user_model, indent=1, sort_keys=True))
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
prefix = os.environ.get('JUPYTERHUB_SERVICE_PREFIX', '/')
|
|
|
|
app = Application(
|
|
|
|
[
|
|
|
|
(prefix, RefreshHandler),
|
|
|
|
(r'.*', RefreshHandler),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
http_server = HTTPServer(app)
|
|
|
|
url = urlparse(os.environ['JUPYTERHUB_SERVICE_URL'])
|
|
|
|
|
|
|
|
http_server.listen(url.port, url.hostname)
|
|
|
|
|
|
|
|
IOLoop.current().start()
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|
|
|
|
|
2023-05-03 09:19:31 +00:00
|
|
|
extraEnv:
|
2024-01-15 09:14:07 +00:00
|
|
|
NEXTCLOUD_DEBUG_OAUTH: "yes"
|
2023-05-03 09:19:31 +00:00
|
|
|
NEXTCLOUD_HOST: sunet.drive.test.sunet.se
|
|
|
|
JUPYTER_HOST: jupyter.drive.test.sunet.se
|
2024-01-13 15:09:47 +00:00
|
|
|
JUPYTERHUB_CRYPT_KEY:
|
|
|
|
valueFrom:
|
|
|
|
secretKeyRef:
|
|
|
|
name: jupyterhub-secrets
|
|
|
|
key: crypt-key
|
2023-05-03 09:19:31 +00:00
|
|
|
NEXTCLOUD_CLIENT_ID:
|
|
|
|
valueFrom:
|
|
|
|
secretKeyRef:
|
|
|
|
name: nextcloud-oauth-secrets
|
|
|
|
key: client-id
|
|
|
|
NEXTCLOUD_CLIENT_SECRET:
|
|
|
|
valueFrom:
|
|
|
|
secretKeyRef:
|
|
|
|
name: nextcloud-oauth-secrets
|
|
|
|
key: client-secret
|
2024-01-11 08:27:05 +00:00
|
|
|
networkPolicy:
|
|
|
|
enabled: false
|
2024-01-10 12:45:44 +00:00
|
|
|
|
2023-05-03 09:19:31 +00:00
|
|
|
singleuser:
|
|
|
|
image:
|
|
|
|
name: docker.sunet.se/drive/jupyter-custom
|
2024-01-12 15:07:42 +00:00
|
|
|
tag: lab-4.0.10
|
2023-05-03 09:19:31 +00:00
|
|
|
storage:
|
2024-01-12 15:12:57 +00:00
|
|
|
dynamic:
|
|
|
|
storageClass: csi-sc-cinderplugin
|
2023-05-03 09:19:31 +00:00
|
|
|
extraEnv:
|
|
|
|
JUPYTER_ENABLE_LAB: "yes"
|
|
|
|
extraFiles:
|
|
|
|
jupyter_notebook_config:
|
|
|
|
mountPath: /home/jovyan/.jupyter/jupyter_server_config.py
|
|
|
|
stringData: |
|
|
|
|
import os
|
|
|
|
c = get_config()
|
|
|
|
c.NotebookApp.allow_origin = '*'
|
|
|
|
c.NotebookApp.tornado_settings = {
|
|
|
|
'headers': { 'Content-Security-Policy': "frame-ancestors *;" }
|
|
|
|
}
|
|
|
|
os.system('/usr/local/bin/nc-sync')
|
|
|
|
mode: 0644
|