-
Notifications
You must be signed in to change notification settings - Fork 1
/
body.yaml
216 lines (181 loc) · 10.4 KB
/
body.yaml
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
00-auth-config: |
import json
import uuid
import jwt
from urllib.parse import urlparse, parse_qs
from traitlets import Unicode, observe
'''
A Hub which instantiates a new anonymous session for a user given an image. The intended use case for this is a Jupyter window in an iframe
which contains instructional material. The image name and the secret for instantiating an image are passed in as parameters in the URL.
'''
from tmpauthenticator import TmpAuthenticator, TmpAuthenticateHandler
from tornado import web
# The basics are an extension of TmpAuthenticator and TmpAuthenticatorHandler to check the secret.
# If the secret is not present or wrong, throw a 403; otherwise mirror the behavior of TmpAuthenticator
# Also supports choosing an image.
class TmpSecretAuthenticateHandler(TmpAuthenticateHandler):
"""
Clone of TmpAuthenticateHandler, except we handle the case where self.login_user returns None.
For the base class, see: https://github.com/jupyterhub/tmpauthenticator/blob/main/tmpauthenticator/__init__.py
"""
async def get(self):
print('In TmpSecretAuthenticateHandler')
self.clear_login_cookie() # force a new login
user = await self.login_user(None)
# This is the only new code from TmpAuthenticatorHandler
if user is None:
raise web.HTTPError(403)
# the rest of the code is TmpAuthenticatorHandler.get
self.set_hub_cookie(user)
next_url = self.get_next_url(user)
self.redirect(next_url)
class TmpSecretAuthenticator(TmpAuthenticator):
"""
Clone of TmpAuthenticator, except we only start a new session when a secret is passed in, present, and correct.
Also add a class which chooses the image based on what is passed in.
For the base class, see: https://github.com/jupyterhub/tmpauthenticator/blob/main/tmpauthenticator/__init__.py
"""
token_secret = Unicode(help =
'''
A secret to decode the JWT which is used to pass configuration variables in an authenticated
way. This should be configured with c.TmpSecretAuthenticator.token_secret
'''
).tag(config=True)
@observe("token_secret")
def _observe_token_secret(self, change):
print(f'token_secret = {change["new"]}')
def _assign_configuration(self, parm_dict):
# Choose an image based on the 'image' parameter passed in in the URL. parm_dict is a dictionary generated by
# parse_qs(urlparse.query) where parse_qs and urlparse are both in the standard library urllib.parse
# the image dictionary and default_image should be in an offboard file.
configurations = {
"fds": {"image": "gcr.io/ezczstat/foundationz:1.0.0", "url": ""},
"tidy-i": {"image": "gcr.io/ezczstat/breezing-jlab:1.0.2", "url": "/rstudio"},
"shiny-iii": {"image": "gcr.io/ezczstat/widgetz:1.0.2", "url": "/rstudio"},
"shiny-ii": {"image": "gcr.io/ezczstat/shiny:1.0.2", "url": "/rstudio"},
"stan-i": {"image": "gcr.io/ezczstat/bayesian-jlab:1.1.0", "url": "/rstudio"},
"tools-and-tech": {"image": "gcr.io/ezczstat/tools-python:1.0.1", "url": ""},
"rstudio": {"image": "engagelively/el-jupyter:rstudio2.2.2", "url": "/rstudio"},
"rstudio-test-iframe": {"image": "engagelively/test-rstudio", "url": "/rstudio"},
"data_science": {"image": "engagelively/el-jupyter:datascience2.2.2", "url": ""},
"tensor_flow": {"image": "engagelively/el-jupyter:tensorflow2.2.2", "url": ""},
"spark": {"image": "engagelively/el-jupyter:allspark2.2.2", "url": ""},
"base": {"image": "engagelively/el-jupyter:base2.2.2", "url": ""},
"scipy": {"image": "engagelively/el-jupyter:scipy2.2.2", "url": ""},
"default": {"image": "quay.io/jupyterhub/singleuser:latest", "url": ""}
}
# return the selected config if there is one or the default
selected_config_key = parm_dict['config'][0] if 'config' in parm_dict else "default"
return configurations[selected_config_key]
async def authenticate(self, handler, data):
"""
Authenticate a new user when secret is present in the query string and is identical to my_secret.
Note that secret and my_secret should be in an offline, not stored file.
When secret is present and correct, slways authenticate a new user by generating a universally unique
identifier (uuid). (Identical to tmp_authenticator)
"""
self.auth_refresh_age = 3600
# New code. parse the URL and get the query dictionary.
parsed_url = urlparse(handler.get_next_url())
parm_dict = parse_qs(parsed_url.query)
print(parm_dict)
if 'jwt' in parm_dict:
token = parm_dict['jwt'][0]
try:
encoded = jwt.decode(token, self.token_secret, algorithms=["HS256"])
data = encoded['data'] # check for KeyError
print(data)
user = data["user"]
return {
"name": str(uuid.uuid4()),
"auth_state": {
"orig_user": user,
"token_data": data
}
}
except jwt.exceptions.InvalidSignatureError:
print(f'Decode of {token} failed using secret {self.token_secret}')
encoded = 'no jwt'
return None
else:
encoded = "no jwt"
return None
async def refresh_user(self, user, handler):
# We should always refresh -- no session history for this application
# False indicates that the user's data has expired. See:
# https://github.com/jupyterhub/jupyterhub/blob/17aee17c5f6a949038cc9bed8c64dad8b2b1831c/jupyterhub/auth.py#L429-L457
return False
def get_handlers(self, app):
"""
Registers a dedicated endpoint and web request handler for logging in
with TmpAuthenticator. This is needed as /hub/login is reserved for
redirecting to what's returned by login_url.
ref: https://github.com/jupyterhub/jupyterhub/pull/1066
Code is identical to TmpAuthenticator but it uses TmpSecretAuthenticator
"""
return [("/tmplogin", TmpSecretAuthenticateHandler)]
# Set the authenticator
c.JupyterHub.authenticator_class = TmpSecretAuthenticator
def token_data_hook(spawner, auth_state):
spawner.token_data = auth_state["token_data"]
c.Spawner.auth_state_hook = token_data_hook
class ProfileAssigner:
'''
Choose an profile based on the 'config' parameter passed encoded in the JWT in the URL. token_data is what was passed in
encoded in the jwt and has a field 'config' which is the key for the profile. This just returns the selected profile
from the passed config_key
'''
def __init__(self):
'''
profiles is the dictionary of profiles we maintain. Each profile has an image (mandatory, no default) which is a
Docker container image, andan optional url parameter (default "")
'''
self.profiles = {
"fds": {"image": "gcr.io/ezczstat/foundationz:1.0.0", "url": ""},
"tidy-i": {"image": "gcr.io/ezczstat/breezing-jlab:1.0.2", "url": "/rstudio"},
"shiny-iii": {"image": "gcr.io/ezczstat/widgetz:1.0.2", "url": "/rstudio"},
"shiny-ii": {"image": "gcr.io/ezczstat/shiny:1.0.2", "url": "/rstudio"},
"stan-i": {"image": "gcr.io/ezczstat/bayesian-jlab:1.1.0", "url": "/rstudio"},
"tools-and-tech": {"image": "gcr.io/ezczstat/tools-python:1.0.1", "url": ""},
"rstudio": {"image": "engagelively/el-jupyter:rstudio2.2.2", "url": "/rstudio"},
"rstudio-test-iframe": {"image": "engagelively/test-rstudio", "url": "/rstudio"},
"data_science": {"image": "engagelively/el-jupyter:datascience2.2.2", "url": ""},
"tensor_flow": {"image": "engagelively/el-jupyter:tensorflow2.2.2", "url": ""},
"spark": {"image": "engagelively/el-jupyter:allspark2.2.2", "url": ""},
"base": {"image": "engagelively/el-jupyter:base2.2.2", "url": ""},
"scipy": {"image": "engagelively/el-jupyter:scipy2.2.2", "url": ""},
"default": {"image": "quay.io/jupyterhub/singleuser:latest", "url": ""}
}
def get_profile(self, token_data):
'''
Find the key from token_data (in field 'config') and return the selected profile. Return the default profile if there is no key
'''
selected_config_key = token_data['config'] if 'config' in token_data else "default"
return self.choose_profile(selected_config_key)
def choose_profile(self, profile_name):
'''
Return the profile from profile_name if it exists, the default profile if it doesn't
'''
return self.profiles[profile_name] if profile_name in self.profiles else self.profiles['default']
def spawn_get_config_hook(spawner):
# print('In pre-spawn hook')
'''
The pre-spawn hook. If there is a spawner, and it has a token_data attribute (the authenticator should have created it
and passed it to the spawner), get the profile from token_data; if not the default is used. Then set the spawner's image and url
to the profile image and URL in the profile. If the profile has a cmd, set the spawner's cmd to that
'''
profile_chooser = ProfileAssigner()
profile = profile_chooser.choose_profile('default')
if spawner is not None:
if hasattr(spawner, 'token_data'):
# print(f'token data: {spawner.token_data}')
profile = profile_chooser.get_profile(spawner.token_data)
# else:
# print('no token data')
# print(json.dumps(profile))
spawner.image = profile["image"]
spawner.default_url = profile["url"]
if "cmd" in profile:
spawner.cmd = profile["cmd"]
# print({"image":spawner.image, "url": spawner.default_url, "cmd": spawner.cmd})
c.Spawner.pre_spawn_hook = spawn_get_config_hook