Access JupyterHub REST API from Config

Hey there,

originally, I wanted to modify the groups of an user after the user authenticated.
manage_groups is not a option because I still want to be able to manually assign groups since the authenticator may not return the proper group for every user.

Thus, I want to modify the groups using JupyterHub’s REST API in the authenticator’s post_auth_hook.
However, I somehow cannot access the JupyterHub REST API from inside the hub pod.
I tried to access the API using localhost and hub, but it didn’t work.
The pod freezes completely.

Since I can access the API inside the pod’s container using curl, it may be related to my Python code?

Relevant code:

api_session = requests.Session()
api_session.headers = {
    "Authorization": "token ..."
}

def assign_user_to_group(user: str, group: str) -> bool:
    url = f"http://localhost:8081/hub/api/groups/{group}/users"
    payload = {"users": [user]}

    try:
        response = api_session.post(url, json=payload)

        print(response.text)

        if response.status_code == 200:
            print("Request was successful!")
        else:
            print(
                f"Failed to assign user '{user}' to group '{group}' (HTTP status code {response.status_code}): {response.reason}"
            )
    except Exception as e:
        print(f"Failed to assign user '{user}' to group '{group}': {e}")


def update_groups(authenticator: Authenticator, handler, auth_model: Dict[str, Any]):
    # Determine correct group...
 
    assign_user_to_group(auth_model["name"], "my-group")

    return auth_model


c.GitHubOAuthenticator.post_auth_hook = update_groups

Thanks in advance.

Best regards
Paul

The requests library makes blocking calls. Try using a non-blocking async client instead. Tornado includes one:
https://www.tornadoweb.org/en/stable/httpclient.html

1 Like

Thank you so much (as always).

Now, I run into the issue of the custom page (including group-specific changes) being rendered before the groups are assigned via REST API.

I could extend the “group check” inside the template by checking the group first, and if it does not exist, I could use the auth state to determine the group.

Is there a more elegant way?

if auth_model has a groups key, it is interpreted as a list of groups the user is a member of. Then you can set the group membership of the user immediately as part of login:

def update_groups(authenticator: Authenticator, handler, auth_model: Dict[str, Any]) -> dict:
    groups = auth_model.setdefault("groups", [])
    groups.append("my-group")
    return auth_model

c.GitHubOAuthenticator.post_auth_hook = update_groups
c.GitHubOAuthenticator.manage_groups = True

and you don’t need to make any API requests. Maybe this is the simplest way?

Indeed, this would be my preferred solution.
However, c.GitHubOAuthenticator.manage_groups prevents me from assigning groups manually.
The overall issue is that the groups provided by GitHub (in this case) do not contain the correct group for every user. Thus, I have to be able to adjust the groups manually, which I cannot do with manage_groups = True.

That’s true, if it must be done via later REST API calls (though it likely will not stay true).

If you know the groups in your code, though, manage_groups only means the Authenticator is responsible, not that GitHub is the only source of this information. So if you have the information available at login time in order to return it from post_auth_hook, this should work with managed groups.

I’ll have to look at refresh_user, though, since that may not pass through your hook and we’ll need another mechanism to. It may require doing it in a small subclass if there isn’t a callable config hook that affects both.

1 Like

I’ll go with manage_groups = True and consider an additional database inside the hook, which contains the manually (i.e., database insertions) assigned groups.