jupyterhub / ltiauthenticator

A JupyterHub authenticator for LTI
https://ltiauthenticator.readthedocs.io
BSD 3-Clause "New" or "Revised" License
67 stars 54 forks source link

Moodle LTI 1.3 support and guidelines #118

Open jlanza opened 1 year ago

jlanza commented 1 year ago

Proposed change

Is Moodle LTI 1.3 supported? Is there any information on how to integrate it with Moodle? The README is focused on Canvas. Currently I don't know where to put in JupyterHub configuration the private keys, etc. Moodle is asking for a public key.

Who would use this feature?

(just because I have to fill it) Probably quite some people trying to provide more security ;)

welcome[bot] commented 1 year ago

Thank you for opening your first issue in this project! Engagement like this is essential for open source projects! :hugs:
If you haven't done so already, check out Jupyter's Code of Conduct. Also, please try to follow the issue template as it helps other other community members to contribute more effectively. welcome You can meet the other Jovyans by joining our Discourse forum. There is also an intro thread there where you can stop by and say Hi! :wave:
Welcome to the Jupyter community! :tada:

martinclaus commented 1 year ago

Hi @jlanza, support for LTI 1.3 is currently added (#134) and will be part of the next release. But you are correct, the documentation and README really require some attention. Would be great if you are willing to contribute the Moodle specific documentation, once the PR is merged.

jlanza commented 1 year ago

Thanks a lot for the work towards LTI 1.3. I have used you LTI 1.1 implementation at my course and I will be eager to try to integrate LTI 1.3 (first I have to read the spec and check if the Moodle of my organization is updated (I guess yes) ;)). If I have time I can do so and also contribute.

martinclaus commented 1 year ago

Hi @jlanza! LTI 1.3 support (#134) is merged into the main branch and I have updated the LTI 1.3 section of the README. I hope the configuration of the LTI13Authenticator is now more clear.

jlanza commented 1 year ago

I really have to thank you all for the great job. I will test it as soon as I have sometime. I have integrated the functionality in my lectures, which are currently running. So I will test in a parallel deployment.

jeflem commented 1 year ago

Hi @jlanza, I'm running JupyterHub with LTIAuthenticator via Moodle 4 (just when I started to think about writing the 1.3 part for LTIAuthenticator on my own a few weeks ago, @martinclaus did it, saved me many hours, really great job done, many thanks!!!). Here are some config hints from my experience and testing:

In jupyterhub_config.py:

c.LTI13Authenticator.issuer = 'https://your-moodle-domain.org'
c.LTI13Authenticator.authorize_url = 'https://your-moodle-domain.org/mod/lti/auth.php'
c.LTI13Authenticator.client_id = 'client_id_generated_by_moodle'
c.LTI13Authenticator.jwks_endpoint ='http://your-moodle-domain.org/mod/lti/certs.php'

In Moodle's external tool configuration dialog (I use manual configuration):

Important: The 'Default launch container' has to be set to 'Existing window'. All other option (even 'New window') do not work with JupyterHub because JupyterHub doesn't allow embedding into other sites (Moodle's 'New Window' first embeds JupyterHub for some reason and then opens it in a new window).

hugokoopmans commented 1 year ago

Hi there, I am trying to get this to work ;-) I have added the ltiauthenticator to my docker file, done all the steps above (moodle 4 and jupyterhub both on docker with a nginx reverse proxy running), added appropriate lines to the config file.

Both systems work fine, but I cannot get the integration working.

Seems like the lti is not available in the jupyterhub?

When I add the jupyterhub as external tool the oath url is not found...

https://my.jupyter.org/hub/lti13/oauth_login gives

404 : Not Found

Jupyter has lots of moons, but this is not one...

My dockerfile:

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
ARG JUPYTERHUB_VERSION
FROM jupyterhub/jupyterhub:$JUPYTERHUB_VERSION
# Install dockerspawner, nativeauthenticator
# hadolint ignore=DL3013
RUN python3 -m pip install --no-cache-dir \
dockerspawner \
jupyterhub-nativeauthenticator \
jupyterhub-ltiauthenticator

CMD ["jupyterhub", "-f", "/srv/jupyterhub/jupyterhub_config.py"]`

Do i need something extra on the commandline to make the authenticator run?

All advice welcome

hugokoopmans commented 1 year ago

logging from jupyterhub


[I 2023-08-31 09:09:13.119 JupyterHub users:768] Server jupyter-admin is ready
[I 2023-08-31 09:09:13.120 JupyterHub log:191] 200 GET /hub/api/users/jupyter-admin/server/progress?_xsrf=[secret] (jupyter-admin@86.93.106.67) 2122.98ms
[I 2023-08-31 09:09:13.158 JupyterHub log:191] 302 GET /hub/spawn-pending/jupyter-admin -> /user/jupyter-admin/ (jupyter-admin@86.93.106.67) 3.45ms
[I 2023-08-31 09:09:13.242 JupyterHub log:191] 302 GET /hub/api/oauth2/authorize?client_id=jupyterhub-user-jupyter-admin&redirect_uri=%2Fuser%2Fjupyter-admin%2Foauth_callback&response_type=code&state=[secret] -> /user/jupyter-admin/oauth_callback?code=[secret]&state=[secret] (jupyter-admin@86.93.106.67) 24.23ms
[I 2023-08-31 09:09:13.291 JupyterHub log:191] 200 POST /hub/api/oauth2/token (jupyter-admin@172.20.0.4) 30.71ms
[I 2023-08-31 09:09:13.316 JupyterHub log:191] 200 GET /hub/api/user (jupyter-admin@172.20.0.4) 22.27ms
[W 2023-08-31 09:10:04.218 JupyterHub web:1869] 403 POST /hub/lti13/oauth_login (86.93.106.67): '_xsrf' argument missing from POST
[W 2023-08-31 09:10:04.255 JupyterHub log:191] 403 POST /hub/lti13/oauth_login (@86.93.106.67) 38.16ms
[W 2023-08-31 09:10:25.584 JupyterHub web:1869] 403 POST /hub/lti13/oauth_login (86.93.106.67): '_xsrf' argument missing from POST
[W 2023-08-31 09:10:25.586 JupyterHub log:191] 403 POST /hub/lti13/oauth_login (@86.93.106.67) 2.28ms
[W 2023-08-31 09:10:55.231 JupyterHub log:191] 404 GET /hub/lti13/oauth_login (jupyter-admin@86.93.106.67) 37.30ms
[W 2023-08-31 09:12:07.748 JupyterHub web:1869] 403 POST /hub/lti13/oauth_login (86.93.106.67): '_xsrf' argument missing from POST
[W 2023-08-31 09:12:07.750 JupyterHub log:191] 403 POST /hub/lti13/oauth_login (@86.93.106.67) 2.29ms
09:13:28.382 [ConfigProxy] info: 200 GET /api/routes 
[I 2023-08-31 09:14:04.606 JupyterHub log:191] 200 POST /hub/api/users/jupyter-admin/activity (jupyter-admin@172.20.0.4) 12.41ms
09:18:28.382 [ConfigProxy] info: 200 GET /api/routes 
[I 2023-08-31 09:19:16.609 JupyterHub log:191] 200 POST /hub/api/users/jupyter-admin/activity (jupyter-admin@172.20.0.4) 13.56ms
[I 2023-08-31 09:20:52.988 JupyterHub log:191] 200 GET /hub/api/user (jupyter-admin@172.20.0.4) 10.99ms
[W 2023-08-31 09:21:00.229 JupyterHub log:191] 404 GET /hub/lti13/oauth_login (jupyter-admin@86.93.106.67) 3.83ms
[W 2023-08-31 09:21:13.255 JupyterHub log:191] 404 GET /hub/lti13/oauth_login (jupyter-admin@86.93.106.67) 3.45ms
09:23:28.382 [ConfigProxy] info: 200 GET /api/routes 
[I 2023-08-31 09:24:13.586 JupyterHub log:191] 200 POST /hub/api/users/jupyter-admin/activity (jupyter-admin@172.20.0.4) 12.20ms
09:28:28.382 [ConfigProxy] info: 200 GET /api/routes 
[I 2023-08-31 09:28:59.096 JupyterHub log:191] 200 POST /hub/api/users/jupyter-admin/activity (jupyter-admin@172.20.0.4) 14.65ms```
hugokoopmans commented 1 year ago

hmmm i assume these two can run side by side

jupyterhub-nativeauthenticator \ jupyterhub-ltiauthenticator

hugokoopmans commented 1 year ago

hehe

you do need to choose indeed:

c.JupyterHub.authenticator_class = "nativeauthenticator.NativeAuthenticator"

c.JupyterHub.authenticator_class = "ltiauthenticator.lti13.auth.LTI13Authenticator"

now it works from moodle (but not anymore from outside)

martinclaus commented 1 year ago

Hi @hugokoopmans,

you were quicker than me :smile:.

Indeed, ltiauthenticator cannot run alongside another authenticator as mentioned in the README. Though, it could be mentioned in the docs too. Actually, I am not sure if multiple authenticator are supported by Jupyterhub at all. You may try to look here for some hints how to add another authenticator.

hugokoopmans commented 3 months ago

ok it worked fine untill a few weeks ago...

hugokoopmans commented 3 months ago

now I get this error : Required LTI 1.3 arg iss not in request

[I 2024-05-28 12:19:50.793 JupyterHub log:191] 200 GET /hub/login?next=%2Fhub%2Fadmin (@90.145.96.110) 2.46ms

12:20:58.516 [ConfigProxy] info: 200 GET /api/routes 

[I 2024-05-28 12:21:30.095 JupyterHub log:191] 302 GET /hub/user-redirect/git-pull?repo=https%3A%2F%2Fgithub.com%2Fstefmolin%2Fpandas-workshop&urlpath=lab%2Ftree%2Fpandas-workshop%2F -> /hub/login?next=%2Fhub%2Fuser-redirect%2Fgit-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252Fstefmolin%252Fpandas-workshop%26urlpath%3Dlab%252Ftree%252Fpandas-workshop%252F (@90.145.96.110) 1.38ms

[I 2024-05-28 12:21:30.134 JupyterHub log:191] 200 GET /hub/login?next=%2Fhub%2Fuser-redirect%2Fgit-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252Fstefmolin%252Fpandas-workshop%26urlpath%3Dlab%252Ftree%252Fpandas-workshop%252F (@90.145.96.110) 3.47ms

[W 2024-05-28 12:21:51.949 JupyterHub web:1869] 400 GET /hub/lti13/oauth_login?next=%2Fhub%2Fadmin (90.145.96.110): Required LTI 1.3 arg iss not in request
hugokoopmans commented 3 months ago

any suggestions?

meffmadd commented 1 month ago

@hugokoopmans Were you able to solve the issue with the iss arg?

jeflem commented 1 month ago

Hi @hugokoopmans and @meffmadd, tried to reproduce your problem, but without success. My guess is that there's some missconfiguration on the Moodle side, maybe related to using nbgitpuller (the GET to /hub/lti13/oauth_login looks wrong).

Here's my external tool config for Moodle+nbgitpuller (everything on current stable version):

Full log output from JHub would be good for discussion.

Best regards,

Jens

meffmadd commented 1 month ago

Hi @jeflem, thanks for the answer! I deploy Moodle locally using https://github.com/moodlehq/moodle-docker. Maybe this helps with reproducibility if you still want to try.

Indeed, the POST request to the LTI13LoginInitHandler handler contains all the arguments necessary to pass validation (iss, login_hint and target_link_uri), but the second GET request only contains the next URL.

This has to be a misconfiguration of Moodle but this is all quite opaque to me...

jeflem commented 1 month ago

I do not see any GET to /hub/lti13/oauth_login in my JHub logs. Login related log output:

Aug 21 11:29:22 7e44488f7419 jupyterhub[6977]: [W 2024-08-21 11:29:22.217 JupyterHub handlers:243] Ignoring next_url None, using '/'
Aug 21 11:29:22 7e44488f7419 jupyterhub[6977]: [I 2024-08-21 11:29:22.218 JupyterHub log:192] 302 POST /hub/lti13/oauth_login -> http://192.168.178.128:9090/moodle/mod/lti/auth.php?response_type=id_token&scope=openid&response_mode=form_post&prompt=none&client_id=SPhDLIochoLakRz&redirect_uri=http%3A%2F%2F192.168.178.128%3A8000%2Fhub%2Flti13%2Foauth_callback&login_hint=7&nonce=fec754f48b52c13986bf39ab516168b300f5b5691185d19f611a5fe5bd1233b7&state=[secret]&lti_message_hint=%7B%22cmid%22%3A1%2C%22launchid%22%3A%22ltilaunch1_1352468325%22%7D (@::ffff:10.0.2.100) 1.90ms
Aug 21 11:29:22 7e44488f7419 jupyterhub[6977]: [I 2024-08-21 11:29:22.294 JupyterHub base:973] User logged in: u7
Aug 21 11:29:22 7e44488f7419 jupyterhub[6977]: [I 2024-08-21 11:29:22.295 JupyterHub log:192] 302 POST /hub/lti13/oauth_callback -> / (u7@::ffff:10.0.2.100) 22.77ms
Aug 21 11:29:22 7e44488f7419 jupyterhub[6977]: [I 2024-08-21 11:29:22.302 JupyterHub log:192] 302 GET / -> /hub/ (@::ffff:10.0.2.100) 0.70ms
Aug 21 11:29:22 7e44488f7419 jupyterhub[6977]: [I 2024-08-21 11:29:22.330 JupyterHub log:192] 302 GET /hub/ -> /user/u7/ (u7@::ffff:10.0.2.100) 21.20ms
Aug 21 11:29:22 7e44488f7419 jupyterhub-singleuser[7290]: [I 2024-08-21 11:29:22.337 ServerApp] 302 GET /user/u7/ -> /user/u7/lab? (@::ffff:10.0.2.100) 0.74ms
Aug 21 11:29:22 7e44488f7419 jupyterhub-singleuser[7290]: [I 2024-08-21 11:29:22.341 ServerApp] 302 GET /user/u7/lab? -> /hub/api/oauth2/authorize?client_id=jupyterhub-user-u7&redirect_uri=%2Fuser%2Fu7%2Foauth_callback&response_type=code&state=[secret] (@::ffff:10.0.2.100) 1.12ms
Aug 21 11:29:22 7e44488f7419 jupyterhub[6977]: [I 2024-08-21 11:29:22.371 JupyterHub log:192] 302 GET /hub/api/oauth2/authorize?client_id=jupyterhub-user-u7&redirect_uri=%2Fuser%2Fu7%2Foauth_callback&response_type=code&state=[secret] -> /user/u7/oauth_callback?code=[secret]&state=[secret] (u7@::ffff:10.0.2.100) 25.08ms
Aug 21 11:29:22 7e44488f7419 jupyterhub[6977]: [I 2024-08-21 11:29:22.440 JupyterHub log:192] 200 POST /hub/api/oauth2/token (u7@127.0.0.1) 58.35ms
Aug 21 11:29:22 7e44488f7419 jupyterhub[6977]: [I 2024-08-21 11:29:22.459 JupyterHub log:192] 200 GET /hub/api/user (u7@127.0.0.1) 17.05ms
Aug 21 11:29:22 7e44488f7419 jupyterhub-singleuser[7290]: [I 2024-08-21 11:29:22.460 ServerApp] Logged-in user u7

If you post your JHub logs, I'll have a look at them.

PS: I'm testing with Moodle Podman image shipping with Ananke Jupyter Distribution instead of the official Docker images (see doc).

meffmadd commented 1 month ago

Hi @jeflem, thanks for your help, my error is unrelated to the ltiauthenticator! We actually forked the implementation because we needed special handling for usernames and used a different BaseHandler, which it turns out was incompatible with the LTI handlers. Since the error today was identical to the Moodle integration problems, I jumped to early conclusions.

I tested a new Moodle LTI tool against the vanilla implementation (with JH 5.1.0) and everything worked. If the error that @hugokoopmans encountered was using the standard ltiauthenticator implementation, this error has been fixed since then.

My error was caused by checking user authentication in the prepare method of the base handler while the auth flow was executing, resulting in another redirect to the login handler without any arguments, leading to the above error message about the missing iss argument.