Skip to content

Commit

Permalink
I2 login user after the LTI launch (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
pushyamig authored Jun 18, 2024
1 parent 63ac695 commit 742a026
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 31 deletions.
5 changes: 3 additions & 2 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ MYSQL_PORT=3306
# Django settings
# python manage.py shell -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
SECRET_KEY='you-django-key'
DEBUG=True
DJANGO_DEBUG=True
DJANGO_LOG_LEVEL=DEBUG
TZ=America/Detroit
CSRF_TRUSTED_ORIGINS=https://*.instructure.com,https://*.umich.edu
ALLOWED_HOSTS=.loophole.site,.ngrok-free.app
ALLOWED_HOSTS=.loophole.site,.ngrok-free.app, 127.0.0.1, localhost
58 changes: 52 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,64 @@
# canvas-lti-redirect-tool
This is Canvas LTI redirect tool which use the CAI developed [LTI library](https://pypi.org/project/django-lti/)

## Generating Django security key
### Prerequisites

To follow the instructions below, you will at minimum need the following:
1. **[Docker Desktop](https://www.docker.com/products/docker-desktop/)**.
1. **[Git](https://git-scm.com/downloads)**
### Installation and Setup
1. You need to web Proxy like Loophole or ngrok to run the application. Loophole offers custom domain
```sh
loophole http 6000 --hostname <your-host>
```
1. Copy the `.env.sample` file as `.env`.
```sh
cp .env.sample .env
1. Examine the `.env` file. It will have the suggested default environment variable settings,
mostly just MySQL information as well as locations of other configuration files.
1. Start the Docker build process (this will take some time).
```sh
docker compose build
```

1. Start up the web server and database containers.
```sh
docker compose up
```

1. generate Django secret using below command
```sh
python manage.py shell -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
```

## LTI install
1. Need to run this command once in order for LTI to work
1. Need to run this command once docker container is up in order for LTI to work. This is important step otherwise the LTI tool launch won't happen
```sh
docker exec -it clrt_web /bin/bash -c \
"python manage.py rotate_keys"
```
2. Create superuser via using `python manage.py createsuperuser', need to run a proxy like loophole or ngrok for LTI installation and login with that user.
https://<url>/admin/
4. Use `LTIRegistration` to configure an LTI tool
```
2. Create superuser via using `python manage.py createsuperuser', need to run a proxy like loophole or ngrok for LTI installation and login with that user. Go to https://{app-hostname}/admin/.
3. Go to Canvas instance, choose Developer Keys in admin site
4. Add LTI Key
5. Choose Paste JSON method
6. Goto `LTIRegistration` to configure an LTI tool from admin console. This will create the `uuid` automatically. Hold on to that value and update the `OpenID Connect Initiation Url` in the LTI tool registration from Canvas with this id.
` for Eg: https://clrt-local.loophole.site/init/0b54a91b-cac6-4c96-ba1e/`
7. use the `setup/lti-config.json` for registing the LTI tool. Replace all the `{app-hostname}` with your web proxy url and <uuid:lti-registration> with UUID value from LTI tool registration.
8. Configure the LTI configuration from CLRT tool going to admin again. Give the following value. Note: `<canvas-instance>: ['canvas.test', 'canvas.beta']`
1. Name: any name
2. Issuer: https://<canvas-instance>.instructure.com
2. Client ID: (get this from Platform)
3. Auth URL: https://<canvas-instance>.instructure.com/api/lti/authorize_redirect
4. Access token URL: https://<canvas-instance>.instructure.com/login/oauth2/token
5. Keyset URL: https://<canvas-instance>.instructure.com/api/lti/security/jwks
6. DEPLOYMENT ID: get this as it is described the step 7 and paste
9. Save
10. Go to the Canvas(platform) add the LTI tool at account/course level and copy the deployment id by clicking the setting button next to it.

## Make a user superuser
1. go to the `auth_user` table and set `is_superuser` and `is_staff` to `1` or `true` this will give the logged user access to admin interface



50 changes: 46 additions & 4 deletions backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
https://docs.djangoproject.com/en/5.0/ref/settings/
"""

import os
import os, logging
from pathlib import Path
from decouple import config
# from csp.constants import SELF, UNSAFE_INLINE

logger = logging.getLogger(__name__)
logging.basicConfig(level='INFO')

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
print(BASE_DIR)


# Quick-start development settings - unsuitable for production
Expand All @@ -27,11 +29,12 @@
# Read the CSRF_TRUSTED_ORIGINS variable from the .env file
csrf_trusted_origins = config('CSRF_TRUSTED_ORIGINS', '')
CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in csrf_trusted_origins.split(',')]
print(CSRF_TRUSTED_ORIGINS)


# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = config('DJANGO_DEBUG', default=False, cast=bool)
print(DEBUG)
RANDOM_PASSWORD_DEFAULT_LENGTH = 32

allowed_hosts = config('ALLOWED_HOSTS', '')
ALLOWED_HOSTS = [host.strip() for host in allowed_hosts.split(',')]
Expand Down Expand Up @@ -78,13 +81,52 @@
"BACKEND": 'whitenoise.storage.CompressedManifestStaticFilesStorage',
},
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
"generic": {
"format": "%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s",
"datefmt": "[%Y-%m-%d %H:%M:%S %z]",
"class": "logging.Formatter",
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'generic',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'propagate': False,
'level': config('DJANGO_LOG_LEVEL', 'DEBUG'),
},
'rules': {
'handlers': ['console'],
'propagate': False,
'level': 'INFO',
},
'': {
'level': 'WARNING',
'handlers': ['console'],
},

},
'root': {
'level': 'INFO',
'handlers': ['console']
},
}

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, "templates")],
'APP_DIRS': True,
'OPTIONS': {
'debug': DEBUG,
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
Expand Down
19 changes: 8 additions & 11 deletions lti_redirect/static/home.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,26 @@
}

.navbar a {
text-decoration: none;
color: inherit;
}

.navbar-brand {
font-size: 1.2em;
font-weight: 600;
text-decoration: none;
}
.navbar-user{
font-size: 1em;
font-weight: 600;
padding-left: 30px;

}
.navbar-item {
font-variant: small-caps;
margin-left: 30px;
float: right;
text-decoration: underline;
}

.body-content {
padding: 5px;
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
input[name=message] {
width: 80%;
}

.message_list th,td {
text-align: left;
padding-right: 15px;
}
13 changes: 13 additions & 0 deletions lti_redirect/templates/error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>{% block title %}{% endblock %}</title>
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'home.css' %}"/>
</head>

<body>
<p>Error occured during launch the Canvas LTI Redirect Tool</p>
</body>
</html>
6 changes: 6 additions & 0 deletions lti_redirect/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
<body>
<div class="navbar">
<a href="{% url 'home' %}" class="navbar-brand">LTI Redirect TOOL</a>
{% if user.is_authenticated and user.is_superuser %}
<a class="navbar-item" href="{% url 'admin:index' %}">Admin</a>
{% endif %}
{% if user.is_authenticated %}
<span class="navbar-user">{{ user.username }}</span>
{% endif %}
</div>

<div class="body-content">
Expand Down
1 change: 1 addition & 0 deletions lti_redirect/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from lti_redirect.views import ApplicationLaunchView
urlpatterns = [
path('', views.get_home_template, name = 'home'),
path('error', views.error, name="error" ),

# LTI launch urls
path(".well-known/jwks.json", jwks, name="jwks"),
Expand Down
44 changes: 36 additions & 8 deletions lti_redirect/views.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import random, logging
import string
import requests
import jwt
import django.contrib.auth
from django.conf import settings
from django.shortcuts import redirect, render
from lti_tool.views import LtiLaunchBaseView
from django.contrib.auth.models import User

logger = logging.getLogger(__name__)

# Create your views here.
def get_home_template(request):
return render(request, 'home.html')

def error(request):
return render(request, "error.html")

def get_restrucutured_data(launch_data):
print('you are in get_restructured_data')
# print(launch_data)
custom = launch_data['https://purl.imsglobal.org/spec/lti/claim/custom']
course_title = launch_data['https://purl.imsglobal.org/spec/lti/claim/context']['title']
lis = launch_data['https://purl.imsglobal.org/spec/lti/claim/lis']
Expand Down Expand Up @@ -43,21 +51,41 @@ def get_restrucutured_data(launch_data):
"sis_id": custom["course_sis_account_id"]
}
}
print(restructured_data)
return restructured_data


def login_user_from_lti(request, launch_data):
try:
first_name = launch_data['given_name']
last_name = launch_data['family_name']
email = launch_data['email']
username = launch_data['https://purl.imsglobal.org/spec/lti/claim/custom']['login_id']
logger.info(f'the user {first_name} {last_name} {email} {username} launch the tool')
user_obj = User.objects.get(username=username)
except User.DoesNotExist:
logger.warn(f'user {username} never logged into the app, hence creating the user')
password = ''.join(random.sample(string.ascii_letters, settings.RANDOM_PASSWORD_DEFAULT_LENGTH))
user_obj = User.objects.create_user(username=username, email=email, password=password, first_name=first_name,
last_name=last_name)
except Exception as e:
logger.error(f'error occured while getting the user info from auth_user table due to {e}')
return False


try:
django.contrib.auth.login(request, user_obj)
except (ValueError, TypeError, Exception) as e:
logger.error(f'Logging user after LTI launch failed due to {e}')
return False
return True

class ApplicationLaunchView(LtiLaunchBaseView):

# @xframe_options_exempt
def handle_resource_launch(self, request, lti_launch):
... # Required. Typically redirects the users to the appropriate page.
print('you are in LTI launch')
launch_data = lti_launch.get_launch_data()
custom = launch_data['https://purl.imsglobal.org/spec/lti/claim/custom']
redirect_url = custom['redirect_url']
print(custom)
if not login_user_from_lti(request, launch_data):
return redirect("error")
return redirect("home")

def handle_deep_linking_launch(self, request, lti_launch):
Expand Down
44 changes: 44 additions & 0 deletions setup/lti-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"title": "Canvas LTI Redirect Tool",
"scopes": [],
"extensions": [
{
"domain": "{app-hostname}",
"platform": "canvas.instructure.com",
"settings": {
"platform": "canvas.instructure.com",
"placements": [
{
"default": "disabled",
"placement": "course_navigation",
"message_type": "LtiResourceLinkRequest",
"target_link_uri": "https://{app-hostname}/ltilaunch"
}
]
},
"privacy_level": "public"
}
],
"public_jwk": {},
"description": "This is canvas LTI redirect tool",
"custom_fields": {
"roles": "$Canvas.membership.roles",
"term_id": "$Canvas.term.id",
"login_id": "$Canvas.user.loginId",
"term_end": "$Canvas.term.endAt",
"course_id": "$Canvas.course.id",
"term_name": "$Canvas.term.name",
"canvas_url": "$Canvas.api.baseUrl",
"term_start": "$Canvas.term.startAt",
"redirect_url": "https://dev.dev.umgpt.umich.edu/",
"course_status": "$Canvas.course.workflowState",
"user_canvas_id": "$Canvas.user.id",
"course_account_name": "$Canvas.account.name",
"course_enroll_status": "$Canvas.enrollment.enrollmentState",
"course_sis_account_id": "$Canvas.course.sisSourceId",
"course_canvas_account_id": "$Canvas.account.id"
},
"public_jwk_url": "https://{app-hostname}/.well-known/jwks.json",
"target_link_uri": "https://{app-hostname}/ltilaunch",
"oidc_initiation_url": "https://{app-hostname}/init/<uuid:lti-registration>/"
}

0 comments on commit 742a026

Please sign in to comment.