jupyter-collaboration icon indicating copy to clipboard operation
jupyter-collaboration copied to clipboard

Adding a UI to let users share their servers to other users and groups

Open Hyrla opened this issue 10 months ago • 15 comments

Hello,
I've added the necessary code related to my issue #437.

image

It replaces the default link share UI in cases where Jupyter is running behind a Hub that allows the creation of shares. It enables users to:

  • Create a share to the current server for a user or a group using a search box.
  • Revoke a share for a user or a group.

The permissions needed (both in Hub and Lab) are:

  • "shares!user" to manage users' shares.
  • "read:users:name" to create shares by designating a user by their name (TODO: check if it's really necessary if list:users is already in scope?).
  • "list:users" (not mandatory) to list users for the search box.
  • "list:groups" (not mandatory) to list groups for the search box.
  • "servers!user" (not mandatory) to allow other users to start and stop the shared server

My code does not:

  • Work if neither list:users nor list:groups is in scope. I wanted to add an option to invite someone by typing their exact username, without having to search.
  • Manage share codes, which may be useful in some scenarios.

This development has been made for my own internal purposes, but I think it could be useful to everyone. Comments and critiques are welcome!

Hyrla avatar Feb 08 '25 13:02 Hyrla

Binder :point_left: Launch a Binder on branch Hyrla/jupyter-collaboration/main

github-actions[bot] avatar Feb 08 '25 13:02 github-actions[bot]

Amazing work! I think determining the server owner should be more robust:

https://github.com/Hyrla/jupyter-collaboration/blob/1743de5c50dcd6372dd7926cfc72b96970544f02/packages/collaboration/src/sharedlink.ts#L48

This does not work if c.JupyterHub.base_url is set.

ykazakov avatar Mar 16 '25 10:03 ykazakov

Amazing work! I think determining the server owner should be more robust:

https://github.com/Hyrla/jupyter-collaboration/blob/1743de5c50dcd6372dd7926cfc72b96970544f02/packages/collaboration/src/sharedlink.ts#L48

This does not work if c.JupyterHub.base_url is set.

Thank you for your feedback. Do you have any suggestion to determine the server owner with a cleaner way?

Hyrla avatar Mar 16 '25 11:03 Hyrla

I think, if the server is running under JupyterHub, one can extract JupyterHub.base_url from PageConfig.getOption('hubPrefix') by removing the trailing /hub/.

ykazakov avatar Mar 16 '25 12:03 ykazakov

There also appears to be an option PageConfig.getOption('hubServerUser'), which is probably what you need?

ykazakov avatar Mar 16 '25 12:03 ykazakov

Similarly, serverName can be extracted from PageConfig.getOption('hubServerName').

ykazakov avatar Mar 16 '25 12:03 ykazakov

You can see all available options of PageConfig by inspecting the jupyter-config-data tag of the HTML source of the lab page. See https://jupyterlab.readthedocs.io/en/latest/api/functions/coreutils.PageConfig.getOption.html

ykazakov avatar Mar 16 '25 12:03 ykazakov

You can see all available options of PageConfig by inspecting the jupyter-config-data tag of the HTML source of the lab page. See https://jupyterlab.readthedocs.io/en/latest/api/functions/coreutils.PageConfig.getOption.html

Omg thank you, I searched hours for this list of PageConfig

Hyrla avatar Mar 16 '25 15:03 Hyrla

@ykazakov I've pushed the fix, thanks again for your feedback. Could you please try if it works on your Hub? It works on mine but c.JupyterHub.base_url is not set there.

Hyrla avatar Mar 16 '25 17:03 Hyrla

@Hyrla I tested, now it works with c.JupyterHub.base_url set! However, these options are probably available only if running under JupyterHub, so the variables should probably be set after the check is done:

https://github.com/jupyterlab/jupyter-collaboration/blob/0f9e77ed4d839a59757295f3e499a1810dc8a3d8/packages/collaboration/src/sharedlink.ts#L56-L61

Also, if running behind a JupyterHub, shouldn't all these options be set, not just one of them? I would suggest to instead check the presence of (all) options that you really use for the dialog.

ykazakov avatar Mar 17 '25 07:03 ykazakov

@ykazakov thank you for your feedback. I am pushing a fix regarding the two getOptions() that are misplaced. Regarding the detection of JupyterHub, not all these variables are necessarily set. I did set back the original condition as seen before my pull request: https://github.com/jupyterlab/jupyter-collaboration/blob/1c23b688ba22c1911d3ee93542e1da6e67f0514a/packages/collaboration/src/sharedlink.ts#L53

Hyrla avatar Mar 17 '25 08:03 Hyrla

I have been further testing, and in general, the dialog works great! 👍

Some (mostly minor) comments:

  1. In the search list one cannot tell users and groups apart. If there is a group named exactly as the user, it is not clear which one is wich.
  2. Clicking on the item in the list immediately creates a share. Perhaps it would make sense to have a confirmation dialogue to prevent unintentional shares (particularly for groups).
  3. What happens if the list is very long (hundreds of users)? Perhaps wait until something is typed in the search box and the number of matches is narrowed down. See the nextcloud share dialogue.
  4. If the user does not have a permission to share a server (example: user Alice shares her server with user Bob, and user Bob tries to re-share the server of Alice), the share button results in the standard dialog, which would not work even if to include the token. Perhaps instead, one should show a dialog that the user does not have permission to share this server (if in doubt, ask admin), or even better, not to show the share icon at all.

ykazakov avatar Mar 17 '25 09:03 ykazakov

This should be where the page config vars originate: https://github.com/jupyterhub/jupyterhub/blob/5.2.1/jupyterhub/singleuser/extension.py#L216-L234

manics avatar Mar 17 '25 09:03 manics

I have been further testing, and in general, the dialog works great! 👍

Thank you!

Some (mostly minor) comments:

1. In the search list one cannot tell users and groups apart. If there is a group named exactly as the user, it is not clear which one is wich.

Yes you're right, I will add a "Group" prefix like in the shares list

2. Clicking on the item in the list immediately creates a share. Perhaps it would make sense to have a confirmation dialogue to prevent unintentional shares (particularly for groups).

Do you have any example snippets of code to create such a dialogue ? I wasn't able to find one (I am not an expert at all in TS nor Jupyter extension)

3. What happens if the list is very long (hundreds of users)? Perhaps wait until something is typed in the search box and the number of matches is narrowed down. See the [nextcloud share dialogue](https://nextcloud.com/blog/sharing-in-nextcloud/).

On my Hub there are about 350 users and it works like a charm. I am using the API with pagination, so there is no big query with every users at a single time. But if there is thousands of users it could be a problem. As far as I know, there is no Hub API to search for users or group?

4. If the user does not have a permission to share a server (example: user Alice shares her server with user Bob, and user Bob tries to re-share the server of Alice), the share button results in the standard dialog, which would not work even if to include the token. Perhaps instead, one should show a dialog that the user does not have permission to share this server (if in doubt, ask admin), or even better, not to show the share icon at all.

Like for 2., I don't know (yet) a way to create such dialogue but I will add it asap

Hyrla avatar Mar 17 '25 09:03 Hyrla

This should be where the page config vars originate: https://github.com/jupyterhub/jupyterhub/blob/5.2.1/jupyterhub/singleuser/extension.py#L216-L234

Thank you!

Hyrla avatar Mar 17 '25 09:03 Hyrla

Amazing, thank you for this work! This will be really valuable for a workshop @fperez , myself, and others will be teaching at AGU this Winter. If I can help out with anything, maybe typing? let me know :)

mfisher87 avatar May 27 '25 16:05 mfisher87

Hi @krassowski, thank you for your valuable feedback! I fixed my code regarding most of your suggestions. I'm a beginner in TypeScript and not really sure about typing this stuff. @mfisher87 if you have some time, your help is welcome :)

Hyrla avatar May 28 '25 11:05 Hyrla

I will push typing updates.

It looks like both /user and /groups return kind: 'user' and kind: 'group' respectively, is the addition of type needed?

https://jupyterhub.readthedocs.io/en/stable/reference/rest-api.html#operation/get-users https://jupyterhub.readthedocs.io/en/stable/reference/rest-api.html#operation/get-groups

krassowski avatar May 28 '25 12:05 krassowski

Thank you very much @krassowski for your feedbacks and help on this PR!

Hyrla avatar May 28 '25 14:05 Hyrla

4.1.0 RC0 is available for testing:

  • https://github.com/jupyterlab/jupyter-collaboration/releases/tag/v4.1.0rc0
  • https://pypi.org/project/jupyter-collaboration/4.1.0rc0/

krassowski avatar May 28 '25 16:05 krassowski

I have installed 4.1.0 RC0 on my school Jupyter Hub and everything seems to work fine

Hyrla avatar May 28 '25 20:05 Hyrla

@krassowski how do you think we should handle the documentation of this feature? Especially regarding the permissions needed to make it working. On my Hub, I've simply added this to my Z2JH yaml config:

hub:
[...]
  loadRoles:
    share:
      description: Allow users to manage shares on their server
      scopes: [shares!user, read:users:name, list:users, servers!user]
      groups: [d35c2143-xxxx, 6bdd8535-xxxx, c481a9cf-xxxx, c1927c2a-xxxx, ba715d4d-xxxx, 6c7c5e80-xxxx, 9e98f201-xxxx, 4e3c0034-xxxx]

Then I've added this extra config to give these permissions to the lab's scope:

hub:
[...]
 extraConfig:
  customLabScopesOptions: |
      c.Spawner.oauth_client_allowed_scopes = ["read:users:name", "shares!user", "list:users", "servers!user"]

Finally, I've added this extra config to remove the oauth confirmation page (which I found not necessary for my use case):

hub:
[...]
 extraConfig:
 [...]
    disableConfirmation: |
      # Désactivation de la page de confirmation relou
      import jupyterhub.apihandlers.auth
      class DisableConfirmationOAuthAuthorizeHandler(
          jupyterhub.apihandlers.auth.OAuthAuthorizeHandler
      ):
          def needs_oauth_confirm(self, *args, **kwargs):
              return False
      for i, (route, handler) in enumerate(jupyterhub.apihandlers.default_handlers):
          if route == "/api/oauth2/authorize":
              jupyterhub.apihandlers.default_handlers[i] = (route, DisableConfirmationOAuthAuthorizeHandler)
              break

If needed, here is my full config: https://gitlab.esiea.fr/gauthier.heiss/esiea-jupyter-image/-/blob/main/JupyterHub.yaml

I am pretty sure this config is not the perfect way to go and could be much simplier and secure.

Hyrla avatar May 29 '25 10:05 Hyrla

I would defer to @manics on that.

krassowski avatar May 29 '25 12:05 krassowski

You could update the docs on https://jupyterhub.readthedocs.io/en/stable/tutorial/collaboration-users.html https://github.com/jupyterhub/jupyterhub/blob/main/docs/source/tutorial/collaboration-users.md

Disabling oauth isn't recommended unless all users fully trust each other, so I wouldn't include that.

manics avatar May 29 '25 14:05 manics

You could update the docs on https://jupyterhub.readthedocs.io/en/stable/tutorial/collaboration-users.html https://github.com/jupyterhub/jupyterhub/blob/main/docs/source/tutorial/collaboration-users.md

Disabling oauth isn't recommended unless all users fully trust each other, so I wouldn't include that.

Thank you for your reply. Concerning oauth, is there other any to handle it? Of course disabling to confirmation UI is not recommended at all, but otherwise every use has to accept the particular sharing role permissions when starting a server, even if it's not meant to be collaborative

Hyrla avatar May 29 '25 16:05 Hyrla