-
Notifications
You must be signed in to change notification settings - Fork 354
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Apply TLJH auth config with less assumptions #721
Changes from 4 commits
5c8e567
a32ec5e
ea72021
efb8ee0
0cd6b2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -149,27 +149,53 @@ def update_base_url(c, config): | |
|
||
def update_auth(c, config): | ||
""" | ||
Set auth related configuration from YAML config file | ||
|
||
Use auth.type to determine authenticator to use. All parameters | ||
in the config under auth.{auth.type} will be passed straight to the | ||
authenticators themselves. | ||
Set auth related configuration from YAML config file. | ||
|
||
As an example, this function should update the following TLJH auth | ||
configuration: | ||
|
||
```yaml | ||
auth: | ||
type: oauthenticator.github.GitHubOAuthenticator | ||
GitHubOAuthenticator: | ||
client_id: "..." | ||
client_secret: "..." | ||
oauth_callback_url: "..." | ||
ClassName: | ||
arbitrary_key: "..." | ||
arbitrary_key_with_none_value: | ||
``` | ||
|
||
by applying the following configuration: | ||
|
||
```python | ||
c.JupyterHub.authenticator_class = "oauthenticator.github.GitHubOAuthenticator" | ||
c.GitHubOAuthenticator.client_id = "..." | ||
c.GitHubOAuthenticator.client_secret = "..." | ||
c.GitHubOAuthenticator.oauth_callback_url = "..." | ||
c.ArbitraryKey.arbitrary_key = "..." | ||
``` | ||
|
||
Note that "auth.type" and "auth.ArbitraryKey.arbitrary_key_with_none_value" | ||
are treated a bit differently. auth.type will always map to | ||
c.JupyterHub.authenticator_class and any configured value being None won't | ||
be set. | ||
""" | ||
auth = config.get('auth') | ||
tljh_auth_config = config['auth'] | ||
|
||
# FIXME: Make sure this is something importable. | ||
# FIXME: SECURITY: Class must inherit from Authenticator, to prevent us being | ||
# used to set arbitrary properties on arbitrary types of objects! | ||
authenticator_class = auth['type'] | ||
# When specifying fully qualified name, use classname as key for config | ||
authenticator_configname = authenticator_class.split('.')[-1] | ||
c.JupyterHub.authenticator_class = authenticator_class | ||
# Use just class name when setting config. If authenticator is dummyauthenticator.DummyAuthenticator, | ||
# its config will be set under c.DummyAuthenticator | ||
authenticator_parent = getattr(c, authenticator_class.split('.')[-1]) | ||
|
||
for k, v in auth.get(authenticator_configname, {}).items(): | ||
set_if_not_none(authenticator_parent, k, v) | ||
# FIXME: SECURITY: Class must inherit from Authenticator, to prevent us | ||
# being used to set arbitrary properties on arbitrary types of objects! | ||
c.JupyterHub.authenticator_class = tljh_auth_config['type'] | ||
|
||
for auth_key, auth_value in tljh_auth_config.items(): | ||
if not (auth_key[0] == auth_key[0].upper() and isinstance(auth_value, dict)): | ||
continue | ||
minrk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
class_name = auth_key | ||
class_config_to_set = auth_value | ||
class_config = c[class_name] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This results in the following errors in our test suite, perhaps because we have a class that isn't inheriting from a traitlets class to make it configurable?
It would work to use
What is our action point to fix this?
I figure the latter is to be preferred then, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've resolved this by adding a getitem function to the MockConfigurer class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why we have a MockConfigurer that's only used in tests, instead of testing with a Config object. It doesn't seem to serve any purpose, other than making the tests less like what's being tested, for instance this case where an error is raised in tests when there is no error in real code. |
||
for config_name, config_value in class_config_to_set.items(): | ||
set_if_not_none(class_config, config_name, config_value) | ||
|
||
|
||
def update_userlists(c, config): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the back-and-forth, @consideRatio, but this FIXME is already fixed by the use of traitlets config and I think it should be removed, as it gives a false impression that there's something security-related to fix. The only things that can be set here are attributes that:
config=True
Any attempt to configure something else here will be ignored.
traitlets config cannot in any way "set arbitrary properties on arbitrary types of objects"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this isn't a regression in this PR, happy to merge this as-is when tests pass, and remove the inaccurate FIXMEs in a single PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great to know, @minrk! I wasn't sure if that was handled by traitlets or if we were using setattr, and this is pretty awesome.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we have some setattr, but it's always on the Config object itself where setattr is really a proxy for setitem in a dict-of-dicts that doesn't do anything on its own. Traitlets config loading then handles the safety of that, by only loading recognized values from the Config object.