Skip to content
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

[Generic] Add support for manage_groups #708

Merged
merged 5 commits into from
Jan 25, 2024
Merged

Conversation

benjimin
Copy link
Contributor

Addresses #706

Copy link

welcome bot commented Nov 28, 2023

Thanks for submitting your first pull request! You are awesome! 🤗

If you haven't done so already, check out Jupyter's Code of Conduct.
welcome
You can meet the other Jovyans by joining our Discourse forum. There is also a intro thread there where you can stop by and say Hi! 👋

Welcome to the Jupyter community! 🎉

oauthenticator/generic.py Outdated Show resolved Hide resolved
@consideRatio consideRatio changed the title Support GenericOAuth managed groups [Generic] Add support for manage_groups Nov 28, 2023
@minrk minrk added the new label Nov 28, 2023
Copy link
Member

@minrk minrk left a comment

Choose a reason for hiding this comment

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

Great!

This works for me. If anyone has an objection to bumping the minimum hub version to 2.2 (March, 2022), the check can be manage_groups = getattr(self, "manage_groups", False).

@minrk
Copy link
Member

minrk commented Nov 29, 2023

Added test to exercise this, which resulted in fixing a bug in the type of the groups field, and matching #710 in handling pre-hub-2.2 behavior.

@benjimin
Copy link
Contributor Author

benjimin commented Dec 5, 2023

Hmm... something might not be quite right yet..

I just tried testing this, by instrumenting update_auth_model (to log inputs self.manage_groups and auth_model) and get_user_groups (to log inputs self.claim_groups_key and user_info). I then built a container image using a Dockerfile like:

FROM jupyterhub/k8s-hub:3.2.1
USER root
RUN pip uninstall oauthenticator -y && pip install git+https://github.com/benjimin/oauthenticator.git@debug#egg=oauthenticator

I tested this with my AWS Cognito user pool, making sure to set:

c.GenericOAuthenticator.allow_existing_users = True
c.Authenticator.manage_groups = True
c.GenericOAuthenticator.claim_groups_key = "cognito:groups"

When I log in, the pod logs include the message (from generic:147) "The claim_groups_key cognito:groups does not exist in the user token".

(The instrumented logs affirm that manage_groups is True.)

The auth_model has structure:

  • name
  • admin
  • auth_state
    • access_token
    • refresh_token
    • id_token
    • scope
    • token_response
      • id_token (appears identical to above)
      • access_token
      • refresh_token
      • expires_in
      • token_type (="Bearer")
    • oauth_user
      • sub
      • email_verified ('true')
      • email
      • username (identical to sub)

I then decoded the JWT id_token from the instrumented logs. The payload included all the expected claims, such as email, sub (identical to claim cognito:username), and particularly included claims:

  "cognito:groups": [
    "trusted-group",
    "dev-group",
    "internal-group"
  ],
  "email_verified": true,
  "token_use": "id",

(FWIW, I confirmed the cognito:groups claim happens to also be present in the access token as well, although the email-related claims are not present there and it also differs in containing "token_use": "access".)

(In the instrumented logs, user_info appears identical to oauth_user above: a subset of claims from the token but critically omitting cognito:groups.)

So as far as AWS Cognito is concerned, the group membership is definitely available in the OAuth (OIDC?) ID token, but it unfortunately seems to get stripped out from what is passed to get_user_groups.

The oauth_user/user_info dictionary seems to be generated by OAuthenticator.token_to_user (which some of the non-generic authenticators appear to override). Does it ignore the ID token and instead parse JSON from another user-info HTTP endpoint authorised via the access token?

@manics
Copy link
Member

manics commented Dec 5, 2023

user_info is populated from the oauthenticator.OAuthenticator.userdata_url endpoint:

async def token_to_user(self, token_info):
"""
Determines who the logged-in user by sending a "GET" request to
:data:`oauthenticator.OAuthenticator.userdata_url` using the `access_token`.

https://docs.aws.amazon.com/cognito/latest/developerguide/userinfo-endpoint.html

claim_groups_key can be a callable but currently it only has user_info as a parameter

if callable(self.claim_groups_key):
return set(self.claim_groups_key(user_info))

Maybe overriding subclassing and overiding get_user_groups in your config file is the easiest short term solution?

@yuvipanda
Copy link
Collaborator

Just wanted to check in to see if there's anything more to do to move this along

oauthenticator/tests/test_generic.py Outdated Show resolved Hide resolved
oauthenticator/generic.py Outdated Show resolved Hide resolved
@manics manics mentioned this pull request Jan 22, 2024
@manics
Copy link
Member

manics commented Jan 22, 2024

I've opened a PR to bump the minimum JupyterHub version #720
I think it will make this PR a lot clearer!

@manics
Copy link
Member

manics commented Jan 22, 2024

@benjimin Sorry for the back and forth, but we've bumped the minimum version of JupyterHub: #720
If you rebase on main you can assume the manage_groups property always exists.

@manics manics merged commit 5667bf1 into jupyterhub:main Jan 25, 2024
11 checks passed
Copy link

welcome bot commented Jan 25, 2024

Congrats on your first merged pull request in this project! 🎉
congrats
Thank you for contributing, we are very proud of you! ❤️

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

Successfully merging this pull request may close these issues.

4 participants