Programmatically adding roles for collaboration groups for RTC in a custom Authenticator

Hello!

I am working toward a z2jh instance that manages and launches RTC-enabled JupyterLab pods, adapting the typical tutorial.

In order to not have to restart I am integrating the necessary management into my already-existing custom authenticator via authenticator-managed roles, groups, etc rather than code in the helm config like the tutorial.

Nearly everything is working as planned. Upon auth:

  1. Actual user is being created
  2. “Collaboration” users for that user’s associated projects are being created
  3. Groups are being created (both for each project and "collaborative’ group)
  4. Actual users are being added to groups
  5. “Collaboration” users are being added to the collaborative group
  6. Even the RTC websockets are working with the same notebook open by the same user in 2 browser windows

The last thing remaining is having the member users of a group able to spawn and open the “collaborative” user’s pod.

When redirecting the authenticated non-admin user to the spawn url associated with the respective collaboration group, I’m receiving a 404. When spawning the same pod via admin, then trying to access the Lab instance directly with the non-admin, I get a permission denied error.

Clearly the roles I’m building are not taking. Can anyone provide guidance here as to e.g., whether the role syntax is different when approaching in this manner?

Scrubbed code follows:

# API calls for authentication, admin status, and grabbing a json list of projects happens above

groups = []
roles = []

# Create projects as collaborative groups
for project in projects_json_response['items']:

    project_name = project['uuid']
                            
    # create a JupyterHub user for each collaboration and assign the collaboration user to the collaboration group
    collab_username = f"{project_name}-collab"
    collab_user = await self.auth_to_user({'name': collab_username, 'admin': False, 'groups': ['collaborative']})

    # create a role granting access to the collaboration user’s account
    roles.append({
        "name": f"collab-access-{project_name}",
        "scopes": [
            f"access:servers!user={collab_username}",
            f"admin:servers!user={collab_username}",
            "admin-ui",
            f"list:users!user={collab_username}",
        ],
        "groups": [project_name],
    })

    # create a group for each collaboration
    groups.append(project_name)

# assign the group to the role, so it has access to the account
# assign members of the project to the collaboration group, so they have access to the project
user = await self.auth_to_user({'name': username, 'admin': admin, 'groups': groups, 'roles': roles})
self.set_login_cookie(user)

# For non-admins, skip the home screen and redirect the user to spawn the collaboration notebook
if not admin:
    _url=url_path_join(self.hub.server.base_url, '/spawn/', project_param_content, '-collab')

self.redirect(_url)

Have looked at the JupyterHub database and indeed the roles are not being created. The roles table only has the standard user, admin, server, token, and jupyterhub-idle-culler records.

Strangely it appears that the BaseHandler’s auth_to_user function does not create new roles, but only allow mappings to existing roles. This is despite the documentation stating that when manage_roles config is True and this is called:

Any roles not already present in the database will be created

Indeed I call self.auth_to_user in my custom authenticator, which calls user.sync_roles, which ultimately calls roles.create_role for each role passed as part of the initial call, which directly interacts with the ORM.

I’m left wondering whether my manage_roles config is not taking, or if something is wrong with the objects that need to persist?

Oh my goodness, it’s because I’m running JupyterHub 4.1.6 on my server…

…and this feature was only released in 5.0 (which I’ve been using to debug on my dev machine):

Well at least I’m feeling slightly saner, if not foolish about this misstep.

2 Likes

One final note here, which will save someone a ton of time: when handle_roles is True, the roles array passed to the base handler’s auth_to_user method will overwrite all other default roles, scopes, etc

In the pseudocode above, I was missing “self” in the array of scopes, which then disallowed someone to accept the oauth prompt to be identified when joining a “collaboration” user’s server.