Skip to content

Commit

Permalink
[POC] Auth aware profile list
Browse files Browse the repository at this point in the history
  • Loading branch information
npapapietro committed Feb 8, 2023
1 parent 21642eb commit d098fe9
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ repos:

# Autoformat: Python code
- repo: https://github.com/pycqa/isort
rev: 5.11.4
rev: 5.11.5
hooks:
- id: isort

Expand Down
61 changes: 55 additions & 6 deletions kubespawner/spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import string
import sys
import warnings
from functools import partial, wraps
from functools import lru_cache, partial, wraps
from urllib.parse import urlparse

import escapism
Expand Down Expand Up @@ -1616,6 +1616,8 @@ def _validate_image_pull_secrets(self, proposal):
and value can be either the final value or a callable that returns the final
value when called with the spawner instance as the only parameter. The callable
may be async.
- `oauthenticator_override` in the profile will allow certain profiles to be set
based on specific OAuthenticator instance. Top level override
- `default`: (optional Bool) True if this is the default selected option
kubespawner setting overrides work in the following manner, with items further in the
Expand All @@ -1626,6 +1628,8 @@ def _validate_image_pull_secrets(self, proposal):
3. `kubespawner_override` in the specific choices the user has made within the
profile, applied linearly based on the ordering of the option in the profile
definition configuration
4. `oauthenticator_override` in the profile will allow certain profiles to be set
based on specific OAuthenticator instance.
Example::
Expand Down Expand Up @@ -1698,6 +1702,9 @@ def _validate_image_pull_secrets(self, proposal):
'cpu_limit': 48,
'mem_limit': '96G',
'extra_resource_guarantees': {"nvidia.com/gpu": "2"},
},
'oauthenticator_override': {
'allowed_groups': ['gpu_user', 'ml_engineers']
}
}
]
Expand Down Expand Up @@ -2927,10 +2934,55 @@ def _render_options_form(self, profile_list):
return profile_form_template.render(profile_list=self._profile_list)

async def _render_options_form_dynamically(self, current_spawner):
profile_list = await maybe_future(self.profile_list(current_spawner))
if callable(self.profile_list):
profile_list = await maybe_future(self.profile_list(current_spawner))
profile_list = self._init_profile_list(profile_list)
# protect non oauthenticator instances
if all(
(
hasattr(self.authenticator, 'enable_auth_state'),
hasattr(self.authenticator, 'user_is_authorized'),
self.authenticator.enable_auth_state,
)
):
profile_list = await self._filter_profile_options_form(profile_list)
return self._render_options_form(profile_list)

async def _filter_profile_options_form(self, profile_list):
@lru_cache
async def check_auth_overrides(auth_state, oauthenticator_overrides=None):
# only check if overrides are present
if oauthenticator_overrides:
return await self.authenticator.user_is_authorized(
auth_state, **oauthenticator_overrides
)
return True

auth_profile_list = []

auth_state = await self.user.get_auth_state()
for profile in profile_list:
# top level oauthenticator_override should take precendent
if not await check_auth_overrides(
auth_state, profile.pop("oauthenticator_override", None)
):
continue

profile_options = {}
for pk, pv in profile.pop("profile_options", {}).items():
# filter out choices on profile_options
profile_options[pk]["choices"] = {
ck: cv
for ck, cv in pv.pop("choices", {}).items()
if await check_auth_overrides(
auth_state, cv.pop("oauthenticator_override", None)
)
}

profile["profile_options"] = profile_options
auth_profile_list.append(profile)
return auth_profile_list

@default('options_form')
def _options_form_default(self):
"""
Expand All @@ -2942,10 +2994,7 @@ def _options_form_default(self):
"""
if not self.profile_list:
return ''
if callable(self.profile_list):
return self._render_options_form_dynamically
else:
return self._render_options_form(self.profile_list)
return self._render_options_form_dynamically

@default('options_from_form')
def _options_from_form_default(self):
Expand Down

0 comments on commit d098fe9

Please sign in to comment.