hypothesis / hypothesis/lms

LTI launch verification error: Expired timestamp: given X and now Y has a greater difference than threshold 300

Open
#495 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
53
Forks
16
Avg merge
14d 5h
Merged PRs (30d)
14

Description

https://sentry.io/organizations/hypothesis/issues/903786446/?project=259908&referrer=github_plugin

Our LTl launch verification fails much more often than you'd expect it to with an error message saying that the request's `oauth_timestamp` parameter was expired (more than 5 mins old).

I don't think the LTI/OAuth 1 verification itself is wrong: it looks like the timestamps are in fact too old. It's actually correct behaviour for our app to reject launch requests that have old timestamps.

I don't think this is a one-off or a hacker, bot, or misconfigured LMS sending us invalid LTI launches either: This happens about 10 times per day, we have a few hundred instances of this in Sentry, and the requests (claim to) come from a variety of different LMS's including Canvas instances run by Instructure and non-Canvas LMS's.

We've ruled out clock-drift on our servers: the received timestamps on the Sentry events (which we're assuming are added by Sentry itself on their server) are within one second of the current time as given by the error message from our server.

Looking at the error messages the timestamps are not just minutes but hours out of date when this happens.

See also:

* [Slack thread](https://hypothes-is.slack.com/archives/C074BUPEG/p1552922789000200)

### What I think is happening / how you can reproduce this

1. Create a Hypothesis assignment in Canvas or any LMS and check the box to have the assignment **open in a new tab**
2. Launch the assignment
3. Wait for more than 5 mins
4. Click the browser's Reload page button

The browser will resubmit the launch form, including the old `oauth_timestamp` field, and our app will crash with the traceback below.

You can also reproduce it by browsing to other pages for more than five mins then using the browser's Back button to go back to the Hypothesis assignment page. This will also cause the browser to resubmit the old launch form.

It's possible that laptop / device, app, or tab suspend and wake can also trigger it.

### How I think we should fix this

This is a bug in our LMS app. The app should implement the web app best practice called "redirect after post" or Post Redirect Get (PRG) (google it): In order to prevent exactly these kinds of form resubmissions, after an app receives a `POST` request (such as an LTI launch request) it should verify the request, stash the params in some kind of session, and then return a redirect to an "assignment page" URL that is a `GET` for a different URL than the launch request form submission URL. If the user reloads the page they'll be re-`GET`ting this assignment page, not resubmitting the launch form. I believe the Back button variation may also be fixed by this.

This [official guide to handling launch requests](https://www.imsglobal.org/learning-tools-interoperability-verifying-launch-messages) also tells you to do this redirect.

### Also

Invalid LTI launch requests will happen (people can send us invalid requests). They shouldn't be crashes reported to Sentry. Log them; Return a nice and helpful error page; Don't crash, and don't send anything to Sentry.

### Traceback

```
LTIOAuthError:
(6 additional frame(s) were not displayed)
...
File "pyramid/viewderivers.py", line 144, in _requestonly_view
response = view(request)
File "lms/util/_lti_launch.py", line 23, in wrapper
request.find_service(name="launch_verifier").verify()
File "lms/services/launch_verifier.py", line 45, in verify
raise self._exception # pylint: disable=raising-bad-type
File "lms/services/launch_verifier.py", line 38, in verify
self._verify()
File "lms/services/launch_verifier.py", line 72, in _verify
raise LTIOAuthError()

LTIException: OAuth error: Please check your key and secret
File "lms/services/launch_verifier.py", line 69, in _verify
dict(self._request.params),
File "pylti/common.py", line 306, in verify_request_common
raise LTIException("OAuth error: Please check your key and secret")

Error: Expired timestamp: given 1552895690 and now 1552999863 has a greater difference than threshold 300
File "pylti/common.py", line 302, in verify_request_common
oauth_server.verify_request(oauth_request, consumer, None)
File "__init__.py", line 714, in verify_request
self._check_signature(request, consumer, token)
File "__init__.py", line 754, in _check_signature
self._check_timestamp(timestamp)
File "__init__.py", line 778, in _check_timestamp
self.timestamp_threshold))
```

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.