SSP Technical Design: LTI Provider

Overview

SSP will act as a LTI v1.0 Provider (TP). See the spec for what that entails, especially as it relates to Tool Consumers (TCs).

We choose 1.0 because we are leveraging LTI as an SSO spec with a standardized means for conveying the originating "learning context", i.e. course. We do not have any need for callbacks to the TC added in subsequent LTI versions. E.g. we do not need to pass back grades to the LMS. A future enhancement may leverage TC roster list callbacks, though, to support just-in-time provisioning of SSP Early Alert (EA) rosters.

Components

The following diagram illustrates high-level interactions between software components involved in SSP's proposed LTI TP implementation.

 

SSP Components

None of the server-side SSP components shown above exist at this writing. "SSP Admin UI" exists as a general framework. This implementation will add a small collection of screens to it. See below for mockups.

Oauth1 Filter

The "OAuth1 Filter" component will be implemented using the SpringOAuth library. That library functions most naturally as a bean in a SpringSecurity FilterChain, so it is shown intercepting requests to the "LTI Launch API".  LTI only uses OAuth for request signature validation; there is no token exchange. So the "OAuth1 Filter" is only responsible for:

  • Rejecting requests with invalid signatures, and
  • Caching a SpringSecurity Authentication for requests with valid signatures.

OAuth1-validated Authentications will represent the TC, not the end user initiating the launch request.

SpringOAuth validates request signatures by looking up ConsumerDetails objects from a ConsumerDetailsService.  Our "LTI Services" (see below) will satisfy the latter interface.  See the org.jasig.ssp.service.security.oauth2.impl package for services playing similar roles in SSP's OAuth2 support, especially for mapping from a OAuth-specific *Details object to a UserDetails object required by SSP's internal security infrastructure.

The implementation will validate oauth_timestamp freshness, and the acceptable slippage must be configurable via the SSP config UI. This can be a global config, i.e. not per-TC. SpringOAuth's OOTB nonce validation capabilities do not support nonce uniqueness validation, either in memory or against a persistent store, and thus cannot be used as-is. Depending on available development time, a uniqueness-validating in-memory implementation (with its implicit limitations) is acceptable, but a database-backed implementation is strongly preferable. Reference implementations of the latter are likely available to expedite development.

LTI Services

The "LTI Services" component shown on the diagram is responsible for handling LTI request parameters, the SSP-specific {target} request path param, and any other request attributes which inform final dispatch to the requested user interface. In particular, it is responsible for:

  • Coordinating translation of the LTI user identifier to a SSP Platform account username. The LTI parameter used for this purpose will be configurable per-TC.
  • Requesting new SSP Platform SSO "tickets" for authenticated LTI launch requests
  • Mapping request parameters to SSP Platform portlet URLs

LTI launch to portlet URL mapping capabilities need not be particularly sophisticated/configurable at this time. See API Design below for more detail.

LTI services should not deal with HTTP requests/responses directly. It's API should depend on new abstractions to encapsulate those concepts: likely LtiLaunchRequest and LtiLaunchResponse. The "LTI Launch API" layer, which is likely implemented as a single controller class, is responsible for direct interactions with the HTTP APIs.

NB: Dynamic account provisioning as a side-effect of TC launch requests is out of scope, as is dynamic EA roster provisioning, whether based on data in the TC launch requests themselves or LTI callback services offered by the TC.

Because SSP requires a Person be associated with the current Authentication when processing any given request, we will most likely follow the pattern established by previous work on OAuth2 support and implement our new TC entity model as a subclass of Person. This unfortunately widens that table even further, but is much easier than refactoring to an Account-Person distinction as described in https://github.com/Jasig/SSP/commit/e7af8330e0aa2da452da84819c25895f444387a2

Please note that because we need access to original secrets in order to verify OAuth signatures, TC secrets will need to be persisted in plain text (unlike OAuth2 secrets, which are salted and hashed before storage).

SSP Platform Components

All SSP Platform/uPortal components shown above do already exist, but special considerations exist for "SSO Ticket Service" and "Person Lookup Service".

SSO Ticket Service

The "SSO Ticket Service" component shown on the diagram is currently implemented as ISsoTicketDao / JpaSsoTicketDao but the former is not exposed to cross-context requests. It, or a "service-ified" version of it, will need to be added to the platform-java-api library. Registering the runtime service implementation with its interface can be something of a challenge, as can ClassLoader issues related to Hibernate/JPA.

This design relies on the Platform/uPortal SSO "ticketing" mechanism, which implements randomized, short-lived, one-time-use, "authenticated" URLs, because SSP currently relies on Platform/uPortal to establish end user sessions. But we choose not to factor the LTI launch URLs POSTed to by the TC into Platform/uPortal because we do not want to create long-term external dependencies on the /ssp-platform URL space. I.e. this approach will not create any new roadblocks to a standalone SSP deployment.

SSO Ticket Service - Implementation Status

This component presented several technical challenges, so as part of authoring this design doc, we verified that the proposed approach will actually work. There is now a new shared API that exposes SSO ticket issuing capabilities to non-portlet requests: SsoTicketService. For additional detail on the technical challenges, please see 

Error rendering macro 'jira' : Unable to locate Jira server for this macro. It may be due to Application Link configuration.
. For additional detail on SsoTicketService design and usage, see  Unable to locate Jira server for this macro. It may be due to Application Link configuration. .

Person Lookup Service

The "Person Lookup Service" component shown on the diagram is currently implemented as IPersonLookupHelper / PersonLookupHelperImpl, which are not exposed to direct cross-context requests via platform-java-api, but adding a corresponding interface to that library should not be necessary since we can access equivalent functionality via a REST interface encapsulated by SSP's PersonAttributesService

 

API Design

Live Launch Resource

Base URL: /ssp/api/1/lti/launch/live/

Method: POST

Parameters: None

Usage: For a launch request to a default view with no TC-specific path extensions. (Used by some TCs, e.g. Desire2Learn, to clarify the launching context and thereby control response content.)

Successful Response:

The effect of a successful launch is the creation of a new end user-redeemable ticket, i.e. authenticated session, so the response code is a 201 and a Location header encoding both the ticket identifier and the URL to which to redirect the end user if the ticket is redeemed successfully. {portlet-url} should evaluate to a portlet render request for whatever the default fragment is in the ticket-redeeming user's layout.

201 Created
Location: /ssp-platform/Login?upp_ticket={ticket-uuid}&refUrl={portlet-url}

Error Response:

OAuth signature validation response errors will be handled by SpringOAuth. This behavior may need to be customized depending on TC-specific UX requirements. It is known that a TP provider can pass IMS certification with SpringOAuth error messages alone, though. See additional considerations below for /test resources.

Other errors will result in either:

  • If the lti_launch_presentation_return_url LTI launch param is present: redirect to {lti_launch_presentation_return_url}?lti_errormsg={url-encoded-error-msg}
  • If the lti_launch_presentation_return_url LTI launch param is not present: simple HTML page containing {url-encoded-error-msg}

url-encoded-error-msg is intended for end user consumption. As such it should be brief and only minimally technical. The underlying cause of the error should be logged in SSP via its standard logging mechanisms and associated with a unique identifier (random UUID is fine). The error identifier should be included in url-encoded-error-msg, should the user choose to follow up with her system administrator.

General categories of error worth communicating to the end user:

  • End user account not on file
  • Missing required parameter
  • Unresolvable target tool
  • Insufficient or incorrect credentials (i.e. bad signature, TC disabled, etc)
  • System error

 

Base URL: /ssp/api/1/lti/launch/live/target/{target}

Method: POST

Parameters:

NamePath/QueryRequiredValid ValuesDescription
targetPathNo

[empty]

ssp

ea

mygps

reports

Specifies the logical name of a SSP portlet

Usage: For a launch request to a specific view. Design note: the path param name is itself modeled as a path component to ensure fully disambiguated URLs in the future. E.g. some TCs append path components to clarify the launching context or navigational control that issued the launch request. This URL design ensures no clashes between the target path param and those TC-specific path extensions. I.e. to support such extensions, new API resources would be:

  • /ssp/api/1/lti/launch/live/tcext/{tcext}
  • /ssp/api/1/lti/launch/live/target/{target}/tcext/{tcext}

An empty target param value should be handled as if the target path component were not present at all.

In the future, target values might be further qualified, likely with dot notation, e.g. ea.roster or ea.form or ssp.journal, but doing at this writing would be of minimal value, because of limitations in LTI and SSP.

Response:

Same as for /ssp/api/1/lti/launch/live described above.

Implementation note: See SsoController for examples of building refUrl values, esp when targeting the Early Alert portlet.

Test Launch Resource

Base URL: "Test" launch resources and supported methods are identical to "live" launch resources above, except that they are based in /ssp/api/1/lti/launch/test

Response:

This capability is being added specifically for the D2L TC, which specifies that the response status code should always be 200, and the response payload should be JSON:

{
    "result_code": <string>,   // Should be either 'OK' or 'FAILURE'
    "result_description": <string>|null
}

D2L's specifications do not explicitly require a response MIME type. The implementation should start by using SSP's standard JSON response MIME type.

result_description is intended for a technical audience and can contain a more detailed explanation of the problem if result_code is FAILURE. Still, complete detail should be confined to SSP logs, and result_description should include an identifier for the error instance for further investigation.

The implementation needn't include elaborate mechanisms for configuring other TC-specific launch test response protocols, but please keep in mind that the protocol discussed here is D2L-specific. Thus the implementation should be sure to remain as encapsulated as possible.

The most challenging aspect of the implementation will likely involve intercepting error responses from SpringOAuth. At this writing it is unclear whether this can be dealt with effectively via configuration or if custom code will need to be added to the filter stack. Obviously the former is desirable, but the latter may prove necessary.

Admin/Config

Admin/Config UX


Admin/Config API Design

Expect that this will be a standard SSP admin UI-driving CRUD API, so not going to detail it all out here. See OAuth2ClientController, which should be able to act as a high-fidelity template for a TC consumer API. For the base resource, I propose /ssp/api/1/lti/tc.

Testing

Pearson teams report success using the following link, but it appears to be broken or behind a paywall now:

http://www.imsglobal.org/developers/LTI/test/v1p1/lms.php

Instead, either install a local Moodle or Sakai instance or try Dr Chuck's PHP test harness in GitHub:

https://github.com/csev/sakai-lti-test

Especially:

https://github.com/csev/sakai-lti-test/blob/master/index.php