Jupyterhub user viewing another user's notebook

This came up while I was trying to find a way for Hub users to share notebooks with one another. As an example, if I share http://<host>:<port>/user/me/notebooks/hello-world.ipynb with another user, the other user should be able to view this notebook by visiting this link.

In that case, my server at /user/me first tries to authorize the access, and upon successful authorization, redirect the other user to my notebook.

What’s interesting though is that ONLY admins are able to view other users’ notebooks, not regular Jupyterhub users. In this example, if this other user is an admin, then he/she would be able to view my notebook, but otherwise, a 403 page is displayed.

So I turned on --debug mode and screened through the logs as I ran through 2 scenarios as different users.

  1. admin user viewing non-admin user’s notebook via direct link. Log shows:

    [D 2020-04-23 09:35:17.269 SingleUserNotebookApp zmqhandlers:296] Initializing websocket connection /user/NonAdminUser/api/kernels/60e17652-6cf8-4a48-b9e1-55f4b20f3f27/channels
    [D 2020-04-23 09:35:17.271 SingleUserNotebookApp auth:857] Allowing Hub admin AdminUser

  2. non-admin user viewing other user’s notebook via direct link. Log shows:

    [I 2020-04-23 09:39:03.150 SingleUserNotebookApp log:174] 302 GET /user/AAAA/oauth_callback?code=[secret]&state=[secret] -> /user/****/notebooks/hello-world.ipynb (@::ffff:135.210.10.9) 120.81ms
    [W 2020-04-23 09:39:03.248 SingleUserNotebookApp auth:883] Not allowing Hub user NonAdminUser

Log confirms the behavior that I am seeing in the GUI. Digging a little deeper, I see this log is generated by jupyterhub/services/auth.py module --> HubAuthenticated.check_hub_user() method. Code snippet here for reference:

def check_hub_user(self, model):
        """Check whether Hub-authenticated user or service should be allowed.

        Returns the input if the user should be allowed, None otherwise.

        Override if you want to check anything other than the username's presence in hub_users list.

        Args:
            model (dict): the user or service model returned from :class:`HubAuth`
        Returns:
            user_model (dict): The user model if the user should be allowed, None otherwise.
        """

        name = model['name']
        kind = model.setdefault('kind', 'user')
        if self.allow_all:
            app_log.debug(
                "Allowing Hub %s %s (all Hub users and services allowed)", kind, name
            )
            return model

        if self.allow_admin and model.get('admin', False):
            app_log.debug("Allowing Hub admin %s", name)
            return model

        if kind == 'service':
            # it's a service, check hub_services
            if self.hub_services and name in self.hub_services:
                app_log.debug("Allowing whitelisted Hub service %s", name)
                return model
            else:
                app_log.warning("Not allowing Hub service %s", name)
                raise UserNotAllowed(model)

        if self.hub_users and name in self.hub_users:
            # user in whitelist
            app_log.debug("Allowing whitelisted Hub user %s", name)
            return model
        elif self.hub_groups and set(model['groups']).intersection(self.hub_groups):
            allowed_groups = set(model['groups']).intersection(self.hub_groups)
            app_log.debug(
                "Allowing Hub user %s in group(s) %s",
                name,
                ','.join(sorted(allowed_groups)),
            )
            # group in whitelist
            return model
        else:
            app_log.warning("Not allowing Hub user %s", name)
            raise UserNotAllowed(model)

Now what really confuses me is that …

in scenario 1, I hit the if self.allow_admin and model.get(‘admin’,False) check and returned a model, meaning successful authorization.

but in scenario 2, I should have hit the first if statement if self.allow_all, because self.allow_all is True at this point. Here’s the snippet:

    hub_services = None  # set of allowed services
    hub_users = None  # set of allowed users
    hub_groups = None  # set of allowed groups
    allow_admin = False  # allow any admin user access

    @property
    def allow_all(self):
        """Property indicating that all successfully identified user
        or service should be allowed.
        """
        return (
            self.hub_services is None
            and self.hub_users is None
            and self.hub_groups is None
        )

and more importantly, I didnt whitelist any users in the jupyterhub_config.py file. So ending up in the last if > elif > else check doesnt make a lot of sense.

In addition, when I log in as a non-admin user and viewing my own notebooks, log shows that
‘Allowing whitelisted Hub user [user-name]’, but again, NO users were whitelisted in the config file. And logging in as self doesn’t mean I’m a whitelisted user.

Apologies for the lengthy post. If anyone has any insights, hmu!