Skip to content

Commit

Permalink
Add initial version of LineItem Implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Giovanni Cimolin da Silva <[email protected]>
  • Loading branch information
giovannicimolin committed Sep 23, 2020
1 parent 955d229 commit 11fd22d
Show file tree
Hide file tree
Showing 24 changed files with 969 additions and 8 deletions.
3 changes: 2 additions & 1 deletion lti_consumer/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Admin views for LTI related models.
"""
from django.contrib import admin
from lti_consumer.models import LtiConfiguration
from lti_consumer.models import LtiAgsLineItem, LtiConfiguration


class LtiConfigurationAdmin(admin.ModelAdmin):
Expand All @@ -15,3 +15,4 @@ class LtiConfigurationAdmin(admin.ModelAdmin):


admin.site.register(LtiConfiguration, LtiConfigurationAdmin)
admin.site.register(LtiAgsLineItem)
70 changes: 70 additions & 0 deletions lti_consumer/lti_1p3/ags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""
LTI Advantage Assignments and Grades service implementation
"""


class LtiAgs:
"""
LTI Advantage Consumer
Implements LTI Advantage Services and ties them in
with the LTI Consumer. This only handles the LTI
message claim inclusion and token handling.
Available services:
* Assignments and Grades services (partial support)
Reference: https://www.imsglobal.org/lti-advantage-overview
"""
def __init__(
self,
lineitems_url,
allow_creating_lineitems=True,
results_service_enabled=True,
scores_service_enabled=True
):
"""
Instance class with LTI AGS Global settings.
"""
# If the platform allows creating lineitems, set this
# to True.
self.allow_creating_lineitems = allow_creating_lineitems

# Result and scores services
self.results_service_enabled = results_service_enabled
self.scores_service_enabled = scores_service_enabled

# Lineitems urls
self.lineitems_url = lineitems_url

def get_available_scopes(self):
"""
Retrieves list of available token scopes in this instance.
"""
scopes = []

if self.allow_creating_lineitems:
scopes.append('https://purl.imsglobal.org/spec/lti-ags/scope/lineitem')
else:
scopes.append('https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly')

if self.results_service_enabled:
scopes.append('https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly')

if self.scores_service_enabled:
scopes.append('https://purl.imsglobal.org/spec/lti-ags/scope/score')

return scopes

def get_lti_ags_launch_claim(self):
"""
Returns LTI AGS Claim to be injected in the LTI launch message.
"""
ags_claim = {
"https://purl.imsglobal.org/spec/lti-ags/claim/endpoint": {
"scope": self.get_available_scopes(),
"lineitems": self.lineitems_url,
}
}

return ags_claim
7 changes: 6 additions & 1 deletion lti_consumer/lti_1p3/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@
"scope",
])

LTI_1P3_ACCESS_TOKEN_SCOPES = []

LTI_1P3_ACCESS_TOKEN_SCOPES = [
# LTI-AGS Scopes
'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly',
'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem',
]


class LTI_1P3_CONTEXT_TYPE(Enum): # pylint: disable=invalid-name
Expand Down
55 changes: 55 additions & 0 deletions lti_consumer/lti_1p3/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
LTI_1P3_CONTEXT_TYPE,
)
from .key_handlers import ToolKeyHandler, PlatformKeyHandler
from .ags import LtiAgs


class LtiConsumer1p3:
Expand Down Expand Up @@ -436,3 +437,57 @@ def set_extra_claim(self, claim):
if not isinstance(claim, dict):
raise ValueError('Invalid extra claim: is not a dict.')
self.extra_claims.update(claim)


class LtiAdvantageConsumer(LtiConsumer1p3):
"""
LTI Advantage Consumer Implementation.
Builds on top of the LTI 1.3 consumer and adds support for
the following LTI Advantage Services:
* Assignments and Grades Service (LTI-AGS): Allows tools to
retrieve and send back grades into the platform.
Note: this is a partial implementation with read-only LineItems.
Reference spec: https://www.imsglobal.org/spec/lti-ags/v2p0
"""
def __init__(self, *args, **kwargs):
"""
Override parent class and set up required LTI Advantage variables.
"""
super(LtiAdvantageConsumer, self).__init__(*args, **kwargs)

# LTI AGS Variables
self.ags = None

@property
def lti_ags(self):
"""
Returns LTI AGS class or throw exception if not set up.
"""
if not self.ags:
raise exceptions.LtiAdvantageServiceNotSetUp(
"The LTI AGS service was not set up for this consumer."
)

return self.ags

def enable_ags(
self,
lineitems_url,
):
"""
Enable LTI Advantage Assignments and Grades Service.
This will include the LTI AGS Claim in the LTI message
and set up the required class.
"""
self.ags = LtiAgs(
lineitems_url=lineitems_url,
allow_creating_lineitems=True,
results_service_enabled=True,
scores_service_enabled=True
)

# Include LTI AGS claim inside the LTI Launch message
self.set_extra_claim(self.ags.get_lti_ags_launch_claim())
4 changes: 4 additions & 0 deletions lti_consumer/lti_1p3/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ class RsaKeyNotSet(Lti1p3Exception):

class PreflightRequestValidationFailure(Lti1p3Exception):
pass


class LtiAdvantageServiceNotSetUp(Lti1p3Exception):
pass
16 changes: 16 additions & 0 deletions lti_consumer/lti_1p3/extensions/rest_framework/parsers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
LTI 1.3 Django extensions - Content parsers
Used by DRF views to render content in LTI APIs.
"""

from rest_framework import parsers


class LineItemParser(parsers.JSONParser):
"""
Line Item Parser.
It's the same as JSON parser, but uses a custom media_type.
"""
media_type = 'application/vnd.ims.lis.v2.lineitem+json'
44 changes: 44 additions & 0 deletions lti_consumer/lti_1p3/extensions/rest_framework/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Django REST Framework extensions for LTI 1.3 & LTI Advantage implementation.
Implements a custom authorization classes to be used by any of the
LTI Advantage extensions.
"""
from rest_framework import permissions


class LtiAgsPermissions(permissions.BasePermission):
"""
LTI AGS Permissions.
This checks if the token included in the request
has the allowed scopes to read/write LTI AGS items
(LineItems, Results, Score).
LineItem scopes: https://www.imsglobal.org/spec/lti-ags/v2p0#scope-and-allowed-http-methods
Results: Not implemented yet.
Score: Not implemented yet.
"""
def has_permission(self, request, view):
# Retrieves token from request, which was already checked by
# the Authentication class, so we assume it's a sane value.
auth_token = request.headers.get('Authorization', '').split()[1]

if view.action in ['list', 'retrieve']:
# We don't need to wrap this around a try-catch because
# the token was already tested by the Authentication class.
has_perm = request.lti_consumer.check_token(
auth_token,
[
'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly',
'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem',
],
)
return has_perm
elif view.action in ['create', 'update', 'partial_update', 'delete']:
has_perm = request.lti_consumer.check_token(
auth_token,
'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem'
)
return has_perm
return False
28 changes: 28 additions & 0 deletions lti_consumer/lti_1p3/extensions/rest_framework/renderers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
LTI 1.3 Django extensions - Content renderers
Used by DRF views to render content in LTI APIs.
"""
from rest_framework import renderers


class LineItemsRenderer(renderers.JSONRenderer):
"""
Line Items Renderer.
It's a JSON parser, but uses a custom media_type.
Reference: https://www.imsglobal.org/spec/lti-ags/v2p0#media-types-and-schemas
"""
media_type = 'application/vnd.ims.lis.v2.lineitemcontainer+json'
format = 'json'


class LineItemRenderer(renderers.JSONRenderer):
"""
Line Item Renderer.
It's a JSON parser, but uses a custom media_type.
Reference: https://www.imsglobal.org/spec/lti-ags/v2p0#media-types-and-schemas
"""
media_type = 'application/vnd.ims.lis.v2.lineitem+json'
format = 'json'
Loading

0 comments on commit 11fd22d

Please sign in to comment.