GithubHelp home page GithubHelp logo

hypothesis / lms Goto Github PK

View Code? Open in Web Editor NEW
44.0 18.0 12.0 21.93 MB

LTI app for integrating with learning management systems

License: BSD 2-Clause "Simplified" License

Makefile 0.20% Python 76.95% Shell 0.03% Mako 0.01% CSS 0.01% JavaScript 10.36% Dockerfile 0.04% Gherkin 0.70% SCSS 0.38% Jinja 3.50% TypeScript 7.72% PLpgSQL 0.11%

lms's Introduction

LMS

An app for integrating Hypothesis with Learning Management Systems (LMS's).

See also:

Setting up Your LMS Development Environment

First you'll need to install:

  • Git. On Ubuntu: sudo apt install git, on macOS: brew install git.
  • GNU Make. This is probably already installed, run make --version to check.
  • pyenv. Follow the instructions in pyenv's README to install it. The Homebrew method works best on macOS. The Basic GitHub Checkout method works best on Ubuntu. You don't need to set up pyenv's shell integration ("shims"), you can use pyenv without shims.
  • Docker Desktop. On Ubuntu follow Install on Ubuntu. On macOS follow Install on Mac.
  • Node and npm. On Ubuntu: sudo snap install --classic node. On macOS: brew install node.
  • Yarn: sudo npm install -g yarn.

Then to set up your development environment:

git clone https://github.com/hypothesis/lms.git
cd lms
make services
make devdata
make help

To run LMS locally run make dev and visit http://localhost:8001.

Changing the Project's Python Version

To change what version of Python the project uses:

  1. Change the Python version in the cookiecutter.json file. For example:

    "python_version": "3.10.4",
  2. Re-run the cookiecutter template:

    make template
    
  3. Re-compile the requirements/*.txt files. This is necessary because the same requirements/*.in file can compile to different requirements/*.txt files in different versions of Python:

    make requirements
    
  4. Commit everything to git and send a pull request

Changing the Project's Python Dependencies

To Add a New Dependency

Add the package to the appropriate requirements/*.in file(s) and then run:

make requirements

To Remove a Dependency

Remove the package from the appropriate requirements/*.in file(s) and then run:

make requirements

To Upgrade or Downgrade a Dependency

We rely on Dependabot to keep all our dependencies up to date by sending automated pull requests to all our repos. But if you need to upgrade or downgrade a package manually you can do that locally.

To upgrade a package to the latest version in all requirements/*.txt files:

make requirements --always-make args='--upgrade-package <FOO>'

To upgrade or downgrade a package to a specific version:

make requirements --always-make args='--upgrade-package <FOO>==<X.Y.Z>'

To upgrade all packages to their latest versions:

make requirements --always-make args=--upgrade

HTTPS/SSL setup

Using self signed certificates with HTTPS

By default make dev runs the web application on two ports: 8001 for HTTP and 48001 for HTTPS with a self-signed certificate.

Using HTTPS is required in most LMS's for LTI 1.3 and for example in D2L it's required for any usage of their API in all LTI versions.

To use HTTPS you'll need to instruct your browser to trust the self-signed certificate.

In Chrome you can do do this with the following flag:

chrome://flags/#allow-insecure-localhost

Bypassing the browser's "unsafe scripts" (mixed content) blocking

If you use our hosted Canvas instance at https://hypothesis.instructure.com/ to test your local dev instance of the app you'll get "unsafe scripts" or "mixed content" warnings from your browser. This is because hypothesis.instructure.com uses https but your local dev app, which is running in an iframe in hypothesis.instructure.com, only uses http.

You'll see a blank iframe in Canvas where the app should be, along with a warning about "trying to launch insecure content" like this:

"Trying to launch insecure content" error

If you open the browser's developer console you should see an error message like:

Mixed Content: The page at 'https://hypothesis.instructure.com/...' was loaded over HTTPS,
but requested an insecure form action 'http://localhost:8001/...'. This request has been
blocked; the content must be served over HTTPS.

Fortunately you can easily bypass this mixed content blocking by your browser. You should also see an "Insecure content blocked" icon in the top right of the location bar:

"Insecure content blocked" dialog

Click on the Load unsafe scripts link and the app should load successfully.

Localhost alias

Some services that the LMS app integrates with (eg. Canvas Studio) do not allow the use of localhost in OAuth callback URLs. For this reason we use https://hypothesis.local URLs in some places. To make this work you must declare hypothesis.local as an alias for 127.0.0.1 in your /etc/hosts file.

Overview and code design

There are three presentations for developers that describe what the Hypothesis LMS app is and how it works. The speaker notes in these presentations also contain additional notes and links:

  1. LMS App Demo & Architecture
  2. LMS App Code Design Patterns
  3. Speed Grader Workshop (about the design of the first version of our Canvas Speed Grader support)

lms's People

Contributors

acelaya avatar chdorner avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar dittonjs avatar dmfine avatar esanzgar avatar github-actions[bot] avatar indigobravo avatar jbasdf avatar jon-betts avatar judell avatar keithrichardsaj avatar klemay avatar lms007 avatar lyzadanger avatar marcospri avatar mattdricker avatar nick-benoit14 avatar pyup-bot avatar robertknight avatar seanh avatar segdeha avatar sheetaluk 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

Watchers

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

lms's Issues

`make lint` failing on master

The linters are currently failing on master and need to be fixed.

In order for our deployment and other integrations (e.g. pyup) to work correctly the linters (make lint) always need to be passing on master, so do development on branches and don't merge the branches until the linters are passing. If you create a github pull request for a branch, then Travis will automatically run the linters and report the result on your pull request, when Travis passes then merge the PR.

Internal Server Error or failed login when logging in to /reports

Go to https://qa-lms.hypothes.is/reports and login with the correct username and password, and you'll get a 500 Internal Server Error.

https://sentry.io/hypothesis/lms-backend/issues/419033074/

HTTPNotFound: The resource could not be found.
  File "pyramid/tweens.py", line 12, in _error_handler
    response = request.invoke_exception_view(exc_info)
  File "pyramid/view.py", line 756, in invoke_exception_view
    raise HTTPNotFound

TypeError: can't concat bytes to NoneType
(24 additional frame(s) were not displayed)
...
  File "pyramid/authentication.py", line 621, in remember
    return self.cookie.remember(request, userid, **kw)
  File "pyramid/authentication.py", line 992, in remember
    cookie_value = ticket.cookie_value()
  File "pyramid/authentication.py", line 676, in cookie_value
    v = '%s%08x%s!' % (self.digest(), int(self.time),
  File "pyramid/authentication.py", line 673, in digest
    self.user_data, self.hashalg)
  File "pyramid/authentication.py", line 752, in calculate_digest
    tokens + b'\0' + user_data)

TypeError: can't concat bytes to NoneType

If I try to login to https://lms.hypothes.is/reports (production rather than qa) then an attempt to login with the correct username and password just reloads the login page with a failed login message.

Don't hardcode https://via.hypothes.is

https://via.hypothes.is/ is hardcoded in a couple of places:

'hypothesis_url': 'https://via.hypothes.is/' + document_url,
and
'hypothesis_url': 'https://via.hypothes.is/' + instance.document_url,
. These should instead use the already-existing via_url Pyramid config setting, which comes from the VIA_URL environment variable, so that https://qa-via.hypothes.is can be used in the QA environment, a development instance of Via can be used in dev environments, etc.

Fix CSS fonts

Remove repetitive font rules from CSS and define a default font on the root (html) element instead.

  • Remove font-family: 'Roboto', sans-serif; from individual scattered selectors and put it on html
  • Set a reasonable default font size on the html element

Error pages are unstyled

Error pages (that will be shown to users in Canvas) look like this:

screenshot from 2018-07-25 17-45-33

The app contains the sad-annotation.svg file for error pages and an error.html.jinja2 template that uses it. But there is no code in the app that uses this template. The app previously had a working error view of which these were a part, but it was deleted it in this 1000+ line, 26 file commit "remove old code".

Put the error view (views/error.py in the commit linked above) back so that error pages are styled again.

Layout of assignment creation form is confusing

Placing the Use Google Picker button immediately to the right of the Public Document URL field makes it look as if the Use Google Picker button does something with the Public Document URL value / as if I'm supposed to click Use Google Picker after pasting a URL into Public Document URL:

screenshot from 2018-08-02 11-02-01

In fact what you're supposed to do is click the further-away Submit button after pasting into Public Document URL, and ignore the nearby Use Google Picker button.

I'm unable to test it currently because it's broken but I thinkUse Google Picker does have something to do with Public Document URL: if you use the Use Google Picker and pick a document from Google then it pastes the URL of the selected document into Public Document URL. But this is unfortunate because we're actually labelling the secret Google Drive URL as Public Document URL.

The note sharing a file in this way creates a shared link for you course to access is also directly above the Public Document URL field but is completely irrelevant to that field: if you paste in a public URL we don't "create a link" at all, we use the one you pasted in. (I think it's questionable whether having this arguably misleading note on the Google Drive and Canvas Files options is a good idea either.)

What we're actually trying to present to the user here is two separate options:

  1. Paste in a URL
  2. Choose a document from Google
  3. Choose a document from Canvas (if Canvas Files enabled)

The layout should make it clear that two (or three) distinct and separate options are available, rather than running the controls for the different options together and confusing things.

P.S. Don't use Submit as a label, something more specific like Create Assignment is better.

Crash when non-ASCII chars in LMS_SECRET envvar

Apparently it's important to encode this envvar as ASCII bytes rather than using it as unicode:

'aes_secret': env_setting('LMS_SECRET').encode('ascii')[0:16]
Though I'm not sure why the ASCII encoding in particular is used? Anyway it's fine to require it to be ASCII but when it's not it should stop and give a helpful error message, not crash with UnicodeEncodeError:

$ ~/P/lms> set -x LMS_SECRET  🤷
$ ~/P/lms> make dev
gunicorn --paste conf/development.ini
[2018-07-27 14:15:40 +0100] [2715] [INFO] Starting gunicorn 19.7.1
[2018-07-27 14:15:40 +0100] [2715] [INFO] Listening at: https://0.0.0.0:8001 (2715)
[2018-07-27 14:15:40 +0100] [2715] [INFO] Using worker: sync
[2018-07-27 14:15:40 +0100] [2718] [INFO] Booting worker with pid: 2718
[2018-07-27 14:15:40 +0100] [2718] [ERROR] Exception in worker process
Traceback (most recent call last):
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/gunicorn/arbiter.py", line 578, in spawn_worker
    worker.init_process()
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/gunicorn/workers/base.py", line 126, in init_process
    self.load_wsgi()
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/gunicorn/workers/base.py", line 135, in load_wsgi
    self.wsgi = self.app.wsgi()
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/gunicorn/app/base.py", line 67, in wsgi
    self.callable = self.load()
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 63, in load
    return self.load_pasteapp()
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 59, in load_pasteapp
    return load_pasteapp(self.cfgurl, self.relpath, global_conf=self.cfg.paste_global_conf)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/gunicorn/app/pasterapp.py", line 69, in load_pasteapp
    global_conf=global_conf)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/paste/deploy/loadwsgi.py", line 247, in loadapp
    return loadobj(APP, uri, name=name, **kw)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/paste/deploy/loadwsgi.py", line 272, in loadobj
    return context.create()
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/paste/deploy/loadwsgi.py", line 710, in create
    return self.object_type.invoke(self)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/paste/deploy/loadwsgi.py", line 146, in invoke
    return fix_call(context.object, context.global_conf, **context.local_conf)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/paste/deploy/util.py", line 55, in fix_call
    val = callable(*args, **kw)
  File "/home/seanh/Projects/lms/lms/app.py", line 14, in create_app
    config = configure(settings=settings)
  File "/home/seanh/Projects/lms/lms/config/__init__.py", line 40, in configure
    'aes_secret': env_setting('LMS_SECRET').encode('ascii')[1:16],
UnicodeEncodeError: 'ascii' codec can't encode character '\U0001f937' in position 0: ordinal not in range(128)
[2018-07-27 14:15:40 +0100] [2718] [INFO] Worker exiting (pid: 2718)
[2018-07-27 14:15:40 +0100] [2715] [INFO] Shutting down: Master
[2018-07-27 14:15:40 +0100] [2715] [INFO] Reason: Worker failed to boot.
Makefile:11: recipe for target 'dev' failed
make: *** [dev] Error 3

Please improve Google Drive setup documentation

The documentation in the README for setting up the Google Drive integration is pretty minimal, and as a result it takes a long time to figure out how to get it working.

Some of the details that are missing:

  • What settings should the API key have under Key restriction and under Google Developer Console?
  • What settings should the OAuth 2.0 Client ID have under Authorized JavaScript origins and Authorized redirect URIs?
  • It doesn't explain how to navigate within the Google Developer Console to the various screens where things need to be done
  • There should be screenshots of the Google Developer Console showing what to do

Add jwt_secret to the Pyramid config

The "jwt_secret" setting is read directly in a couple of different places:

jwt_token = jwt.encode(data, env_setting('JWT_SECRET'), 'HS256').decode('utf-8')

decoded_jwt = jwt.decode(request.params['jwt_token'], env_setting('JWT_SECRET'), algorithms=['HS256'])

The way the lms.config module is intended to be used is that lms/config/__init__.py calls env_setting(), once per setting, adding this settings to the Pyramid config registry (as it currently does for the via_url setting for example). Code elsewhere then just reads the settings from the Pyramid settings object (request.registry.settings). lms/config/settings.py shouldn't be imported by any module other than lms/config/__init__.py.

Crash when oauth_consumer_key invalid

If you post to the /lti_launches URL with an invalid "oauth_consumer_key" param in the request it crashes.

For example simply making a POST request to https://lms.hypothes.is/lti_launches with a random "oauth_consumer_key" in the request body will crash the production app:

$ http --form POST 'https://lms.hypothes.is/lti_launches' oauth_consumer_key=foo
HTTP/1.1 500 Internal Server Error
CF-RAY: 43f0123a1ad2364d-MAN
Connection: keep-alive
Content-Length: 141
Content-Type: text/html
Date: Mon, 23 Jul 2018 18:04:45 GMT
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Server: cloudflare
Set-Cookie: __cfduid=d701463e08bd827f0579a12eae43e3d741532369084; expires=Tue, 23-Jul-19 18:04:44 GMT; path=/; domain=.hypothes.is; HttpOnly
Strict-Transport-Security: max-age=15552000; includeSubDomains; preload
X-Content-Type-Options: nosniff

<html>
  <head>
    <title>Internal Server Error</title>
  </head>
  <body>
    <h1><p>Internal Server Error</p></h1>
    
  </body>
</html>

Related:

Traceback:

Traceback (most recent call last):
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_debugtoolbar/toolbar.py", line 248, in toolbar_tween
    response = _handler(request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_debugtoolbar/panels/performance.py", line 58, in resource_timer_handler
    result = handler(request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_tm/__init__.py", line 171, in tm_tween
    reraise(*exc_info)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_tm/compat.py", line 36, in reraise
    raise value
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_tm/__init__.py", line 136, in tm_tween
    response = handler(request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/tweens.py", line 41, in excview_tween
    response = _error_handler(request, exc)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/tweens.py", line 16, in _error_handler
    reraise(*exc_info)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/compat.py", line 148, in reraise
    raise value
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/tweens.py", line 39, in excview_tween
    response = handler(request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/router.py", line 156, in handle_request
    view_name
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/view.py", line 642, in _call_view
    response = view_callable(context, request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/viewderivers.py", line 390, in attr_view
    return view(context, request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/viewderivers.py", line 368, in predicate_wrapper
    return view(context, request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/viewderivers.py", line 410, in viewresult_to_response
    result = view(context, request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/viewderivers.py", line 148, in _requestonly_view
    response = view(request)
  File "/home/seanh/Projects/lms/lms/util/lti_launch.py", line 50, in wrapper
    shared_secret = get_secret(request, consumer_key)
  File "/home/seanh/Projects/lms/lms/util/lti_launch.py", line 16, in default_get_secret
    instance = get_application_instance(request.db, consumer_key)
  File "/home/seanh/Projects/lms/lms/util/lti_launch.py", line 10, in get_application_instance
    ai.ApplicationInstance.consumer_key == consumer_key
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2820, in one
    raise orm_exc.NoResultFound("No row was found for one()")
sqlalchemy.orm.exc.NoResultFound: No row was found for one()```

Crash when LMS_SECRET environment variable missing

The app should crash on startup with a helpful error message if a required config setting is missing. This can be one using the required=True argument to env_setting. This isn't done for the aes_secret / LMS_SECRET setting, which crashes with AttributeError instead:

    def configure(settings):
        """Return a Configurator for the Pyramid application."""
        if settings is None:
            settings = {}
    
        # Settings from the config file are extended / overwritten by settings from
        # the environment.
        env_settings = {
            # The URL of the https://github.com/hypothesis/via instance to
            # integrate with.
            'via_url': env_setting('VIA_URL', required=True),
            'jwt_secret': env_setting('JWT_SECRET', required=True),
            'google_client_id': env_setting('GOOGLE_CLIENT_ID'),
            'google_developer_key': env_setting('GOOGLE_DEVELOPER_KEY'),
            'google_app_id': env_setting('GOOGLE_APP_ID'),
            'lms_secret': env_setting('LMS_SECRET'),
            'hashed_pw': env_setting('HASHED_PW'),
            'salt': env_setting('SALT'),
            'username': env_setting('USERNAME'),
            # We need to use a randomly generated 16 byte array to encrypt secrets.
            # For now we will use the first 16 bytes of the lms_secret
>           'aes_secret': env_setting('LMS_SECRET').encode('ascii')[0:16]
        }
E       AttributeError: 'NoneType' object has no attribute 'encode'

lms/config/__init__.py:40: AttributeError

(P.S. Why does this config setting have two different names?)

/reports page is crashing: ProgrammingError: (psycopg2.ProgrammingError) relation "lti_launches" does not exist

https://qa-lms.hypothes.is/reports is crashing. It looks like a DB migration needs to be run?

https://sentry.io/hypothesis/lms-backend/issues/421013880/

ProgrammingError: relation "lti_launches" does not exist
LINE 2: FROM lti_launches
             ^

  File "sqlalchemy/engine/base.py", line 1182, in _execute_context
    context)
  File "sqlalchemy/engine/default.py", line 470, in do_execute
    cursor.execute(statement, parameters)
  File "newrelic/hooks/database_psycopg2.py", line 35, in execute
    **kwargs)
  File "newrelic/hooks/database_dbapi2.py", line 25, in execute
    *args, **kwargs)

ProgrammingError: (psycopg2.ProgrammingError) relation "lti_launches" does not exist
LINE 2: FROM lti_launches
             ^
 [SQL: 'SELECT lti_launches.id AS lti_launches_id, lti_launches.created AS lti_launches_created, lti_launches.context_id AS lti_launches_context_id, lti_launches.lti_key AS lti_launches_lti_key \nFROM lti_launches']
(32 additional frame(s) were not displayed)
...
  File "sqlalchemy/util/compat.py", line 186, in reraise
    raise value.with_traceback(tb)
  File "sqlalchemy/engine/base.py", line 1182, in _execute_context
    context)
  File "sqlalchemy/engine/default.py", line 470, in do_execute
    cursor.execute(statement, parameters)
  File "newrelic/hooks/database_psycopg2.py", line 35, in execute
    **kwargs)
  File "newrelic/hooks/database_dbapi2.py", line 25, in execute
    *args, **kwargs)

ProgrammingError: (psycopg2.ProgrammingError) relation "lti_launches" does not exist
LINE 2: FROM lti_launches
             ^
 [SQL: 'SELECT lti_launches.id AS lti_launches_id, lti_launches.created AS lti_launches_created, lti_launches.context_id AS lti_launches_context_id, lti_launches.lti_key AS lti_launches_lti_key \nFROM lti_launches']

lms.secret, hashed_pw, salt and username settings need to be read from environment variables

Our production infrastructure configures apps by setting environment variables (and not by injecting a modified production.ini file), so any config setting that may need to vary on a per-instance basis needs to be read from environment variables. The lms.config module contains code for reading environment variable-based config settings in one place and adding them to Pyramid's request.registry.settings object. lms.secret, hashed_pw, salt and username need to be done in lms.config, the same as how VIA_URL is done.

The production.ini file is public in this git repo, so it can only contain config settings and defaults that aren't private and that don't vary from one deployment of the app to another.

`make test` failing on master

The tests are currently failing on master and need to be fixed.

In order for our deployment and other integrations (e.g. pyup) to work correctly the tests (make test) always need to be passing on master, so do development on branches and don't merge the branches until the tests are passing. If you create a github pull request for a branch, then Travis will automatically run the tests and report the result on your pull request, when Travis passes then merge the PR.

Remove "lms_secret" setting and rename LMS_SECRET environment variable

The LMS_SECRET envvar is used for two things:

  1. It's the value of the "lms_secret" setting

  2. The first 16 chars of the LMS_SECRET envvar also double as the value of the "aes_secret" setting

The "lms_secret" setting doesn't seem to be used, so it should be removed:

$ ag -s lms_secret lms
lms/config/__init__.py
34:        'lms_secret': env_setting('LMS_SECRET'),
39:        # For now we will use the first 16 bytes of the lms_secret
57:        settings['lms_secret'], callback=groupfinder,

That will leave LMS_SECRET being used only as the value of the "aes_secret" setting, so the envvar so it can be renamed to AES_SECRET or perhaps to something more sensible (a name that actually describes what the value is used for).

We might also want to:

  • Remove the 16 character truncation. Why not allow an encryption secret to be longer than 16 chars?
  • Enforce a minimum length for security?
  • Make the variable required, instead of optional?

Setup Sentry crash reporting for frontend code

We use Sentry for tracking errors in both backend (Python) and frontend (JS) code.

I have created a Sentry project for the lms app's frontend code. The "public DSN" configuration parameter should come from a SENTRY_DSN_FRONTEND env var. Search for "SENTRY_DSN_FRONTEND" in the h app to see how we do it there.

The setup steps are here: https://docs.sentry.io/clients/javascript/install/ . Just the basic error capture should be fine, no framework integrations are needed.

Validate all LTI launch params in one place

Create an lms/validation/ package where all input validation (to start with, all validation of LTI launch request parameters) is done in one central place. Views will import lms.validation.* functions and call them, passing in the request parameters, to validate the params and error if a param is invalid or missing. Thereafter any further view code or other code called by the views can assume that the params are valid (e.g. assume that required parameters are present).

Don't ask user to click "Authorize" on every page load

The app is causing Canvas to show the user an OAuth 2 authorization dialog and ask the user to click Authorize whenever:

  • An assignment is created or edited (regardless of whether a public URL, Canvas Files or Google Drive assignment)
  • A Canvas files assignment is launched

screenshot from 2018-08-13 17-32-22

It does not show the authorize dialog when a public URL (HTML or PDF) assignment is launched, though.

Repeatedly showing the authorize dialog is annoying for the user, reduces security, and creates a lot of access tokens in Canvas.

  • The app should re-use the access token that it has saved for that user in the DB.
  • When the saved access token has expired the app should use the user's saved refresh token to get a new access token without user interaction.
  • The user should only be asked to click Authorize the first time they launch the app and, thereafter, only if both the access token and refresh token have expired.

Crash when no "oauth_consumer_key" parameter

If you post to the /lti_launches URL without an "oauth_consumer_key" param in the request it crashes.

For example simply making an empty POST request to https://lms.hypothes.is/lti_launches will crash the production app:

$ http POST 'https://lms.hypothes.is/lti_launches'
HTTP/1.1 500 Internal Server Error
CF-RAY: 43eff4d3de34bfe5-MAN
Connection: keep-alive
Content-Length: 141
Content-Type: text/html
Date: Mon, 23 Jul 2018 17:44:40 GMT
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Server: cloudflare
Set-Cookie: __cfduid=ddb7634f46322be02ffe9025535c7630f1532367880; expires=Tue, 23-Jul-19 17:44:40 GMT; path=/; domain=.hypothes.is; HttpOnly
Strict-Transport-Security: max-age=15552000; includeSubDomains; preload
X-Content-Type-Options: nosniff

<html>
  <head>
    <title>Internal Server Error</title>
  </head>
  <body>
    <h1><p>Internal Server Error</p></h1>
    
  </body>
</html>

Traceback:

Traceback (most recent call last):
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_debugtoolbar/toolbar.py", line 248, in toolbar_tween
    response = _handler(request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_debugtoolbar/panels/performance.py", line 58, in resource_timer_handler
    result = handler(request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_tm/__init__.py", line 171, in tm_tween
    reraise(*exc_info)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_tm/compat.py", line 36, in reraise
    raise value
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid_tm/__init__.py", line 136, in tm_tween
    response = handler(request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/tweens.py", line 41, in excview_tween
    response = _error_handler(request, exc)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/tweens.py", line 16, in _error_handler
    reraise(*exc_info)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/compat.py", line 148, in reraise
    raise value
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/tweens.py", line 39, in excview_tween
    response = handler(request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/router.py", line 156, in handle_request
    view_name
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/view.py", line 642, in _call_view
    response = view_callable(context, request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/viewderivers.py", line 390, in attr_view
    return view(context, request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/viewderivers.py", line 368, in predicate_wrapper
    return view(context, request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/viewderivers.py", line 410, in viewresult_to_response
    result = view(context, request)
  File "/home/seanh/.virtualenvs/lms/lib/python3.6/site-packages/pyramid/viewderivers.py", line 148, in _requestonly_view
    response = view(request)
  File "/home/seanh/Projects/lms/lms/util/lti_launch.py", line 49, in wrapper
    consumer_key = lti_params['oauth_consumer_key']
KeyError: 'oauth_consumer_key'

Review and improve Sentry crash reporting

The crash reports that the app is sending to Sentry seem very minimal, often not containing enough data to figure out what is happening and to who. Review what data we're sending to Sentry and what we're stripping, and consider changing it to make the issues in Sentry more useful.

Python and JavaScript dependencies need to be updated and kept up to date

The app's Python and JavaScript dependencies haven't been kept up to date and the pyup bot is complaining that we have Python dependency versions with known security vulnerabilities. The dependencies need to be brought up to date and a system put in place so that they stay up to date in future.

We should do this using Dependabot as we do for bouncer.

Assignment view asks to choose a document every time

  1. Install the app in a Canvas instance
  2. Create an assignment using the app as an external tool
  3. Navigate to the assignment in Canvas
  4. The app will ask you for a public document URL, or to use Google Picker. Use Google Picker and choose a document.
  5. The document and Hypothesis client loads in the assignment view
  6. Browse to the assignment again, e.g. in another tab. The app will ask you for a public document URL, or to use Google Picker. Use Google Picker and choose a document...

I imagine you should only have to set the assignment's document once, when you create the assignment, then it should be remembered?

I'm doing all of the above when logged in to Canvas as a teacher, haven't tested from the student's point of view.

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.