@@ -76,6 +76,12 @@ basehub:
76
76
77
77
return False
78
78
79
+ async def authenticate(self, *args, **kwargs):
80
+ resp = await super().authenticate(*args, **kwargs)
81
+ # Set scope to groups
82
+ resp["groups"] = resp["auth_state"]["scope"]
83
+ return resp
84
+
79
85
def populate_token(spawner, auth_state):
80
86
# For our deployment-service-check health check user, there is no auth_state.
81
87
# So these env variables need not be set.
@@ -89,7 +95,75 @@ basehub:
89
95
90
96
c.Spawner.auth_state_hook = populate_token
91
97
98
+ c.GenericOAuthenticator.manage_groups = True
99
+
92
100
c.JupyterHub.authenticator_class = CustomGenericOAuthenticator
101
+
102
+ 05-gh-teams : |
103
+ # Filters profileList based on group membership of JupyterHubs
104
+ import copy
105
+
106
+ from textwrap import dedent
107
+ from tornado import web
108
+ from oauthenticator.github import GitHubOAuthenticator
109
+
110
+ original_profile_list = c.KubeSpawner.profile_list
111
+
112
+ async def profile_list_allowed_groups_filter(spawner):
113
+ """
114
+ Returns the initially configured profile_list filtered based on the
115
+ user's membership in each profile's `allowed_groups`. If
116
+ `allowed_groups` isn't set for a profile, its not filtered out.
117
+
118
+ `allowed_groups` is a list of JupyterHub groups.
119
+
120
+ If the returned profile_list is filtered to not include a profile,
121
+ an error is raised and the user isn't allowed to start a server.
122
+ """
123
+ if spawner.user.name == "deployment-service-check":
124
+ print("Ignoring allowed_teams check for deployment-service-check")
125
+ return original_profile_list
126
+
127
+ groups = {g.name.casefold() for g in spawner.user.groups}
128
+ print(f"User {spawner.user.name} is part of groups {groups}")
129
+
130
+ # Filter out profiles with allowed_groups set if the user isn't part of any.
131
+ allowed_profiles = []
132
+ for profile in copy.deepcopy(original_profile_list):
133
+ allowed_groups = set(profile.get("allowed_groups"))
134
+ if allowed_groups is None:
135
+ # If no allowed_groups are set, allow access to everything
136
+ allowed_profiles.append(profile)
137
+ continue
138
+
139
+ if allowed_groups & groups:
140
+ print(f"Allowing profile {profile['display_name']} for user {spawner.user.name} based on group membership")
141
+ allowed_profiles.append(profile)
142
+ continue
143
+
144
+ if len(allowed_profiles) == 0:
145
+ # If no profiles are allowed, user should not be able to spawn anything!
146
+ # If we don't explicitly stop this, user will be logged into the 'default' settings
147
+ # set in singleuser, without any profile overrides. Not desired behavior
148
+ # FIXME: User doesn't actually see this error message, just the generic 403.
149
+ error_msg = dedent(f"""
150
+ Your Group team membership is insufficient to launch any server profiles.
151
+
152
+ GitHub teams you are a member of that this JupyterHub knows about are {', '.join(groups)}.
153
+
154
+ If you are part of additional teams, log out of this JupyterHub and log back in to refresh that information.
155
+ """)
156
+ raise web.HTTPError(403, error_msg)
157
+
158
+ return allowed_profiles
159
+
160
+ # Only set this customized profile_list *if* we already have a profile_list set
161
+ # otherwise, we'll show users a blank server options form and they won't be able to
162
+ # start their server
163
+ if c.KubeSpawner.profile_list:
164
+ # Customize list of profiles dynamically, rather than override options form.
165
+ # This is more secure, as users can't override the options available to them via the hub API
166
+ c.KubeSpawner.profile_list = profile_list_allowed_groups_filter
93
167
config :
94
168
# JupyterHub:
95
169
# authenticator_class: auth0
@@ -110,7 +184,7 @@ basehub:
110
184
username_claim : sub
111
185
# Convert 'scope' from the OAuth2 response into JupyterHub groups
112
186
manage_groups : true
113
- claim_groups_key : ' scope'
187
+ # claim_groups_key: 'scope'
114
188
CILogonOAuthenticator :
115
189
allowed_idps :
116
190
http://github.com/login/oauth/authorize :
@@ -129,6 +203,10 @@ basehub:
129
203
profileList :
130
204
- display_name : " Shared Small: 1-4 CPU, 8-32 GB"
131
205
description : " A shared machine, the recommended option until you experience a limitation."
206
+ allowed_groups :
207
+ - geolab
208
+ - geolab:dev
209
+ - geolab:power
132
210
profile_options : &profile_options
133
211
image :
134
212
display_name : Image
@@ -163,30 +241,36 @@ basehub:
163
241
mem_limit : null
164
242
node_selector :
165
243
node.kubernetes.io/instance-type : r5.xlarge
166
-
167
244
- display_name : " Small: 4 CPU, 32 GB"
168
245
description : " A dedicated machine for you."
246
+ allowed_groups :
247
+ - geolab
248
+ - geolab:dev
249
+ - geolab:power
169
250
profile_options : *profile_options
170
251
kubespawner_override :
171
252
mem_guarantee : 28.937G
172
253
cpu_guarantee : 0.4
173
254
mem_limit : null
174
255
node_selector :
175
256
node.kubernetes.io/instance-type : r5.xlarge
176
-
177
257
- display_name : " Medium: 16 CPU, 128 GB"
178
258
description : " A dedicated machine for you."
179
259
profile_options : *profile_options
260
+ allowed_groups :
261
+ - geolab:dev
262
+ - geolab:power
180
263
kubespawner_override :
181
264
mem_guarantee : 120.513G
182
265
cpu_guarantee : 1.6
183
266
mem_limit : null
184
267
node_selector :
185
268
node.kubernetes.io/instance-type : r5.4xlarge
186
-
187
269
- display_name : " Large: 64 CPU, 512 GB"
188
270
description : " A dedicated machine for you"
189
271
profile_options : *profile_options
272
+ allowed_groups :
273
+ - geolab:power
190
274
kubespawner_override :
191
275
mem_guarantee : 489.13G
192
276
cpu_guarantee : 6.4
0 commit comments