Giter VIP home page Giter VIP logo

ltiauthenticator's Introduction

LTI JupyterHub Authenticator

Documentation build status GitHub Workflow Status Latest PyPI version

Implements the LTI 1.3 and the LTI v1.1 authenticators for use with JupyterHub.

This converts JupyterHub into an LTI Tool Provider, which can be then easily be used with various Tool Consumers, such as Canvas, Open EdX, Moodle, Blackboard, etc.

So far, ltiauthenticator has been tested with Open edX, Canvas, and Moodle. Documentation contributions are highly welcome!

Note that with these LTI authenticators going directly to the hub URL will no longer allow you to log in. You must visit the hub through an appropriate LTI 1.1 compliant Tool Consumer or LTI 1.3 compliant Platform (such as Canvas, Moodle, Open edX, etc.) to be able to log in.

Note: LTI 1.1 identifies the LMS as the Tool Consumer and LTI 1.3 identifies the LMS as the Platform for all practical purposes these terms are equivalent.

Installation

You can install the authenticator from PyPI:

pip install jupyterhub-ltiauthenticator

Using the LTI Authenticators

For detailed instructions on how to configure the LTI13Authenticator or LTI11Authenticator and integrate it with an LMS, such as Canvas, Open EdX, Moodle, Blackboard, etc., please take a look at the documentation.

ltiauthenticator's People

Contributors

bengig avatar brospars avatar consideratio avatar dependabot[bot] avatar fkryvyts-codete avatar jeflem avatar jgwerner avatar krassowski avatar martinclaus avatar minrk avatar pre-commit-ci[bot] avatar regisb avatar rodnorfor avatar ryanlovett avatar samhinshaw avatar sean-morris avatar yuvipanda avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

ltiauthenticator's Issues

AttributeError: module 'oauthlib.oauth1.rfc5849.signature' has no attribute 'signature_base_string'

While trying to authenticate I keep getting this error and not sure how to proceed.

AttributeError: module 'oauthlib.oauth1.rfc5849.signature' has no attribute 'signature_base_string'

I have website jhub.hibbardsclass.org and it is directed to local server ip address.
What do you need to see for files?

Ok, as I dug in a little more I found that the args are being passed through:

` def init(self, consumers):
self.consumers = consumers

def validate_launch_request(
        self,
        launch_url,
        headers,
        args
):`

print(args) {'custom_team_name': '', 'custom_component_display_name': 'Jupyter Notebook', 'lti_version': 'LTI-1p0', 'oauth_nonce': '123242103712172168641578158327', 'resource_link_id': 'localhost-1b3d411c3f82456f8e51d646a59a21bf', 'context_id': 'course-v1:CHS+LOCOXtreme+2019-2020', 'oauth_signature_method': 'HMAC-SHA1', 'custom_cohort_name': '', 'lis_person_contact_email_primary': '[email protected]', 'custom_cohort_id': '', 'oauth_signature': '******************=', 'lis_person_name_family': 'test', 'context_title': 'LocoXtreme Voyager', 'lti_message_type': 'basic-lti-launch-request', 'launch_presentation_return_url': '', 'lis_person_name_full': 'test student', 'context_label': 'CHS', 'user_id': 'd314d43fb3f76d2ffc9f4d46e1e64b41', 'roles': 'Instructor', 'custom_team_id': '', 'oauth_consumer_key': '*******', 'lis_result_sourcedid': 'course-v1%3ACHS%2BLOCOXtreme%2B2019-2020:localhost-1b3d411c3f82456f8e51d646a59a21bf:d314d43fb3f76d2ffc9f4d46e1e64b41', 'lis_person_name_given': 'Robert', 'oauth_version': '1.0', 'custom_user_id': '12', 'launch_presentation_locale': 'en', 'oauth_timestamp': '1578158327', 'lis_person_sourcedid': 'robert', 'oauth_callback': 'about:blank'}
then this error occurs
Uncaught exception POST /hub/lti/launch (::ffff:184.155.193.25) HTTPServerRequest(protocol='http', host='jhub.hibbardsclass.org:8000', method='POST', uri='/hub/lti/launch', version='HTTP/1.1', remote_ip='::ffff:184.155.193.25') Traceback (most recent call last): File "/share/conda/envs/jhub/lib/python3.8/site-packages/tornado/web.py", line 1699, in _execute result = await result File "/srv/jupyterhub/ltiauthenticator/ltiauthenticator/__init__.py", line 209, in post _ = yield self.login_user() File "/share/conda/envs/jhub/lib/python3.8/site-packages/jupyterhub/handlers/base.py", line 655, in login_user authenticated = await self.authenticate(data) File "/share/conda/envs/jhub/lib/python3.8/site-packages/jupyterhub/auth.py", line 383, in get_authenticated_user authenticated = await maybe_future(self.authenticate(handler, data)) File "/srv/jupyterhub/ltiauthenticator/ltiauthenticator/__init__.py", line 148, in authenticate if validator.validate_launch_request( File "/srv/jupyterhub/ltiauthenticator/ltiauthenticator/__init__.py", line 84, in validate_launch_request base_string = signature.signature_base_string( AttributeError: module 'oauthlib.oauth1.rfc5849.signature' has no attribute 'signature_base_string' '

at this point:
base_string = signature.signature_base_string( 'POST', signature.base_string_uri(launch_url), signature.normalize_parameters( signature.collect_parameters(body=args_list, headers=headers) ) )
I am running openEdx at hibbardsclass.org being directed to local IP, as well as my jupyterhub instance at jhub.hibbardsclass.org also directed to a local IP.

I have admin/xblock_config/courseeditltifieldsenabledflag/ enabled and Send extra parameters set to true in the lti_consumers xblock for course.

pip list:

alembic                          1.3.1
async-generator                  1.10
attrs                            19.3.0
backcall                         0.1.0
bcrypt                           3.1.7
bleach                           3.1.0
blinker                          1.4
certifi                          2019.11.28
certipy                          0.1.3
cffi                             1.13.2
chardet                          3.0.4
cryptography                     2.8
decorator                        4.4.1
defusedxml                       0.6.0
docker                           4.1.0
dockerspawner                    0.11.1
entrypoints                      0.3
escapism                         1.0.0
idna                             2.8
importlib-metadata               1.2.0
ims-lti-py                       0.7.1
ipykernel                        5.1.3
ipython                          7.10.1
ipython-genutils                 0.2.0
ipywidgets                       7.5.1
jedi                             0.15.1
Jinja2                           2.10.3
jsonschema                       3.2.0
jupyter                          1.0.0
jupyter-client                   5.3.3
jupyter-console                  6.0.0
jupyter-core                     4.6.1
jupyterhub                       1.0.0
jupyterhub-firstuseauthenticator 0.12
jupyterhub-ltiauthenticator      0.4.1.dev0          /srv/jupyterhub/ltiauthenticator
lti                              0.9.4
lxml                             4.4.2
Mako                             1.1.0
MarkupSafe                       1.1.1
mistune                          0.8.4
more-itertools                   8.0.2
nbconvert                        5.6.1
nbformat                         4.4.0
nbgrader                         0.3.3
notebook                         6.0.1
oauthenticator                   0.10.0
oauthlib                         3.0.1
pamela                           1.0.0
pandocfilters                    1.4.2
parso                            0.5.1
pexpect                          4.7.0
pickleshare                      0.7.5
pip                              19.3.1
prometheus-client                0.7.1
prompt-toolkit                   2.0.10
ptyprocess                       0.6.0
pycparser                        2.19
pycurl                           7.43.0.3
Pygments                         2.5.2
PyJWT                            1.7.1
pyOpenSSL                        19.0.0
PyQt5                            5.12.3
PyQt5-sip                        4.19.18
PyQtWebEngine                    5.12.1
pyrsistent                       0.15.6
PySocks                          1.7.1
python-dateutil                  2.8.1
python-dotenv                    0.10.3
python-editor                    1.0.4
pyzmq                            18.1.1
qtconsole                        4.6.0
requests                         2.22.0
requests-oauthlib                1.3.0
Send2Trash                       1.5.0
setuptools                       42.0.1.post20191125
six                              1.13.0
SQLAlchemy                       1.3.11
terminado                        0.8.3
testpath                         0.4.4
tornado                          6.0.3
traitlets                        4.3.3
urllib3                          1.25.7
wcwidth                          0.1.7
webencodings                     0.5.1
websocket-client                 0.56.0
wheel                            0.33.6
widgetsnbextension               3.5.1
zipp                             0.6.0

conda list
# Name Version Build Channel _libgcc_mutex 0.1 main alembic 1.3.1 py_0 conda-forge async_generator 1.10 py_0 conda-forge attrs 19.3.0 py_0 conda-forge backcall 0.1.0 py_0 conda-forge bcrypt 3.1.7 pypi_0 pypi bleach 3.1.0 py_0 conda-forge blinker 1.4 py_1 conda-forge ca-certificates 2019.11.28 hecc5488_0 conda-forge certifi 2019.11.28 py38_0 conda-forge certipy 0.1.3 py_0 conda-forge cffi 1.13.2 py38h8022711_0 conda-forge chardet 3.0.4 py38_1003 conda-forge configurable-http-proxy 1.3.0 0 conda-forge cryptography 2.8 py38h72c5cf5_0 conda-forge dbus 1.13.6 he372182_0 conda-forge decorator 4.4.1 py_0 conda-forge defusedxml 0.6.0 py_0 conda-forge docker 4.1.0 pypi_0 pypi dockerspawner 0.11.1 pypi_0 pypi entrypoints 0.3 py38_1000 conda-forge escapism 1.0.0 pypi_0 pypi expat 2.2.5 he1b5a44_1004 conda-forge fontconfig 2.13.1 h86ecdb6_1001 conda-forge freetype 2.10.0 he983fc9_1 conda-forge gettext 0.19.8.1 hc5be6a0_1002 conda-forge glib 2.58.3 py38h6f030ca_1002 conda-forge gst-plugins-base 1.14.5 h0935bb2_0 conda-forge gstreamer 1.14.5 h36ae1b5_0 conda-forge icu 64.2 he1b5a44_1 conda-forge idna 2.8 py38_1000 conda-forge importlib_metadata 1.2.0 py38_0 conda-forge ims-lti-py 0.7.1 pypi_0 pypi ipykernel 5.1.3 py38h5ca1d4c_0 conda-forge ipython 7.10.1 py38h5ca1d4c_0 conda-forge ipython_genutils 0.2.0 py_1 conda-forge ipywidgets 7.5.1 py_0 conda-forge jedi 0.15.1 py38_0 conda-forge jinja2 2.10.3 py_0 conda-forge jpeg 9c h14c3975_1001 conda-forge jsonschema 3.2.0 py38_0 conda-forge jupyter 1.0.0 pypi_0 pypi jupyter_client 5.3.3 py38_1 conda-forge jupyter_console 6.0.0 py_0 conda-forge jupyter_core 4.6.1 py38_0 conda-forge jupyterhub 1.0.0 py38_0 conda-forge jupyterhub-firstuseauthenticator 0.12 pypi_0 pypi jupyterhub-ltiauthenticator 0.4.1.dev0 dev_0 <develop> krb5 1.16.3 h05b26f9_1001 conda-forge ld_impl_linux-64 2.33.1 h53a641e_7 conda-forge libclang 9.0.0 default_hde54327_4 conda-forge libcurl 7.65.3 hda55be3_0 conda-forge libedit 3.1.20170329 hf8c457e_1001 conda-forge libffi 3.2.1 he1b5a44_1006 conda-forge libgcc-ng 9.1.0 hdf63c60_0 libiconv 1.15 h516909a_1005 conda-forge libllvm9 9.0.0 hc9558a2_3 conda-forge libpng 1.6.37 hed695b0_0 conda-forge libsodium 1.0.17 h516909a_0 conda-forge libssh2 1.8.2 h22169c7_2 conda-forge libstdcxx-ng 9.1.0 hdf63c60_0 libuuid 2.32.1 h14c3975_1000 conda-forge libuv 1.33.1 h516909a_0 conda-forge libxcb 1.13 h14c3975_1002 conda-forge libxkbcommon 0.9.1 hebb1f50_0 conda-forge libxml2 2.9.10 hee79883_0 conda-forge lti 0.9.4 pypi_0 pypi lxml 4.4.2 pypi_0 pypi mako 1.1.0 py_0 conda-forge markupsafe 1.1.1 py38h516909a_0 conda-forge mistune 0.8.4 py38h516909a_1000 conda-forge more-itertools 8.0.2 py_0 conda-forge nbconvert 5.6.1 py38_0 conda-forge nbformat 4.4.0 py_1 conda-forge nbgrader 0.3.3 pypi_0 pypi ncurses 6.1 hf484d3e_1002 conda-forge nodejs 13.0.0 h10a4023_1 conda-forge notebook 6.0.1 py38_0 conda-forge nspr 4.24 he1b5a44_0 conda-forge nss 3.47 he751ad9_0 conda-forge oauthenticator 0.10.0 pypi_0 pypi oauthlib 3.0.1 py_0 conda-forge openssl 1.1.1d h516909a_0 conda-forge pamela 1.0.0 py_0 conda-forge pandoc 2.8.1 0 conda-forge pandocfilters 1.4.2 py_1 conda-forge parso 0.5.1 py_0 conda-forge pcre 8.43 he1b5a44_0 conda-forge pexpect 4.7.0 py38_0 conda-forge pickleshare 0.7.5 py38_1000 conda-forge pip 19.3.1 py38_0 conda-forge prometheus_client 0.7.1 py_0 conda-forge prompt_toolkit 2.0.10 py_0 conda-forge pthread-stubs 0.4 h14c3975_1001 conda-forge ptyprocess 0.6.0 py_1001 conda-forge pycparser 2.19 py38_1 conda-forge pycurl 7.43.0.3 py38h16ce93b_1 conda-forge pygments 2.5.2 py_0 conda-forge pyjwt 1.7.1 py_0 conda-forge pyopenssl 19.0.0 py38_0 conda-forge pyqt 5.12.3 py38hcca6a23_1 conda-forge pyqt5-sip 4.19.18 pypi_0 pypi pyqtwebengine 5.12.1 pypi_0 pypi pyrsistent 0.15.6 py38h516909a_0 conda-forge pysocks 1.7.1 py38_0 conda-forge python 3.8.0 h357f687_5 conda-forge python-dateutil 2.8.1 py_0 conda-forge python-dotenv 0.10.3 pypi_0 pypi python-editor 1.0.4 py_0 conda-forge pyzmq 18.1.1 py38h1768529_0 conda-forge qt 5.12.5 hd8c4c69_1 conda-forge qtconsole 4.6.0 py_0 conda-forge readline 8.0 hf8c457e_0 conda-forge requests 2.22.0 py38_1 conda-forge requests-oauthlib 1.3.0 pypi_0 pypi send2trash 1.5.0 py_0 conda-forge setuptools 42.0.1 py38_0 conda-forge six 1.13.0 py38_0 conda-forge sqlalchemy 1.3.11 py38h516909a_0 conda-forge sqlite 3.30.1 hcee41ef_0 conda-forge terminado 0.8.3 py38_0 conda-forge testpath 0.4.4 py_0 conda-forge tk 8.6.10 hed695b0_0 conda-forge tornado 6.0.3 py38h516909a_0 conda-forge traitlets 4.3.3 py38_0 conda-forge urllib3 1.25.7 py38_0 conda-forge wcwidth 0.1.7 py_1 conda-forge webencodings 0.5.1 py_1 conda-forge websocket-client 0.56.0 pypi_0 pypi wheel 0.33.6 py38_0 conda-forge widgetsnbextension 3.5.1 py38_0 conda-forge xorg-libxau 1.0.9 h14c3975_0 conda-forge xorg-libxdmcp 1.1.3 h516909a_0 conda-forge xz 5.2.4 h14c3975_1001 conda-forge zeromq 4.3.2 he1b5a44_2 conda-forge zipp 0.6.0 py_0 conda-forge zlib 1.2.11 h516909a_1006 conda-forge

any ideas or help would be awesome

thank you robert

feat(lti13) Allow `username_key` to be located inside the `custom` claim

Proposed change

Allow username_key to be optionally located in the https://purl.imsglobal.org/spec/lti/claim/custom claim, as suggested by the config help. This would improve interoperability since some LMS provide their username only as a custom claim.

Alternative options

Subclassing by user to add required logic which increases their maintenance burden.

Who would use this feature?

User that deal with an LMS Platform that does not provide a proper username via top-level claims (e.g. email, username) but within the "custom" claim, e.g via custom parameter substitution.

Suggested solution

If the value of username_key traitlet begins with custom_, this part will be removed and the remainder is looked up within the https://purl.imsglobal.org/spec/lti/claim/custom claim. This should be sufficiently save with respect to name collisions, since LTI 1.3 does not define top-level claims that begin with custom_. It is also the default naming scheme for custom claims in LTI 1.1.

feat(lti13) Username calculation

Proposed change

Allow username_key value to be a function for username calculation.

Who would use this feature?

Users who want to customise usernames without adding custom LTI claims.

Suggest a solution

Turn username_key into a Union traitlet which can be either Unicode or Callable(function with an ID token parameter, which returns the username in LTI13Authenticator.get_username).

Alternatively a new Callable traitlet could be added instead.

JupyterHub - Bad config encountered during initialization

The 'authenticator_class' trait of <jupyterhub.app.JupyterHub object at 0x7f979b19de10> instance must be a type, but 'ltiauthenticator.LTIAuthenticator' could not be imported.<

This is the error message I get when I try to use the LTIAuthenticator as my authenticator in my JupyterHub config file.

c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator' c.LTIAuthenticator.consumers = {os.environ['LTI_CLIENT_KEY']: os.environ['LTI_CLIENT_SECRET']}

Is there anything more that I have to do, than inserting these two lines in my config file? The required environment variables are set, jupyterhub-ltiauthenticator was installed via pip.

Python - v 3.6.5
JupyterHub - v 0.9.2
Jupyter - v 4.4.0

Configured oauth_consumer_key not known

Bug description

I am testing PR #48 with Helm chart zero-to-jupyterhub-k8s 1.1.1 as follows:

  1. I use the Docker image jupyterhub/k8s-hub:1.1.1 as base. This is working fine with the configuration below.
  2. install the latest ltiauthenticator
  3. configure the authenticator in values.yml
hub:
  config:
    LTIAuthenticator:
      consumers: {
        "aaaaaaaaaaaaaaaaaaaaaaa": "bbbbbbbbbbbbbbbbb"
        }
    JupyterHub:
      authenticator_class: ltiauthenticator.LTIAuthenticator

Expected behaviour

Authentication is working as with ltiauthenticator 1.0.0

Actual behaviour

Authentication fails. The issue raises here:

        if args["oauth_consumer_key"] not in self.consumers:
            raise HTTPError(401, "unknown oauth_consumer_key")

I think the LTI11Authenticator does not get the configuration items from the values.yml (content is {} at initialization); the attribute args["oauth_consumer_key"] contains the correct value.

I tried then to find where the configuration values are read, but I could not find that piece of code here or in JupyterHub.

Your personal set up

Kubernetes setup is straight forward. For testing, my Dockerfile to build the test environment looks like (forked the repo to add additional debugging output only):

FROM jupyterhub/k8s-hub:1.1.1

USER 0
RUN git clone https://github.com/bengig/ltiauthenticator.git && cd ltiauthenticator && git checkout jhub-integration && pip3 install .

USER 1000
CMD ["jupyterhub", "--config", "/usr/local/etc/jupyterhub/jupyterhub_config.py"]

setup.py missing in ltiauthenticator1.6.1

Hi,

I was wondering how I can install ltiauthenticator 1.6.1 from source.
I downloaded the code and wanted to install using pip install -e . after modifying a file but the setup.py is missing.
The modification i did was adding lti1.3 to the init so that we can import it. currently it's not possible to import LTI13Authenticator.

best

Returning "result" from a Jupyter instance (more of a question than an issue)

Hi there,

We are using your code to authenticate Jupyter sessions from a Moodle, which seems to work flawlessly for our initial purposes.

Later, we would in fact like to use LTI to communicate back a "result" from Jupyter (e.g. a number resulting from the execution of a cell) that would enter the Moodle grading system.

Does any of the developers have hints in the direction of achieving this sort of functionality - or is that thought completely out of scope here?

Best
Peter

Create and publish documentation via Readthedocs

Proposed change

Migrate most documentation from the included README to Readthedocs.

Alternative options

Keep the existing README.md and add more content to that file.

Who would use this feature?

Anyone that needs access to user guides, tutorials, dev setup instructions, etc.

Use usernames instead of user IDs

I'm currently using LTI authentication with Moodle. By default, the user IDs are used to create the JHub users. Is it possible to use the Moodle usernames (or first/last names) instead?

Help is appreciated. Thanks!

Allow multiple LTI client IDs

Proposed change

To use LTIAuthenticator the LTI platform's (randomly generated) client ID for the tool (JupyterHub/LTIAuthenticator) has to be provided to LTIAuthenticator via jupyterhub_config.py: c.LTI13Authenticator.client_id = 'some_string'. At the moment only one client ID can be specified. Thus, LTIAuthenticator (and the whole JupyterHub behind it) can be used as exactly one LTI tool.

For several use cases (see below) LTIAuthenticator has to be used as multiple tools on the platform side. Thus there will be several client IDs LTIAuthenticator should accept. On the config side something like c.LTI13Authenticator.client_id = ['id1', 'id2', 'id3'] would be nice. LTIAuthenticator then accepts all requests having one of those client IDs.

Alternative options

The only alternative I see at the moment for the use cases below is to have several JupyterHubs in parallel and share user home directories between hubs. Not really an option.

Use case 1: different nbgitpuller links

In a Moodle (or whatever LMS) course one wants to link to different notebooks in one and the same git repo via nbgitpuller. On LMS side (at least in Moodle and some less known regional LMS named OPAL or OLAT) one has to configure one LTI tool per nbgitpuller link (the nbgitpuller link is the tool URL the platform redirects to after successful LTI authentication). Moodle creates random client IDs for all LTI tools. These IDs cannot be modified. Thus, each nbgitpuller link comes to LTIAuthenticator with different client ID.

Use case 2: using multiple LMS instances

Bigger JupyterHubs may be used by multiple institutions or an institution might have two different LMS in use. Then the Hub will be accessed from different LMS and, thus, with different client IDs.

Use case 3: autoenrolement to nbgrader courses

We (some colleagues and me) are currently working on using LTIAuthentictor and some custom tools to automatically enrole students to nbgrader courses on our JupyterHub when they come in via LTI for the first time (saves a lot of time compared to manual enrolement). A student starts an LTI activity in the Moodle course, which sends the student to JupyterHub. There we evaluate LTI data (course title aso.) and enrole the student to corresponding nbgrader course.

Having multiple courses on the hub (with different audience) requires multiple LTI activities on LMS side (with different course titles) and, thus, multiple client IDs.

Suggest a solution

From my (very limited) point of view adding support for multiple client IDs in LTIAuthenticator requires only one additional line of code in auth.py, line 44:

client_id = TraitletsList(config=True)

(and removal of line 31 (a comment)).

Checking the client ID is done by jwt.decode. The client_id value from jupyterhub_config.py is passed to this method via audience argument, which accepts an iterable (see third code box in audience doc).

I've tested this locally and it works. But I don't know whether there might be unwanted side effects.

Urgent: cut a beta release to help test #91?

#91 adds supports, and even requires the library pyjwt 2+.

A breaking change, and also a change that I haven't tested as I'm not a user of this authenticator.

I want this done as quickly as possible, because:

  1. its a security warning about pyjwt <2.4.0, fixed in 2.4.0
  2. oauthenticator will require pyjwt >2 in its 15.0.0 release, while this now requires <2, making z2jh unable to have both ltiauthenticator and oauthenticator

Since I can't test this, I suggest we merge #91 and cut a beta release, then ask on discourse.jupyter.org for feedback. @jgwerner @yuvipanda, what do you think?

LTI13 - Ensure we can defend against replay attacks

Understanding that the LTI13Authenticator was broken anyhow, makes the merged security patch of not validating JWTs less time critical to release.

To make sure that we don't cut a release with something that does work, but is insecure, I'd like to see this TODO note understood either to be acceptable or resolved.

# TODO: validate that received nonces haven't been received before
# and that they are within the time-based tolerance window
nonce_raw = hashlib.sha256(state.encode())
nonce = nonce_raw.hexdigest()
self.log.debug(f"nonce value: {nonce}")
self.authorize_redirect(
client_id=client_id,
login_hint=login_hint,
lti_message_hint=lti_message_hint,
nonce=nonce,
redirect_uri=redirect_uri,
state=state,
extra_params={"state": state},
)

I understand this currently, as that we would be suspecptible to a "replay attack".

A nonce can be used in an authentication protocol as a method of preventing replay attacks by ensuring that old communications are not being reused. The nonce helps to prove that the message received was sent by the intended sender and was not intercepted and resent by a bad actor.

I'm not sure how this typically is handled though, is state stored in memory or on disk about already handled requests or similar?

Add an LTI 1.3 Authenticator

Currentl, we support an LTI 1.1 based Authenticator. LTI 1.3 is a significant improvement, and we should support that too. An additional authenticator here is probably the way to go

Multiple consistent authentication mechanisms?

We're using JupyterHub with OAUTH using out GSuite authentication to our university unique identifier mechanism. This is the same authentication ID that would be used if we used the LTI authenticator.

It would be nice to be able to use the LTI authenticator AND the OAUTH -- the use of the LTI would allow us to auto-create accounts for students by having them come in from LTI and then we could use the OAUTH mechanism if they want to directly access their instance rather than going in through the LMS (moodle, in our case).

Any idea if this is possible? The README indicates "no", but I'm curious how hard it would be to add. We're doing something similar for an autograder (https://inginious.org/)

Update package with common utility functions

The LTI 1.1 and LTI 1.3 code (primarily the authenticators and the validators) have some common functions. Refactor the code so that these common tasks are encapsulated in functions to make them easier to document, test, and re-use.

Related to #38

How can I contribute?

Good day, I've always wanted to be a contributor to an open source project. While I was trying the authenticator out, I experienced a bug that I patched on my local server. I'd like to patch it up here and also contribute to the documentation if possible :).

Document danger of user name collisions

Even with a single identity provider user name collisions may happen, if the username_key is set to something that is not unique for a user, family_name is a case in point. This needs to be pointed out clearly as a warning in the documentation

Originally posted by @martinclaus in #151 (comment)

LTI operations

Hi guys, I have implemented an LTI Tool that perfectly authenticates with JupyterHub. Looking at the this LTI authenticator there is no extra methods for the basic operations: LTI Basic Outcomes Service [https://www.imsglobal.org/specs/ltiv1p1/implementation-guide#toc-6](speficied here)

The LTI v1.1 docs say the endpoints and params to use, but the auth info should already be provided into the initial authentication process (this authenticator should get it), and then use it to call with it.

for that there is the need of authentication, and because the auth process validation is done in here I was wondering if you could help with this, with some feature, or even just some code snippets here that I can include myself into my custom code.

Thank you very much!

Improve testing by starting up an LTI 1.3 endpoint in the CI system

Extracting some discussion regarding this code:

    endpoint = Unicode(
        # FIXME: Default value set temporarily to ensure we got some
        #        LTI13_ENDPOINT that can provide a response for tests.
        #
        os.getenv("LTI13_ENDPOINT", "https://canvas.instructure.com/api/lti/security/jwks"),

Is there an open-source LTI implementation that can be run with e.g. Docker to test against a real server?

[...] we could set up the IMS Global LTI 1.3 reference implementation to run tests against the "Platform" (LMS). Other options include spinning up a pre configured third party LMS that is LTI 1.3 compatible, such as Canvas or Moodle and run the integration tests with those solutions.

LTI 1.3 is an extension of OIDC / OAuth 2.0. Section 4.2 from this IMS Global article provides a good summary and a reference to the LTI 1.3 security framework which, in turn, references the RFC 7517 (JSON Web Key) standard.

"403 : Forbidden '_xsrf' argument missing from POST" with JupyterHub 4.0.0

Bug description

When running the latest JupyterHub 4.0.0, LTIAuthenticator fails with the following error "403 : Forbidden '_xsrf' argument missing from POST":

Screenshot from 2023-05-09 10-05-05

This error does not occur with JupyterHub 3.1.1.

I only tested this behaviour with LTI 1.1 but I suspect the same error will occur on LTI 1.3.

This error occurs because JupyterHub 4.0.0 examines cross-site request forgery (XSRF) parameters to make sure that requests are not emitted from a rogue website.

I was able to fix this issue by adding the following dummy XSRF validation method to LTI11AuthenticateHandler:

class LTI11AuthenticateHandler(BaseHandler):
    ...

    def check_xsrf_cookie(self):
        return

Alternatively, I believer that the handler could append an _xsrf parameter to all POST requests, based on the value of the _xsrf cookie. I do not have a proof-of-concept for that solution.

Expected behaviour

The notebook should display successfully.

Actual behaviour

The notebook does not load at all.

How to reproduce

See my personal set up below. I have all reasons to believe that this bug will occur on just any LMS, either with LTI 1.1 or 1.3.

Your personal set up

I am integrating JupyterHub with Open edX, using this jupyter-xblock.

Incompatibilities and hanging tljh-config reload

I have been trying to get LTI authentification on a TLJH server hosted on Google Cloud (trying to incorporate Jupyter Notebooks in a course on Canvas). I'm following the description in http://tljh.jupyter.org/en/latest/install/google.html which all works fine.

However, when trying to use the LTI authentication as shown here, I run into problems.

  1. The latest jupyterhub release 1.0.0 require oauth 3.0, whereas lti authenticator here requires oauth 2.x
  2. Upon configuring the LTI authentication method, the command sudo tljh-config reload hangs. Upon investigating the source code, I'm assuming this is because the jupyterhub server isn't coming back up again.

Has anyone experienced any similar problems recently?

401 : Unauthorized oauth_consumer_key not known

401 : Unauthorized
oauth_consumer_key not known This is the error code, when I try to connect Jupyterhub to open edx platform.

this is my jupyter config:

import os
c = get_config()
c.JupyterHub.log_level = 10
c.Spawner.cmd = '/home/robert/anaconda3/bin/jupyterhub-singleuser'

# Cookie Secret Files
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret'
c.ConfigurableHTTPProxy.auth_token = '/srv/jupyterhub/proxy_auth_token'

c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator'
c.LTIAuthenticator.consumers = {
    os.environ['LTI_CLIENT_KEY']: os.environ['LTI_CLIENT_SECRET']
}

c.LocalGitHubOAuthenticator.create_system_users = True
c.Authenticator.whitelist = {'robert'}
c.Authenticator.admin_users = {'robert'}

this is my nginx config:

user www-data;
worker_processes 4;
pid /run/nginx.pid;

events {
        worker_connections 1024;
        # multi_accept on;
}

http {
        include /etc/nginx/mime.types;

        default_type application/octet-stream;

    #top-level http config for websocket headers
        map $http_upgrade $connection_upgrade {
        default upgrade;
            ''      close;
    }


    # All regular http requests on port 80 become SSL/HTTPS requests on port 32
    server {
        listen 80;
        server_name jhub.hibbardsclass.org;

        # Tell all requests to port 80 to be 302 redirected to HTTPS
        return 302 https://$host$request_uri;
    }

    server {
 #listen 443 ssl default_server;
        listen 443;
        ssl on;
        server_name jhub.hibbardsclass.org;

        ## SSL Protocals
        ssl_certificate /etc/letsencrypt/live/jhub.hibbardsclass.org/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/jhub.hibbardsclass.org/privkey.pem;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_dhparam /srv/jupyterhub/dhparam.pem;

        # Make site accessible from http://localhost/
        #server_name localhost;

        # certs sent to the client in SERVER HELLO are concatenated in
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;
        ssl_stapling on;
        ssl_stapling_verify on;

        # modern configuration. tweak to your needs.
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1$

        # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
        add_header Strict-Transport-Security max-age=15768000;
       location /jupterhub {
            proxy_pass http://127.0.0.1:8000;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-NginX-Proxy true;

            #proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

        location ~ /.well-known {
            allow all;
        }
    }
}

I am running the juypterhub on Ubuntu 18.04 server. Keys are stored as env variables.

LTI Passport String is setup in platform with this format

your-hub-name:client-key:client-secret.

any thoughts or ideas.

405 : Method Not Allowed

I follow the guide and try to make it work with open edx, but when i click the button, i got the error:
405 : Method Not Allowed

how to figure it out?

RELEASE.md outdated

The instructions in RELEASE.md had become outdated after #41.

Until now we have used bump2version which is still documented in RELEASE.md. We have some projects being setup with bump2version and some setup with the jupyterhub/jupyterhub like system. I prefer bump2version in general once its setup and consistency, but I don't think its so important.

Should we revert back to use bump2version, or follow through the changes in #41 and update RELEASE.md to follow that pattern? I'll actually open two PRs because I can do it so quickly, merge one after comparing @jgwerner =D

# from ltiauthenticator/__init__.py, this was removed
__version__ = '1.0.1.dev'



# _version.py was added with this
"""ltiauthenticator version info"""

version_info = (
    1,
    0,
    1,
    "dev",
)
__version__ = ".".join(map(str, version_info[:4]))

if len(version_info) > 4:
    __version__ = "%s%s" % (__version__, version_info[4])



# in setup.py, the following was added
# setup logic from github.com/jupyterhub/jupyterhub
v = sys.version_info
if v[:2] < (3, 6):
    error = "ERROR: LTIAuthenticator requires Python version 3.6 or above."
    print(error, file=sys.stderr)
    sys.exit(1)

# Get the current package version.
here = os.path.abspath(os.path.dirname(__file__))
version_ns = {}
with open(os.path.join("_version.py")) as f:
    exec(f.read(), {}, version_ns)



# in setup.py, the following was removed for a dynamic value
setup(
    name='jupyterhub-ltiauthenticator',
    version='1.0.1.dev',

Issues implementating whoami service

Hi,

My Jupyterhub setup uses ltiauthenticator (with Edx) to authenticate users but when I try to implement a whoami service as describe here, the users is not considered authenticated and if I removed the annotation the service return null.. Any idea why ?

Thanks a lot.

LTI1.1 appears to fail with z2jh helm chart version 2.0.0

Bug description

The z2jh helm chart version 2.0.0 is incompatible with the configuration for the LTI1.1 authenticator.

Expected behaviour

The same behaviour as under 1.2.0 would be nice.

z2jh helmchart v. 1.2.0 works as documented.

Actual behaviour

The authenticator cannot find the local configuration and thus does not verify the client id. The browser gets a 401 unknown oauth_consumer_key error.

With this respect it would be helpful, if the logs should indicate which consumer_keys are configured or at least if any consumers are present. Maybe the authenticator could fail prematurely with a helpful remark, if no consumer_keys are present.

I can see that the client sends the correct consumer_key, but is still rejected. From the location of the error in validator.py it appears that no configuration is present in the validator class.

How to reproduce

Try to get the z2jh helm chart version 2.0.0 working with the documented description.

Your personal set up

My deployment runs on GKE based zero-to-jupyterhub and I use moodle as a tool consumer.

Error 405 even after wel logged in

Hi,

I just implemented my jupyterhub server with a LTI authenticator from a Moodle course.
I'm allowed to log in jupyerhub service but the jupyterhub interface is not showing in the Moodle course.
Here are the logs of my attempt :

I 2021-05-31 11:50:41.667 JupyterHub base:663] User logged in: toto
[I 2021-05-31 11:50:41.668 JupyterHub log:174] 302 POST /hub/lti/launch -> /hub/home (@127.0.0.1) 13.06ms
[I 2021-05-31 11:50:41.700 JupyterHub log:174] 302 GET /hub/home -> /hub/login?next=%2Fhub%2Fhome (@127.0.0.1) 1.04ms
[I 2021-05-31 11:50:41.741 JupyterHub log:174] 302 GET /hub/login?next=%2Fhub%2Fhome -> /hub/lti/launch?next=%2Fhub%2Fhome (@127.0.0.1) 1.18ms
[W 2021-05-31 11:50:41.776 JupyterHub log:174] 405 GET /hub/lti/launch?next=%2Fhub%2Fhome (@127.0.0.1) 2.04ms
[W 2021-05-31 11:50:41.889 JupyterHub log:174] 403 POST /hub/security/csp-report (@127.0.0.1) 1.99ms

Why do I still get a 405 error code as I'm logged in as written in the first log ?

Thanks for your kind help !
Regards,

Build fails with error metadata-generation-failed

Bug description

Building the wheel with pip fails due to a misconfiguration in pyproject.toml. The heuristics to identify the files to be included in the wheel fails since the package name jupyterhub/ltiauthenticator differs from the source directory name ltiauthenticator.

How to reproduce

  1. Clone repo's main branch
  2. Switch to Repo Root dir
  3. run pip wheel .
  4. See error

Expected behaviour

Wheel should be build without error.

Actual behaviour

Error is raised:

% pip wheel .
Processing /Users/mclaus/Documents/jupyterhub/ltiauthenticator
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... error
  error: subprocess-exited-with-error
  
  ร— Preparing metadata (pyproject.toml) did not run successfully.
  โ”‚ exit code: 1
  โ•ฐโ”€> [41 lines of output]
      Traceback (most recent call last):
        File "/Users/mclaus/mambaforge/envs/test-install/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
          main()
        File "/Users/mclaus/mambaforge/envs/test-install/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/Users/mclaus/mambaforge/envs/test-install/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 152, in prepare_metadata_for_build_wheel
          whl_basename = backend.build_wheel(metadata_directory, config_settings)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/65/wr102h8j6wz_gdytvslzntvwrql040/T/pip-build-env-nwo3q3ke/overlay/lib/python3.11/site-packages/hatchling/build.py", line 58, in build_wheel
          return os.path.basename(next(builder.build(directory=wheel_directory, versions=['standard'])))
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/65/wr102h8j6wz_gdytvslzntvwrql040/T/pip-build-env-nwo3q3ke/overlay/lib/python3.11/site-packages/hatchling/builders/plugin/interface.py", line 155, in build
          artifact = version_api[version](directory, **build_data)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/65/wr102h8j6wz_gdytvslzntvwrql040/T/pip-build-env-nwo3q3ke/overlay/lib/python3.11/site-packages/hatchling/builders/wheel.py", line 412, in build_standard
          for included_file in self.recurse_included_files():
        File "/private/var/folders/65/wr102h8j6wz_gdytvslzntvwrql040/T/pip-build-env-nwo3q3ke/overlay/lib/python3.11/site-packages/hatchling/builders/plugin/interface.py", line 176, in recurse_included_files
          yield from self.recurse_selected_project_files()
        File "/private/var/folders/65/wr102h8j6wz_gdytvslzntvwrql040/T/pip-build-env-nwo3q3ke/overlay/lib/python3.11/site-packages/hatchling/builders/plugin/interface.py", line 180, in recurse_selected_project_files
          if self.config.only_include:
             ^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/65/wr102h8j6wz_gdytvslzntvwrql040/T/pip-build-env-nwo3q3ke/overlay/lib/python3.11/site-packages/hatchling/builders/config.py", line 781, in only_include
          only_include = only_include_config.get('only-include', self.default_only_include()) or self.packages
                                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/65/wr102h8j6wz_gdytvslzntvwrql040/T/pip-build-env-nwo3q3ke/overlay/lib/python3.11/site-packages/hatchling/builders/wheel.py", line 231, in default_only_include
          return self.default_file_selection_options.only_include
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/Users/mclaus/mambaforge/envs/test-install/lib/python3.11/functools.py", line 1001, in __get__
          val = self.func(instance)
                ^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/65/wr102h8j6wz_gdytvslzntvwrql040/T/pip-build-env-nwo3q3ke/overlay/lib/python3.11/site-packages/hatchling/builders/wheel.py", line 219, in default_file_selection_options
          raise ValueError(message)
      ValueError: Unable to determine which files to ship inside the wheel using the following heuristics: https://hatch.pypa.io/latest/plugins/builder/wheel/#default-file-selection
      
      At least one file selection option must be defined in the `tool.hatch.build.targets.wheel` table, see: https://hatch.pypa.io/latest/config/build/
      
      As an example, if you intend to ship a directory named `foo` that resides within a `src` directory located at the root of your project, you can define the following:
      
      [tool.hatch.build.targets.wheel]
      packages = ["src/foo"]
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

ร— Encountered error while generating package metadata.
โ•ฐโ”€> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.

'Invalid oauth_signature' when the hub is behind a reverse_proxy using HTTPS

Hi,
I'm stuck on this for some days now and I can't find the right configuration to make it work. I'm using nginx as a reverse_proxy in front of Jupyterhub and LTI as the authenticator.
But I can't get the authenticator to validate the oauth signature just by changing nginx config... I saw this is a pretty common issue when working with LTI and a reverse_proxy (as discussed here and here) so maybe something could be done using those fixes.

Here's my nginx config :

upstream jupyterhub{
  server hub:8000;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# HTTP server to redirect all 80 traffic to SSL/HTTPS
server {
    listen 80;
    server_name <mydomain.tld>;

    # Tell all requests to port 80 to be 302 redirected to HTTPS
    return 302 https://$host$request_uri;
}

server {
    listen 443;
    ssl on;

    server_name <mydomain.tld>;

    ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
    ssl_certificate_key /etc/ssl/certs/nginx-selfsigned.key;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security max-age=15768000;

    location /jupyterhub {
        proxy_pass http://jupyterhub;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme; # Tried with and without
        proxy_set_header X-Forwarded-Ssl on; # Tried with and without
        proxy_set_header X-Url-Scheme $scheme; # Tried with and without

        # websocket headers
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

So any help could be great, thanks !

module 'oauthlib.oauth1.rfc5849.signature' has no attribute 'signature_base_string'

Hi,

I just install litAuthenticator in my conda environment : pip install jupyterhub-ltiauthenticator and configured key and secret for usage with Moodle, but when I connect to jupyterhub URL from Moodle, I got this exception :

HTTPServerRequest(protocol='http', host='xxxxxxxxxxxxxxxxx', method='POST', uri='/hub/lti/launch', version='HTTP/1.1', remote_ip='127.0.0.1')
Traceback (most recent call last):
File "/opt/anaconda3/envs/py37/lib/python3.7/site-packages/tornado/web.py", line 1699, in _execute
result = await result
File "/opt/anaconda3/envs/py37/lib/python3.7/site-packages/ltiauthenticator/init.py", line 214, in post
_ = yield self.login_user()
File "/opt/anaconda3/envs/py37/lib/python3.7/site-packages/jupyterhub/handlers/base.py", line 655, in login_user
authenticated = await self.authenticate(data)
File "/opt/anaconda3/envs/py37/lib/python3.7/site-packages/jupyterhub/auth.py", line 383, in get_authenticated_user
authenticated = await maybe_future(self.authenticate(handler, data))
File "/opt/anaconda3/envs/py37/lib/python3.7/site-packages/ltiauthenticator/init.py", line 150, in authenticate
args
File "/opt/anaconda3/envs/py37/lib/python3.7/site-packages/ltiauthenticator/init.py", line 83, in validate_launch_request
base_string = signature.signature_base_string(
AttributeError: module 'oauthlib.oauth1.rfc5849.signature' has no attribute 'signature_base_string'

Could you help me to solve it please ?

Thank your for your kind help
Best regards,

AttributeError: 'LTIAuthenticator' object has no attribute 'request'

I am trying to integrate Jupyterhub and Moodle by using LTI

I generated Key and Secret by using command openssl rand -hex 32
Then I input Key and Secret into Moodle external tools, the following is the configuration:

Tool name: Jupyterhub
Tool URL: http:///MY_HOST/hub/lti/launch
Key: generated by openssl
Secret: generated by openssl

And the following is my jupyterhub_config.py

c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator'
c.LTIAuthenticator.consumers = {
        "Key": "Secret"
}

Finally, I add configured external tool into the course and add the URL on the course dashboard, however, once I clicked the URL, I got following error message:

[W 2019-08-31 09:44:00.599 JupyterHub log:174] 405 GET /hub/lti/launch (@172.24.0.1) 38.61ms
[E 2019-08-31 09:44:07.155 JupyterHub web:1788] Uncaught exception POST /hub/lti/launch (180.217.109.173)
    HTTPServerRequest(protocol='http', host='MY_JUPYTER_HOST', method='POST', uri='/hub/lti/launch', version='HTTP/1.1', remote_ip='180.217.109.173')
    Traceback (most recent call last):
      File "/opt/conda/lib/python3.6/site-packages/tornado/web.py", line 1699, in _execute
        result = await result
      File "/opt/conda/lib/python3.6/site-packages/ltiauthenticator/__init__.py", line 187, in post
        user = yield self.login_user()
      File "/opt/conda/lib/python3.6/site-packages/jupyterhub/handlers/base.py", line 655, in login_user
        authenticated = await self.authenticate(data)
      File "/opt/conda/lib/python3.6/site-packages/jupyterhub/auth.py", line 383, in get_authenticated_user
        authenticated = await maybe_future(self.authenticate(handler, data))
      File "/opt/conda/lib/python3.6/site-packages/ltiauthenticator/__init__.py", line 141, in authenticate
        protocol = self.request.protocol
    AttributeError: 'LTIAuthenticator' object has no attribute 'request'
    
[E 2019-08-31 09:44:07.160 JupyterHub log:166] {
      "Accept-Language": "en-US,en;q=0.9",
      "Accept-Encoding": "gzip, deflate",
      "Referer": "http://MY_MOODLE_HOST/mod/lti/launch.php?id=2",
      "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
      "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36",
      "Content-Type": "application/x-www-form-urlencoded",
      "Upgrade-Insecure-Requests": "1",
      "Origin": "http://MY_MOODLE_HOST",
      "Cache-Control": "max-age=0",
      "Content-Length": "1225",
      "Connection": "upgrade",
      "X-Scheme": "http",
      "X-Real-Ip": "180.217.109.173",
      "Host": "MY_JUPYTER_HOST"
    }

Example in README.md with use together with the JupyterHub Helm chart is outdated

I failed installing Jupyterhub with ltiauthenticator by using helm.
My helm value is configured as this instruction https://github.com/jupyterhub/ltiauthenticator#custom-configuration-with-jupyterhubs-helm-chart

And the hub pod is CrashLoopBackOff with this error

Loading /usr/local/etc/jupyterhub/secret/values.yaml

No config at /usr/local/etc/jupyterhub/existing-secret/values.yaml

[C 2021-09-25 08:33:13.891 JupyterHub application:89] Bad config encountered during initialization: The 'authenticator_class' trait of <jupyterhub.app.JupyterHub object at 0x7f6c731dd0a0> instance must be a type, but 'lti' could not be importe```

Installation fails due to conflicting oauthlib version

Hi, users are unable to run ltiauthenticator due to dependency conflict with oauthlib package.
As shown in the following full dependency graph of ltiauthenticator, ltiauthenticator requires oauthlib==2.*๏ผŒwhile jupyterhub requires oauthlib>=3.0.

According to pipโ€™s โ€œfirst found winsโ€ installation strategy, oauthlib==2.1.0 is the actually installed version.
However, oauthlib==2.1.0 does not satisfy oauthlib>=3.0.

Dependency tree

ltiauthenticator-0.3
| +-jupyterhub(version range:>=0.8)
| | +-alembic(version range:*)
| | | +-mako(version range:*)
| | | +-python-dateutil(version range:*)
| | | | +-six(version range:>=1.5)
| | | +-python-editor(version range:>=0.3)
| | | +-sqlalchemy(version range:>=0.9.0)
| | +-async-generator(version range:>=1.8)
| | +-certipy(version range:>=0.1.2)
| | +-entrypoints(version range:*)
| | +-jinja2(version range:*)
| | | +-markupsafe(version range:>=0.23)
| | +-oauthlib(version range:>=3.0)
| | +-pamela(version range:*)
| | +-prometheus-client(version range:>=0.0.21)
| | +-python-dateutil(version range:*)
| | | +-six(version range:>=1.5)
| | +-requests(version range:*)
| | | +-certifi(version range:>=2017.4.17)
| | | +-chardet(version range:<3.1.0,>=3.0.2)
| | | +-idna(version range:>=2.5,<2.9)
| | | +-urllib3(version range:<1.26,>=1.21.1)
| | +-sqlalchemy(version range:>=1.1)
| | +-tornado(version range:>=5.0)
| | +-traitlets(version range:>=4.3.2)
| +-oauthlib(version range:==2.)

Thanks for your help.
Best,
Neolith

LTI13Authenticator does not set required routes

Bug description

According to the response of hub/lti13/config, there should be the following routes with handlers defined:

  • hub/oauth_login: LTI13LoginHandler
  • hub/lti13/jkws: LTI13JWKSHandler
    and additionally, for the login flow to work, there needs to be hub/oauth_callback handled by LTI13CallbackHandler.
    All these endpoints currently return a 404: Not found and starting a userserver from an LMS via LTI 1.3 is not possible.

IMHO, the culprit is LTI13Authenticator.get_handlers which overrides its superclass equivalent where /hub/oauth_login and /hub/oauth_callback are set.

I have patched the method to include the superclass logic and that fixed the issue. Note that I did not include the JWKS endpoint since it does not seem to be necessary.

I am happy to provide a pull request.

Expected behaviour

Userserver should start after successful authentication by LMS via LTI 1.3 login flow.

Actual behaviour

Since the required routes are not set, a 404 is returned from hub/oauth_login

Your personal set up

  • Version(s):
    • ltiauthenticator 1.3.0
    • zero-to-jupyterhub 1.2.0 on on-premise Kubernetes cluster
    • Jupyterhub: jupyterhub/k8s-hub:1.2.0 but with ltiauthenticator 1.3.0
Full environment
# Jupyterhub environment
alembic==1.6.5
async-generator==1.10
attrs==21.2.0
bcrypt==3.2.0
cachetools==4.2.2
certifi==2021.5.30
certipy==0.1.3
cffi==1.14.5
chardet==4.0.0
cryptography==3.4.7
Deprecated==1.2.13
entrypoints==0.3
escapism==1.0.1
future==0.18.2
google-auth==1.31.0
greenlet==1.1.0
idna==2.10
ipython-genutils==0.2.0
Jinja2==3.0.1
josepy==1.13.0
jsonschema==3.2.0
jupyter-telemetry==0.1.0
jupyterhub==1.5.0
jupyterhub-firstuseauthenticator==1.0.0
jupyterhub-hmacauthenticator==1.0
jupyterhub-idle-culler==1.1
jupyterhub-kubespawner==1.1.0
jupyterhub-ldapauthenticator==1.3.2
jupyterhub-ltiauthenticator==1.3.0
jupyterhub-nativeauthenticator==0.0.7
jupyterhub-tmpauthenticator==0.6
jwcrypto==1.3.1
kubernetes==17.17.0
ldap3==2.9
Mako==1.1.4
MarkupSafe==2.0.1
mwoauth==0.3.7
nullauthenticator==1.0.0
oauthenticator==14.2.0
oauthlib==3.1.1
onetimepass==1.0.1
pamela==1.0.0
pem==21.2.0
prometheus-client==0.11.0
psycopg2-binary==2.9.1
py-spy==0.3.8
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycparser==2.20
pycryptodome==3.14.1
pycryptodomex==3.14.1
pycurl==7.44.1
pyjwkest==1.4.2
PyJWT==1.7.1
PyMySQL==1.0.2
pyOpenSSL==20.0.1
pyrsistent==0.17.3
python-dateutil==2.8.1
python-editor==1.0.4
python-json-logger==2.0.1
python-slugify==5.0.2
PyYAML==5.4.1
requests==2.25.1
requests-oauthlib==1.3.0
rsa==4.7.2
ruamel.yaml==0.17.9
ruamel.yaml.clib==0.2.2
six==1.16.0
SQLAlchemy==1.4.18
sqlalchemy-cockroachdb==1.4.0
statsd==3.3.0
text-unidecode==1.3
tornado==6.1
traitlets==5.0.5
urllib3==1.26.5
websocket-client==1.1.0
wrapt==1.14.1
Configuration
hub:
  config:
    JupyterHub:
      authenticator_class: ltiauthenticator.lti13.auth.LTI13Authenticator
    LTI13Authenticator:
      username_key:  "lms_user_id"
      authorize_url: "https://saltire.lti.app/platform/auth"
      client_id: "saltire.lti.app"
      endpoint: "https://<MY_HUB>/hub/lti13/jwks"
      # The LTI 1.3 token url used to validate JWT signatures
      token_url: "https://saltire.lti.app/platform/token/##########################"
  extraEnv:
    LTI13_PRIVATE_KEY: "FULL_PATH_TO_PRIVATE_KEY_PEM"
Logs

Moodle LTI 1.3 support and guidelines

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 ;)

issue with importing LTI13Authenticator

Hi,

I would like to inherit from LTI13Authenticator and create a custom lti class to overwrite the normalize_username method. but I keep getting the below error:

import ltiauthenticator
class MyLTI13Authenticator(ltiauthenticator.lti13.auth.LTI13Authenticator):
          def normalize_username(self, username):
            return username.lower().replace("@","-").replace(".","-")

>>>AttributeError: module 'ltiauthenticator.lti13' has no attribute 'auth'

is there anyway to import LTI13Authenticator?

best regards,

Ability to configure the JupyterHub username via a username_key configuration

We are testing a JH instance using LTI, we get it to successfully connect from Canvas to our JH instance, but the issue is it is not using the specific user ID we are requesting.

We do not want to use Canvas IDs we want to use a username instead. We have the configuration attributes we want to use but it is not pulling those values and still giving us the canvas ID (not converting to the username).

jupyterhub 1.4.1
jupyterhub-ltiauthenticator 1.0.0
jupyterhub-systemdspawner 0.15.0

#####################################################################

Canvas LTI Auth

#####################################################################
c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator'
c.LTIAuthenticator.consumers = {
"KEY": "KEY"
}
c.LTIAuthenticator.username_key = 'lis_person_sourcedid'

Thank you @jgwerner - and an invite to be recognized as a project maintainer

@jgwerner thank you for your care and effort put into this project! โค๏ธ ๐ŸŽ‰

I personally appreciate how you have worked to contribute to the long term maintenance of this project by adding test, helping with autoformatting practices, refactoring logic for readability etc. I love it!

Having discussed this with @yuvipanda, we would like to ask you: would you like to be recognized as an official maintainer of this project?

Allow to auto create local user

Just like the way LDAPLocalAuthenticator does, which has settings to automatically create the correspondent local users as below:

c.LocalAuthenticator.create_system_users = True
c.LocalAuthenticator.add_user_cmd = ['useradd', '-m'] 

LTI 1.1 - OAuth Parameter `oauth_callback` should be optional

Proposed change

An optional oauth_callback parameter would be preferable, since not every LTI tool consumer provides this parameter. Also according to https://oauth.net/core/1.0/#rfc.section.6.2.1 this parameter is optional. For example, VIPS doesn't provide this parameter. The proposed solution in https://www.imsglobal.org/specs/ltiv1p1/implementation-guide#toc-2 to set an URL parameter to oauth_callback=about:blank would be a high failure factor in a Learning Management System like Stud.IP for lecturers.

Furthermore, I don't see any reason why JupyterHub would require this parameter.

Who would use this feature?

This could be anyone who has a LTI consumer that doesn't deliver this parameter.

"Audience doesn't match" error if client_id is a string, not an iterable of strings

Bug description

Starting with 1.6.1 ltiauthenticator supports iterables of strings for c.LTI13Authenticator.client_id in jupyterhub_config.py. According to the docs string should still work if only one client ID is required. But JHub shows "Audience doesn't match" on login via LMS/LTI.

If a string instead of an iterable is provided, the string seems to be cast to a set of single characters somehow. This makes validator.validate_auth_response(args) in lti13/handlers.py fail (each character is interpreted as a client ID).

Providing a list containing the string everything is fine.

Note that I have several hubs running. On some (the older ones) strings work fine (as they should corresponding to ltiauthenticator doc). On others (freshly installed) strings don't work. Maybe the problem is related to traitlets version. On the old hubs there is traitlets 5.9. On the fresh ones showing the bug it's 5.12. But not sure whether that's related. JHub version is the same (4.0.2) on all hubs.

Of course, a workaround is to avoid strings and to always use lists of strings.

How to reproduce

Setup JHub with ltiauthenticator and use a string for c.LTI13Authenticator.client_id in jupyterhub_config.py. This was the standard setup before ltiauthenticator 1.6.1.

Expected behaviour

No error. Client ID check does not fail.

Actual behaviour

Message "Audience doesn't match" and login to hub fails.

Your personal set up

  • OS: Debian 12 (bookworm), latest
  • Version(s): jupyterhub 4.0.2, ltiauthenticator 1.6.1
Full environment
(jhub) /$ conda list
# packages in environment at /opt/conda/envs/jhub:
#
# Name                    Version                   Build  Channel
_libgcc_mutex             0.1                 conda_forge    conda-forge
_openmp_mutex             4.5                       2_gnu    conda-forge
alembic                   1.12.1             pyhd8ed1ab_0    conda-forge
anyio                     4.0.0              pyhd8ed1ab_0    conda-forge
argon2-cffi               23.1.0             pyhd8ed1ab_0    conda-forge
argon2-cffi-bindings      21.2.0          py311h459d7ec_4    conda-forge
arrow                     1.3.0              pyhd8ed1ab_0    conda-forge
asttokens                 2.4.1              pyhd8ed1ab_0    conda-forge
async-lru                 2.0.4              pyhd8ed1ab_0    conda-forge
async_generator           1.10                       py_0    conda-forge
attrs                     23.1.0             pyh71513ae_1    conda-forge
babel                     2.13.1             pyhd8ed1ab_0    conda-forge
backcall                  0.2.0              pyh9f0ad1d_0    conda-forge
backports                 1.0                pyhd8ed1ab_3    conda-forge
backports.functools_lru_cache 1.6.5              pyhd8ed1ab_0    conda-forge
beautifulsoup4            4.12.2             pyha770c72_0    conda-forge
bleach                    6.1.0              pyhd8ed1ab_0    conda-forge
blinker                   1.6.3              pyhd8ed1ab_0    conda-forge
brotli-python             1.1.0           py311hb755f60_1    conda-forge
bzip2                     1.0.8                h7f98852_4    conda-forge
c-ares                    1.20.1               hd590300_1    conda-forge
ca-certificates           2023.7.22            hbcca054_0    conda-forge
cached-property           1.5.2                hd8ed1ab_1    conda-forge
cached_property           1.5.2              pyha770c72_1    conda-forge
certifi                   2023.7.22          pyhd8ed1ab_0    conda-forge
certipy                   0.1.3                      py_0    conda-forge
cffi                      1.16.0          py311hb3a22ac_0    conda-forge
charset-normalizer        3.3.1              pyhd8ed1ab_0    conda-forge
comm                      0.1.4              pyhd8ed1ab_0    conda-forge
configurable-http-proxy   4.6.0                he2f69ee_0    conda-forge
cryptography              41.0.5          py311h63ff55d_0    conda-forge
debugpy                   1.8.0           py311hb755f60_1    conda-forge
decorator                 5.1.1              pyhd8ed1ab_0    conda-forge
defusedxml                0.7.1              pyhd8ed1ab_0    conda-forge
entrypoints               0.4                pyhd8ed1ab_0    conda-forge
escapism                  1.0.1                    pypi_0    pypi
exceptiongroup            1.1.3              pyhd8ed1ab_0    conda-forge
executing                 1.2.0              pyhd8ed1ab_0    conda-forge
fqdn                      1.5.1              pyhd8ed1ab_0    conda-forge
greenlet                  3.0.1           py311hb755f60_0    conda-forge
icu                       73.2                 h59595ed_0    conda-forge
idna                      3.4                pyhd8ed1ab_0    conda-forge
importlib-metadata        6.8.0              pyha770c72_0    conda-forge
importlib_metadata        6.8.0                hd8ed1ab_0    conda-forge
importlib_resources       6.1.0              pyhd8ed1ab_0    conda-forge
ipykernel                 6.26.0             pyhf8b6a83_0    conda-forge
ipython                   8.16.1             pyh0d859eb_0    conda-forge
isoduration               20.11.0            pyhd8ed1ab_0    conda-forge
jedi                      0.19.1             pyhd8ed1ab_0    conda-forge
jinja2                    3.1.2              pyhd8ed1ab_1    conda-forge
json5                     0.9.14             pyhd8ed1ab_0    conda-forge
jsonpointer               2.4             py311h38be061_3    conda-forge
jsonschema                4.19.1             pyhd8ed1ab_0    conda-forge
jsonschema-specifications 2023.7.1           pyhd8ed1ab_0    conda-forge
jsonschema-with-format-nongpl 4.19.1             pyhd8ed1ab_0    conda-forge
jupyter-lsp               2.2.0              pyhd8ed1ab_0    conda-forge
jupyter_client            8.5.0              pyhd8ed1ab_0    conda-forge
jupyter_core              5.4.0           py311h38be061_0    conda-forge
jupyter_events            0.8.0              pyhd8ed1ab_0    conda-forge
jupyter_server            2.9.1              pyhd8ed1ab_0    conda-forge
jupyter_server_terminals  0.4.4              pyhd8ed1ab_1    conda-forge
jupyter_telemetry         0.1.0              pyhd8ed1ab_1    conda-forge
jupyterhub                4.0.2              pyh31011fe_0    conda-forge
jupyterhub-base           4.0.2              pyh31011fe_0    conda-forge
jupyterhub-idle-culler    1.2.1                    pypi_0    pypi
jupyterhub-ltiauthenticator 1.6.1                    pypi_0    pypi
jupyterhub-systemdspawner 1.0.1                    pypi_0    pypi
jupyterlab                4.0.7              pyhd8ed1ab_0    conda-forge
jupyterlab_pygments       0.2.2              pyhd8ed1ab_0    conda-forge
jupyterlab_server         2.25.0             pyhd8ed1ab_0    conda-forge
keyutils                  1.6.1                h166bdaf_0    conda-forge
krb5                      1.21.2               h659d440_0    conda-forge
ld_impl_linux-64          2.40                 h41732ed_0    conda-forge
libcurl                   8.4.0                hca28451_0    conda-forge
libedit                   3.1.20191231         he28a2e2_2    conda-forge
libev                     4.33                 h516909a_1    conda-forge
libexpat                  2.5.0                hcb278e6_1    conda-forge
libffi                    3.4.2                h7f98852_5    conda-forge
libgcc-ng                 13.2.0               h807b86a_2    conda-forge
libgomp                   13.2.0               h807b86a_2    conda-forge
libnghttp2                1.55.1               h47da74e_0    conda-forge
libnsl                    2.0.1                hd590300_0    conda-forge
libsodium                 1.0.18               h36c2ea0_1    conda-forge
libsqlite                 3.43.2               h2797004_0    conda-forge
libssh2                   1.11.0               h0841786_0    conda-forge
libstdcxx-ng              13.2.0               h7e041cc_2    conda-forge
libuuid                   2.38.1               h0b41bf4_0    conda-forge
libuv                     1.46.0               hd590300_0    conda-forge
libzlib                   1.2.13               hd590300_5    conda-forge
mako                      1.2.4              pyhd8ed1ab_0    conda-forge
markupsafe                2.1.3           py311h459d7ec_1    conda-forge
matplotlib-inline         0.1.6              pyhd8ed1ab_0    conda-forge
mistune                   3.0.1              pyhd8ed1ab_0    conda-forge
nbclient                  0.8.0              pyhd8ed1ab_0    conda-forge
nbconvert-core            7.9.2              pyhd8ed1ab_0    conda-forge
nbformat                  5.9.2              pyhd8ed1ab_0    conda-forge
nbgitpuller               1.2.0                    pypi_0    pypi
ncurses                   6.4                  hcb278e6_0    conda-forge
nest-asyncio              1.5.8              pyhd8ed1ab_0    conda-forge
nodejs                    18.17.1              h1990674_1    conda-forge
notebook                  7.0.6              pyhd8ed1ab_0    conda-forge
notebook-shim             0.2.3              pyhd8ed1ab_0    conda-forge
oauthlib                  3.2.2              pyhd8ed1ab_0    conda-forge
openssl                   3.1.4                hd590300_0    conda-forge
overrides                 7.4.0              pyhd8ed1ab_0    conda-forge
packaging                 23.2               pyhd8ed1ab_0    conda-forge
pamela                    1.1.0              pyh1a96a4e_0    conda-forge
pandocfilters             1.5.0              pyhd8ed1ab_0    conda-forge
parso                     0.8.3              pyhd8ed1ab_0    conda-forge
pexpect                   4.8.0              pyh1a96a4e_2    conda-forge
pickleshare               0.7.5                   py_1003    conda-forge
pip                       23.3.1             pyhd8ed1ab_0    conda-forge
pkgutil-resolve-name      1.3.10             pyhd8ed1ab_1    conda-forge
platformdirs              3.11.0             pyhd8ed1ab_0    conda-forge
prometheus_client         0.17.1             pyhd8ed1ab_0    conda-forge
prompt-toolkit            3.0.39             pyha770c72_0    conda-forge
prompt_toolkit            3.0.39               hd8ed1ab_0    conda-forge
psutil                    5.9.5           py311h459d7ec_1    conda-forge
ptyprocess                0.7.0              pyhd3deb0d_0    conda-forge
pure_eval                 0.2.2              pyhd8ed1ab_0    conda-forge
pycparser                 2.21               pyhd8ed1ab_0    conda-forge
pycurl                    7.45.1          py311hae980a4_3    conda-forge
pygments                  2.16.1             pyhd8ed1ab_0    conda-forge
pyjwt                     2.8.0              pyhd8ed1ab_0    conda-forge
pyopenssl                 23.2.0             pyhd8ed1ab_1    conda-forge
pysocks                   1.7.1              pyha2e5f31_6    conda-forge
python                    3.11.6          hab00c5b_0_cpython    conda-forge
python-dateutil           2.8.2              pyhd8ed1ab_0    conda-forge
python-fastjsonschema     2.18.1             pyhd8ed1ab_0    conda-forge
python-json-logger        2.0.7              pyhd8ed1ab_0    conda-forge
python_abi                3.11                    4_cp311    conda-forge
pytz                      2023.3.post1       pyhd8ed1ab_0    conda-forge
pyyaml                    6.0.1           py311h459d7ec_1    conda-forge
pyzmq                     25.1.1          py311h34ded2d_2    conda-forge
readline                  8.2                  h8228510_1    conda-forge
referencing               0.30.2             pyhd8ed1ab_0    conda-forge
requests                  2.31.0             pyhd8ed1ab_0    conda-forge
rfc3339-validator         0.1.4              pyhd8ed1ab_0    conda-forge
rfc3986-validator         0.1.1              pyh9f0ad1d_0    conda-forge
rpds-py                   0.10.6          py311h46250e7_0    conda-forge
ruamel.yaml               0.18.2          py311h459d7ec_0    conda-forge
ruamel.yaml.clib          0.2.7           py311h459d7ec_2    conda-forge
send2trash                1.8.2              pyh41d4057_0    conda-forge
setuptools                68.2.2             pyhd8ed1ab_0    conda-forge
six                       1.16.0             pyh6c4a22f_0    conda-forge
sniffio                   1.3.0              pyhd8ed1ab_0    conda-forge
soupsieve                 2.5                pyhd8ed1ab_1    conda-forge
sqlalchemy                2.0.22          py311h459d7ec_0    conda-forge
stack_data                0.6.2              pyhd8ed1ab_0    conda-forge
terminado                 0.17.1             pyh41d4057_0    conda-forge
tinycss2                  1.2.1              pyhd8ed1ab_0    conda-forge
tk                        8.6.13               h2797004_0    conda-forge
tomli                     2.0.1              pyhd8ed1ab_0    conda-forge
tornado                   6.3.3           py311h459d7ec_1    conda-forge
traitlets                 5.12.0             pyhd8ed1ab_0    conda-forge
types-python-dateutil     2.8.19.14          pyhd8ed1ab_0    conda-forge
typing-extensions         4.8.0                hd8ed1ab_0    conda-forge
typing_extensions         4.8.0              pyha770c72_0    conda-forge
typing_utils              0.1.0              pyhd8ed1ab_0    conda-forge
tzdata                    2023c                h71feb2d_0    conda-forge
uri-template              1.3.0              pyhd8ed1ab_0    conda-forge
urllib3                   2.0.7              pyhd8ed1ab_0    conda-forge
wcwidth                   0.2.8              pyhd8ed1ab_0    conda-forge
webcolors                 1.13               pyhd8ed1ab_0    conda-forge
webencodings              0.5.1              pyhd8ed1ab_2    conda-forge
websocket-client          1.6.4              pyhd8ed1ab_0    conda-forge
wheel                     0.41.2             pyhd8ed1ab_0    conda-forge
xz                        5.2.6                h166bdaf_0    conda-forge
yaml                      0.2.5                h7f98852_2    conda-forge
zeromq                    4.3.5                h59595ed_0    conda-forge
zipp                      3.17.0             pyhd8ed1ab_0    conda-forge
zlib                      1.2.13               hd590300_5    conda-forge
zstd                      1.5.5                hfc55251_0    conda-forge
Configuration
# jupyterhub_config.py
# showing only the ltiauthenticator part

base_url = 'http://192.168.178.28:9090'
c.LTI13Authenticator.client_id = 'PUPg1NRDTBsicjZ'
c.LTI13Authenticator.issuer = base_url
c.LTI13Authenticator.authorize_url = f'{base_url}/mod/lti/auth.php'
c.LTI13Authenticator.jwks_endpoint = f'{base_url}/mod/lti/certs.php'
Logs
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.620 JupyterHub handlers:259] Initial login request args are {'iss': 'http://192.168.178.28:9090', 'target_link_uri': 'http://192.168.178.28:8000', 'login_hint': '3', 'lti_message_hint': '7', 'client_id': 'PUPg1NRDTBsicjZ', 'lti_deployment_id': '3'}
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.620 JupyterHub handlers:268] login_hint is 3
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.620 JupyterHub handlers:333] lti_message_hint is 7
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.620 JupyterHub handlers:333] client_id is PUPg1NRDTBsicjZ
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.620 JupyterHub handlers:279] redirect_uri is: http://192.168.178.28:8000/hub/lti13/oauth_callback
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [W 2023-10-29 14:35:15.620 JupyterHub handlers:243] Ignoring next_url None, using '/'
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.620 JupyterHub base:587] Setting cookie lti13authenticator-state: {'httponly': True, 'expires_days': 1}
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.621 JupyterHub base:587] Setting cookie lti13authenticator-nonce-state: {'httponly': True, 'expires_days': 1}
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.621 JupyterHub handlers:286] nonce value: ef2ba7ab86763fbe7b5ac42a9736bf482ae30f3941d1f8502b67c18fcd0054b4
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [I 2023-10-29 14:35:15.621 JupyterHub log:191] 302 POST /hub/lti13/oauth_login -> http://192.168.178.28:9090/mod/lti/auth.php?response_type=id_token&scope=openid&response_mode=form_post&prompt=none&client_id=PUPg1NRDTBsicjZ&redirect_uri=http%3A%2F%2F192.168.178.28%3A8000%2Fhub%2Flti13%2Foauth_callback&login_hint=3&nonce=ef2ba7ab86763fbe7b5ac42a9736bf482ae30f3941d1f8502b67c18fcd0054b4&state=[secret]&lti_message_hint=7 (@::ffff:10.0.2.100) 2.08ms
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.671 JupyterHub handlers:416] Initial launch request args are {'id_token': 'removed token here, token contains correct client id'}
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [W 2023-10-29 14:35:15.704 JupyterHub web:1869] 401 POST /hub/lti13/oauth_callback (::ffff:10.0.2.100): Audience doesn't match
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.704 JupyterHub base:1371] No template for 401
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [W 2023-10-29 14:35:15.726 JupyterHub log:191] 401 POST /hub/lti13/oauth_callback (@::ffff:10.0.2.100) 54.72ms
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.750 JupyterHub log:191] 200 GET /hub/static/css/style.min.css?v=01598a5386176f0279952a3b9632a07e7fce9a12aa53108973c83be9ec3473e7a59354876fab64bfeb01892eb503870183707aa03f207d7a94845ca7980c3819 (@::ffff:10.0.2.100) 1.90ms
Oct 29 15:35:15 5d838adff081 jupyterhub[33]: [D 2023-10-29 14:35:15.765 JupyterHub log:191] 200 GET /hub/logo (@::ffff:10.0.2.100) 1.39ms

Add support the latest release of PyJWT

We currently have the PyJWT package (required for the current implementation of the LTI 1.3 Authenticator) to not install version 2+. We need to refactor the code to support the latest release of this package or replace it altogether.

Do we really need to depend on oauthenticator?

When analysing the dependency to the oauthenticator package, I find most all logic to be related to XSRF state handling, which is only a small portion of the logic implemented in the classes from which we derive.

More precisely, the following functions, classes and their attributes and methods are used:

  • oauthenticator.oauth2._seralize()
  • oauthenticator.oauth2._deserialize()
  • oauthenticator.oauth2.OAuthCallbackHandler._state_cookie
  • oauthenticator.oauth2.OAuthCallbackHandler.check_state()
  • oauthenticator.oauth2.OAuthCallbackHandler.get_next_url()
  • oauthenticator.oauth2.OAuthLoginHandler._state
  • oauthenticator.oauth2.OAuthLoginHandler.set_state_cookie()
  • OAuthenticator.oauth2.authorize_url

If we decide to manage the handling of XSRF state (which is an opaque value and solely for security) ourselves, we can drop the dependency to oauthenticator.

Ping @consideRatio, @minrk

Add an LTI 1.1 Tool Provider XML endpoint

Proposed change

Add a new LTI 1.1 handler to dynamically display the LTI 1.1 configuration XML.

Alternative options

To add an LTI Link for a Tool Consumer users have to manually include the LTI 1.1 settings values to the LMS' LTI 1.1 configuration page, manually create an XML based on the standard LTI 1.1 specification to paste it into the LMS configuration page, among others.

Who would use this feature?

Any user that needs to quickly add the JupyterHub as a Tool Provider to a Tool Consumer (LMS).

Suggest a solution

Create a handler and publish the correct XML with a web-accessible endpoint based on JupyterHub's URL, placements settings, etc.

Is LTI13Authenticator specific to IllumiDesk?

I lack a lot of background to understand this project as I've never used LTI1.1 or LTI1.3 for authentication, but I saw the following and become a bit concerned.

keys = {
"title": "IllumiDesk",

Is there anyone besides IllumiDesk using the LTI1.3 authenticator? @yuvipanda perhaps?

I'm not confident what it means that the LTI13ConfigHandler serving responses for /lti13/config respons with json including a title and description associated with IllumiDesk hardcoded by default.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.