Skip to content
Snippets Groups Projects
Commit c0528f4a authored by Shimul Chowdhury's avatar Shimul Chowdhury Committed by Ned Batchelder
Browse files

LTI-DL content rendering endpoint.

parent 098c9243
No related branches found
No related tags found
No related merge requests found
......@@ -96,10 +96,28 @@ def publish_grade(block, user, score, possible, only_if_higher=False, score_dele
)
def user_has_staff_access(user, course_key):
def user_has_access(*args, **kwargs):
"""
Check if an user has write permissions to a given course.
Import and run `has_access` from LMS
"""
# pylint: disable=import-error,import-outside-toplevel
from lms.djangoapps.courseware.access import has_access
return has_access(user, "staff", course_key)
return has_access(*args, **kwargs)
def get_course_by_id(course_key):
"""
Import and run `get_course_by_id` from LMS
"""
# pylint: disable=import-error,import-outside-toplevel
from lms.djangoapps.courseware.courses import get_course_by_id as lms_get_course_by_id
return lms_get_course_by_id(course_key)
def user_course_access(*args, **kwargs):
"""
Import and run `check_course_access` from LMS
"""
# pylint: disable=import-error,import-outside-toplevel
from lms.djangoapps.courseware.courses import check_course_access
return check_course_access(*args, **kwargs)
......@@ -15,6 +15,7 @@ from lti_consumer.plugin.views import (
# LTI Advantage URLs
LtiAgsLineItemViewset,
deep_linking_response_endpoint,
deep_linking_content_endpoint,
)
......@@ -45,6 +46,11 @@ urlpatterns = [
deep_linking_response_endpoint,
name='lti_consumer.deep_linking_response_endpoint'
),
url(
r'lti_consumer/v1/lti/(?P<lti_config_id>[-\w]+)/lti-dl/content',
deep_linking_content_endpoint,
name='lti_consumer.deep_linking_content_endpoint'
),
url(
r'lti_consumer/v1/lti/(?P<lti_config_id>[-\w]+)/',
include(router.urls)
......
......@@ -7,6 +7,7 @@ from django.db import transaction
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.shortcuts import render
from django_filters.rest_framework import DjangoFilterBackend
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey
......@@ -43,10 +44,48 @@ from lti_consumer.lti_1p3.extensions.rest_framework.parsers import (
from lti_consumer.plugin.compat import (
run_xblock_handler,
run_xblock_handler_noauth,
user_has_staff_access,
get_course_by_id,
user_course_access,
user_has_access,
)
def user_has_staff_access(user, course_key):
"""
Check if an user has write permissions to a given course.
"""
return user_has_access(user, "staff", course_key)
def has_block_access(user, block, course_key):
"""
Checks if a user has access to given xblock.
``has_access`` doesn't checks for course enrollment. On the otherhand, ``get_course_with_access``
only checks for the course itself. There is no way to check access for specific xblock. This function
has been created to perform a combination of check for both enrollment and access for specific xblock.
Args:
user: User Object
block: xblock Object to check permission for
course_key: A course_key specifying which course run this access is for.
Returns:
bool: True if user has access, False otherwise.
"""
# Get the course
course = get_course_by_id(course_key)
# Check if user is authenticated & enrolled
course_access = user_course_access(course, user, 'load', check_if_enrolled=True, check_if_authenticated=True)
# Check if user has access to xblock
block_access = user_has_access(user, 'load', block, course_key)
# Return True if the user has access to xblock and is enrolled in that specific course.
return course_access and block_access
@require_http_methods(["GET"])
def public_keyset_endpoint(request, usage_id=None):
"""
......@@ -185,6 +224,35 @@ def deep_linking_response_endpoint(request, lti_config_id=None):
return HttpResponse(status=403)
@require_http_methods(['GET'])
def deep_linking_content_endpoint(request, lti_config_id=None):
"""
Deep Linking endpoint for rendering Deep Linking Content Items.
"""
try:
# Get LTI Configuration
lti_config = LtiConfiguration.objects.get(id=lti_config_id)
except LtiConfiguration.DoesNotExist:
return HttpResponse(status=404)
# check if user has proper access
if not has_block_access(request.user, lti_config.block, lti_config.location.course_key):
return HttpResponse(status=403)
# Get all LTI-DL contents
content_items = LtiDlContentItem.objects.filter(lti_configuration=lti_config)
# If no LTI-DL contents found for current configuration, throw 404 error
if not content_items.exists():
return HttpResponse(status=404)
# Render LTI-DL contents
return render(request, 'html/lti-dl/render_dl_content.html', {
'content_items': content_items,
'block': lti_config.block,
})
class LtiAgsLineItemViewset(viewsets.ModelViewSet):
"""
LineItem endpoint implementation from LTI Advantage.
......
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>{{ block.display_name }} | Deep Linking Contents</title>
</head>
<body>
</body>
</html>
......@@ -218,3 +218,52 @@ class LtiDeepLinkingResponseEndpointTestCase(LtiDeepLinkingTestCase):
self.assertEqual(content_items.count(), 1)
self.assertEqual(content_items[0].content_type, "ltiResourceLink")
self.assertEqual(content_items[0].attributes["url"], "https://example.com/lti")
class LtiDeepLinkingContentEndpointTestCase(LtiDeepLinkingTestCase):
"""
Test ``deep_linking_content_endpoint`` view.
"""
def setUp(self):
super().setUp()
self.url = '/lti_consumer/v1/lti/{}/lti-dl/content'.format(self.lti_config.id)
@patch('lti_consumer.plugin.views.has_block_access', return_value=False)
def test_forbidden_access(self, has_block_access_patcher): # pylint: disable=unused-argument
"""
Test if 403 is returned when a user doesn't have access.
"""
resp = self.client.get(self.url)
self.assertEqual(resp.status_code, 403)
def test_invalid_lti_config(self):
"""
Test if throws 404 when lti configuration not found.
"""
resp = self.client.get('/lti_consumer/v1/lti/200/lti-dl/content')
self.assertEqual(resp.status_code, 404)
@patch('lti_consumer.plugin.views.has_block_access', return_value=True)
def test_no_dl_contents(self, has_block_access_patcher): # pylint: disable=unused-argument
"""
Test if throws 404 when there is no LTI-DL Contents.
"""
resp = self.client.get(self.url)
self.assertEqual(resp.status_code, 404)
@patch('lti_consumer.plugin.views.has_block_access', return_value=True)
def test_dl_contents(self, has_block_access_patcher): # pylint: disable=unused-argument
"""
Test if successfully returns an HTML response.
"""
LtiDlContentItem.objects.create(
lti_configuration=self.lti_config,
content_type=LtiDlContentItem.IMAGE,
attributes={}
)
resp = self.client.get(self.url)
self.assertEqual(resp.status_code, 200)
expected_title = '{} | Deep Linking Contents'.format(self.lti_config.block.display_name)
self.assertContains(resp, expected_title)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment