Skip to content

Allow user to update identity values #1518

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

brichet
Copy link
Contributor

@brichet brichet commented Apr 29, 2025

Fixes #1514

This PR adds a new PATCH method to the api handler, to allow users to update their identity values. This handler is associated to a update_user method in the IdentityProvider, to effectively change the values.

The fields that can be updated is an attribute of the IdentityProvider class, and can contain all the attributes of User except username. By default only the color can be updated.

The PasswordIdentityProvider, which is the default identity provider, allows updating all the fields (except the username).

setattr(current_user, field, user_data[field])

# Persist changes (if applicable)
self.set_login_cookie(handler, current_user) # Save updated user to cookie/session
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation assumes that get_user is not overridden, which is the main thing IdentityProvider subclasses (e.g. JupyterHub) will do.

The result of this change as-is for JupyterHub will be that:

  • the PATCH request succeeds
  • an unused cookie is set
  • the user model returned by /api/me is not actually changed

Probably the right thing to happen for JupyterHub which overrides get_user but not this method (yet) is to hit a NotImplementedError. I see two ways to go about that:

  1. move this default implementation into the PasswordIdentityProvider, leaving the base class with NotImplementedError
  2. split the last set_login_cookie step to a persist_user_model method, so the field validation and user model persistence don't have to be overridden at the same time

This method really does 3 things:

  1. validate keys (override via config works as you have it, base class can define this and it should work for all subclasses)
  2. validate values (not currently possible without full override of update_user)
  3. persist changes (not currently possible with full override of update_user)

I think keeping it as a single method is okay, but that means the base class shouldn't have an implementation of it by default (It can still have it as a method, so subclasses can opt-in to default behavior). But if you want to slice it up so e.g. the key validation happens outside the typically overridden field, e.g.

def _update_user(self, ...):
    # check updatable_fields
    # only call update_user after validating
    return self.update_user(...) # responsible for persistence, possibly _value_ validation

that would reduce the duplication required by subclasses. If you want, validate_update_user could also be an overridable method.

raise web.HTTPError(500, "Identity provider not configured properly")

try:
updated_user = identity_provider.update_user(self, user_data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably pass self.current_user here rather than self, so it's not part of the update_user API how we store the current user on the Handler. Then pass `self on the end for IdentityProviders that need to use Handler for persistence (some won't, e.g. JupyterHub).

@brichet
Copy link
Contributor Author

brichet commented Apr 29, 2025

Thanks @minrk for the feedbacks.

To summarize, would it works if :

  • we add a check_update() method to IdentityProvider that would check the fields
  • we add a update_user() method, that is not implemented in IdentityProvider and overridden in PasswordIdentityProvider to actually update the user fields (if the default implementation doesn't break Jupyterhub, it can also be implemented in IdentityProvider directly)
  • we add a persist_user_model() that is not implemented in IdentityProvider and overridden in PasswordIdentityProvider to call set_login_cookie()

The 3 methods would be called in a try/except in the patch handler.
That way it would work out of the box with the PasswordIdentityProvider, but update_user() and persist_user_model() would need to be overridden for other providers (e.g. Jupyterhub).

Is there any use case for this feature in Jupyterhub ? Probably updatable_fields would have to be redefined to match the Jupyterhub user fields.

@minrk
Copy link
Contributor

minrk commented Apr 30, 2025

That sounds like a good plan!

Is there any use case for this feature in Jupyterhub ?

I think it makes sense to allow setting the fields JupyterHub doesn't understand. What's going to slow down the implementation is that JupyterHub needs to store this info somewhere. As it is now, JupyterHubIdentityProvider only persists a token in the cookie, and then fetches the whole user model from JupyterHub (because it may be updated). There isn't an obvious place to put this info, so I'll have to think about it. But as long as we get the behavior that it appropriately doesn't work in the meantime while it's not supported, that works for me.

I opened jupyterhub/jupyterhub#5061 for tracking the JupyterHub implementation of this feature.

@brichet brichet force-pushed the allow_user_to_update_fields branch from e00ae8d to 7d49971 Compare April 30, 2025 20:33
@brichet brichet force-pushed the allow_user_to_update_fields branch from 7d49971 to 93385a9 Compare April 30, 2025 20:34
@brichet
Copy link
Contributor Author

brichet commented Apr 30, 2025

I updated the PR as described above.

It keeps one main update_user() method, that calls check_update(), update_user_model() and persist_user_model().
The 2 last are only implemented in PasswordIdentityProvider, not in IdentityProvider.

@brichet
Copy link
Contributor Author

brichet commented May 6, 2025

As suggested by @krassowski, there is probably no need for adding a new field in the user model.
Instead, we can use the permissions fields.

9d1060d adds a updatable_fields attribute to the permissions dictionary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allowing users to update their name
2 participants