diff --git a/lti_auth/urls.py b/lti_auth/urls.py index 4b311c9b2..ce31f27e8 100644 --- a/lti_auth/urls.py +++ b/lti_auth/urls.py @@ -1,12 +1,15 @@ from django.urls import path -from lti_auth.views import LTIConfigView, LTILandingPage, LTIRoutingView, \ - LTICourseEnableView +from lti_auth.views import ( + LTIConfigView, LTILandingPage, LTIRoutingView, + LTICourseEnableView, LTI1p3JSONConfigView +) from lti_tool.views import jwks, OIDCLoginInitView urlpatterns = [ path('config.xml', LTIConfigView.as_view(), {}, 'lti-config'), + path('config.json', LTI1p3JSONConfigView.as_view(), name='lti-1p3-config'), path('enable/', LTICourseEnableView.as_view(), {}, 'lti-enable-course'), path('landing//', LTILandingPage.as_view(), {}, 'lti-landing-page'), diff --git a/lti_auth/views.py b/lti_auth/views.py index a7c7b0c70..fd7f89803 100644 --- a/lti_auth/views.py +++ b/lti_auth/views.py @@ -11,6 +11,8 @@ from django.views.decorators.clickjacking import xframe_options_exempt from django.views.decorators.csrf import csrf_exempt from django.views.generic.base import View, TemplateView +from urllib.parse import urljoin + from lti_auth.lti import LTI from lti_auth.models import LTICourseContext @@ -127,101 +129,88 @@ def get_context_data(self, **kwargs): return ctx -class LTI13JSONConfigView(View): - def get(self, **kwargs): +class LTI1p3JSONConfigView(View): + """ + JSON configuration endpoint for LTI 1.3. + + In Canvas LMS, an LTI Developer Key can be created via Manual + Entry, or by URL. This view provides the JSON necessary for URL + configuration in Canvas. + + https://canvas.instructure.com/doc/api/file.lti_dev_key_config.html + """ + def get(self, request, *args, **kwargs): + domain = request.get_host() + title = settings.LTI_TOOL_CONFIGURATION['title'] + icon_url = urljoin(settings.STATIC_URL, + settings.LTI_TOOL_CONFIGURATION['embed_icon_url']) + json_obj = { - "title": "The Best Tool", - "description": "1.3 Test Tool used for documentation purposes.", - "oidc_initiation_url": "https://your.oidc_initiation_url", - "oidc_initiation_urls": { - "eu-west-1": "https://your.eu-specific1.oidc_initiation_url", - "eu-central-1": "https://your.eu-specific2.oidc_initiation_url" - }, - "target_link_uri": "https://your.target_link_uri", - "scopes": [ - "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", - "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly" + 'title': title, + 'description': settings.LTI_TOOL_CONFIGURATION['description'], + 'oidc_initiation_url': 'https://{}.oidc_initiation_url'.format( + domain), + 'target_link_uri': 'https://{}.target_link_uri'.format(domain), + 'scopes': [ + 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem', + 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly' ], - "extensions": [ + 'extensions': [ { - "domain": "thebesttool.com", - "tool_id": "the-best-tool", - "platform": "canvas.instructure.com", - "privacy_level": "public", - "settings": { - "text": "Launch The Best Tool", - "labels": { - "en": "Launch The Best Tool", - "en-AU": "G'day, Launch The Best Tool", - "es": "Lanzar la mejor herramienta", - "zh-Hans": "启动最佳工具" + 'domain': domain, + 'tool_id': 'mediathread', + 'platform': 'canvas.instructure.com', + 'privacy_level': 'public', + 'settings': { + 'text': 'Launch ' + title, + 'labels': { + 'en': 'Launch ' + title, }, - "icon_url": "https://some.icon.url/tool-level.png", - "selection_height": 800, - "selection_width": 800, - "placements": [ + 'icon_url': icon_url, + 'selection_height': 800, + 'selection_width': 800, + 'placements': [ { - "text": "User Navigation Placement", - "icon_url": "https://some.icon.url/my_dashboard.png", - "placement": "user_navigation", - "message_type": "LtiResourceLinkRequest", - "target_link_uri": "https://your.target_link_uri/my_dashboard", - "canvas_icon_class": "icon-lti", - "custom_fields": { - "foo": "$Canvas.user.id" + 'text': 'User Navigation Placement', + 'icon_url': icon_url, + 'placement': 'user_navigation', + 'message_type': 'LtiResourceLinkRequest', + 'target_link_uri': None, + 'canvas_icon_class': 'icon-lti', + 'custom_fields': { + 'foo': '$Canvas.user.id' } }, { - "text": "Editor Button Placement", - "icon_url": "https://some.icon.url/editor_tool.png", - "placement": "editor_button", - "message_type": "LtiDeepLinkingRequest", - "target_link_uri": "https://your.target_link_uri/content_selector", - "selection_height": 500, - "selection_width": 500 + 'text': 'Editor Button Placement', + 'icon_url': icon_url, + 'placement': 'editor_button', + 'message_type': 'LtiDeepLinkingRequest', + 'target_link_uri': None, + 'selection_height': 500, + 'selection_width': 500 }, { - "text": "Course Navigation Placement", - "icon_url": "https://static.thenounproject.com/png/131630-200.png", - "placement": "course_navigation", - "message_type": "LtiResourceLinkRequest", - "target_link_uri": "https://your.target_link_uri/launch?placement=course_navigation", - "required_permissions": "manage_calendar", - "selection_height": 500, - "selection_width": 500 + 'text': 'Course Navigation Placement', + 'icon_url': icon_url, + 'placement': 'course_navigation', + 'message_type': 'LtiResourceLinkRequest', + 'target_link_uri': None, + 'required_permissions': 'manage_calendar', + 'selection_height': 500, + 'selection_width': 500 } ] } } ], - "public_jwk": { - "kty": "RSA", - "alg": "RS256", - "e": "AQAB", - "kid": "8f796169-0ac4-48a3-a202-fa4f3d814fcd", - "n": "nZD7QWmIwj-3N_RZ1qJjX6CdibU87y2l02yMay4KunambalP9g0fU9yZLwLX9WYJINcXZDUf6QeZ-SSbblET-h8Q4OvfSQ7iuu0WqcvBGy8M0qoZ7I-NiChw8dyybMJHgpiP_AyxpCQnp3bQ6829kb3fopbb4cAkOilwVRBYPhRLboXma0cwcllJHPLvMp1oGa7Ad8osmmJhXhM9qdFFASg_OCQdPnYVzp8gOFeOGwlXfSFEgt5vgeU25E-ycUOREcnP7BnMUk7wpwYqlE537LWGOV5z_1Dqcqc9LmN-z4HmNV7b23QZW4_mzKIOY4IqjmnUGgLU9ycFj5YGDCts7Q", - "use": "sig" - }, - "custom_fields": { - "bar": "$Canvas.user.sisid" + 'public_jwk_url': urljoin( + 'https://{}'.format(domain), reverse('jwks')), + 'custom_fields': { + 'bar': '$Canvas.user.sisid' } } - - domain = self.request.get_host() - launch_url = '%s://%s/%s' % ( - self.request.scheme, domain, - settings.LTI_TOOL_CONFIGURATION['launch_url']) - - ctx = { - 'domain': domain, - 'launch_url': launch_url, - 'title': settings.LTI_TOOL_CONFIGURATION['title'], - 'description': settings.LTI_TOOL_CONFIGURATION['description'], - 'embed_icon_url': - settings.LTI_TOOL_CONFIGURATION['embed_icon_url'], - 'embed_tool_id': settings.LTI_TOOL_CONFIGURATION['embed_tool_id'], - } - return ctx + return JsonResponse(json_obj) @method_decorator(xframe_options_exempt, name='dispatch')