OIDC RBAC - possible to map users to groups and groups to roles, where users and groups are defined by OIDC?

Hello!

Is it possible to leverage both OIDC (via GenericOAuthenticator) and RBAC at the same time?

And, specifically, is it possible to use RBAC to map groups to roles, and then OIDC (GenericOAuthenticator) to map users to groups?

Here’s what I would expect to accomplish the job:

# Define roles and assign them to groups.
c.JupyterHub.load_roles.append(...)

# Allow OIDC to define user groups.
def compute_groups(claims):
    pass
c.GenericOAuthenticator.claim_groups_key = compute_groups
c.GenericOAuthenticator.manage_groups = True

However as far as I can tell, claim_groups_key is only ever read by get_user_groups, which is only ever read in two scenarios:

  • in check_allowed, if allowed_groups is set to a static list of groups that are allowed in, and
  • in update_auth_model, if a specific set of admin_groups are defined

and both of those scenarios seem to violate the basic principles of RBAC. I’d want my group’s role to be consulted for permissions, not the simple fact of role membership.

And, since I’m not setting allowed_groups or admin_groups, I’m not ever seeing my compute_groups function getting called.

What’s the magic incantation to allow OIDC to define user groups, map groups to roles, and use roles to define permissions?

Edit:

I’ve also tried specifying load_groups and disabling manage_groups. That definitely loads a static group membership map into the database, but then the OIDC GenericOAuthenticator still doesn’t seem to pick up user groups or even invoke claim_groups_key.

# Define roles and assign them to groups.
c.JupyterHub.load_roles.append(...)

# Define static group mapping.
c.JupyterHub.load_groups.append(...)

# Allow OIDC to define user groups.
def compute_groups(claims):
    pass
c.GenericOAuthenticator.claim_groups_key = compute_groups

I’m beginning to think this simply isn’t possible with the current GenericOAuthenticator implementation.

Per these jupyterhub docs, when manage_groups is enabled the authenticator needs to include a groups key in the dictionary returned by authenticate() and refresh_user().

I had to provide a custom GenericOAuthenticator subclass that implemented this behavior.

    async def update_auth_model(self, auth_model):
        auth_model = await super().update_auth_model(auth_model)
        user_info = auth_model["auth_state"][self.user_auth_state_key]
        user_groups = self.get_user_groups(user_info)
        auth_model["groups"] = user_groups
        return auth_model

This works, assuming get_user_groups (via claim_groups_key) behaves as expected!

2 Likes

As you’ve found out OAuthenticator only uses the external group membership to make a binary decision on whether to authorise a user, since this is all that was supported by JupyterHub prior to RBAC. At first glance what you’ve done above looks like the right approach to using OIDC groups in JupyterHub!

1 Like

I was also trying to customise JupyterHub based on group membership defined by OIDC. Raised issue here: