Overview
This Java package encapsulates the code required by an LTI 1 compliant tool provider to communicate with a tool consumer. It includes support for LTI 1.1 and the unofficial extensions to Basic LTI. A similar set of PHP classes is also available.
Whilst supporting LTI 1 is relatively simple, the benefits to using a class library like this one are:
- the abstraction layer provided by the classes keeps the LTI communications separate from the application code;
- the code can be re-used between multiple tool providers;
- LTI data is transformed into useful objects and missing data automatically replaced with sensible defaults;
- the outcomes service function uses LTI 1.1 or the unofficial outcomes extension according to whichever is supported by the tool consumer;
- the unofficial extensions for memberships and setting services are supported;
- additional functionality is included to:
- enable/disable a consumer key;
- set start and end times for enabling access for each consumer key;
- set up arrangements such that users from different resource links can all collaborate together within a single tool provider link;
- tool providers can take advantage of LTI updates with minimal impact on their application code.
System requirements
The LTI Tool Provider package is written for Java 6 (or higher) and references the following dependent libraries:
- servlet-api
- commons-httpclient
- jdom
- oauth-core.jar
- commons-codec
- gson
A copy of these dependent library files can also be found in the Blackboard Building Block version of the sample Rating tool provider web application which is available as a separate download for this project.
Installation
Java package
The download file available from OSCELOT (see Licence section below) includes the LtiToolProvider.jar file which contains all the class files. Add this file to your Java web project.
Data connector classes
These classes persist data through an extension of the DataConnector class. The jar file includes an implementation for JDBC connections (org.oscelot.lti.dataconnector.JDBC) which uses a standard data structure. The data may alternatively be merged with an existing tool provider data schema by creating an alternative DataConnector class which retrieves and updates each object from the appropriate location. To use the classes without persisting any data use the org.oscelot.lti.dataconnector.None class.
The standard JDBC connector uses the following database tables and relationships:
CREATE TABLE lti_consumer ( consumer_key varchar(255) NOT NULL, name varchar(45) NOT NULL, secret varchar(32) NOT NULL, lti_version varchar(12) DEFAULT NULL, consumer_name varchar(255) DEFAULT NULL, consumer_version varchar(255) DEFAULT NULL, consumer_guid varchar(255) DEFAULT NULL, css_path varchar(255) DEFAULT NULL, protected tinyint(1) NOT NULL, enabled tinyint(1) NOT NULL, enable_from datetime DEFAULT NULL, enable_until datetime DEFAULT NULL, last_access date DEFAULT NULL, created datetime NOT NULL, updated datetime NOT NULL, PRIMARY KEY (consumer_key) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE lti_context ( consumer_key varchar(255) NOT NULL, context_id varchar(255) NOT NULL, lti_context_id varchar(255) DEFAULT NULL, lti_resource_id varchar(255) DEFAULT NULL, title varchar(255) NOT NULL, settings text, primary_consumer_key varchar(255) DEFAULT NULL, primary_context_id varchar(255) DEFAULT NULL, share_approved tinyint(1) DEFAULT NULL, created datetime NOT NULL, updated datetime NOT NULL, PRIMARY KEY (consumer_key, context_id) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE lti_user ( consumer_key varchar(255) NOT NULL, context_id varchar(255) NOT NULL, user_id varchar(255) NOT NULL, lti_result_sourcedid varchar(255) NOT NULL, created datetime NOT NULL, updated datetime NOT NULL, PRIMARY KEY (consumer_key, context_id, user_id) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE lti_nonce ( consumer_key varchar(255) NOT NULL, value varchar(32) NOT NULL, expires datetime NOT NULL, PRIMARY KEY (consumer_key, value) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE lti_share_key ( share_key_id varchar(32) NOT NULL, primary_consumer_key varchar(255) NOT NULL, primary_context_id varchar(255) NOT NULL, auto_approve tinyint(1) NOT NULL, expires datetime NOT NULL, PRIMARY KEY (share_key_id) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; ALTER TABLE lti_context ADD CONSTRAINT lti_context_consumer_FK1 FOREIGN KEY (consumer_key) REFERENCES lti_consumer (consumer_key); ALTER TABLE lti_context ADD CONSTRAINT lti_context_context_FK1 FOREIGN KEY (primary_consumer_key, primary_context_id) REFERENCES lti_context (consumer_key, context_id); ALTER TABLE lti_user ADD CONSTRAINT lti_user_context_FK1 FOREIGN KEY (consumer_key, context_id) REFERENCES lti_context (consumer_key, context_id); ALTER TABLE lti_nonce ADD CONSTRAINT lti_nonce_consumer_FK1 FOREIGN KEY (consumer_key) REFERENCES lti_consumer (consumer_key); ALTER TABLE lti_share_key ADD CONSTRAINT lti_share_key_context_FK1 FOREIGN KEY (primary_consumer_key, primary_context_id) REFERENCES lti_context (consumer_key, context_id);
An optional prefix may be added to the table names. For example, if a prefix of "MyApp_" is specified the expected table names would be MyApp_lti_consumer, MyApp_lti_context etc.
Class definitions
The following classes are defined:
- ToolProvider
- ToolConsumer
- Nonce
- ResourceLink
- ResourceLinkShare
- ResourceLinkShareKey
- User
- Outcome
- Data_Connector (abstract)
- Callback
- Utils
The classes are fully described in the JavaDocs documentation.
Usage
A quick way to see how LTI can be used to integrate an external tool with a learning environment is to watch the following
screencast showing how it is being used with WebPA, a peer assessment tool. Note that, whilst this uses Blackboard Learn 9 as the
learning environment, the same integration is possible with any other which provides support for the unofficial extensions,
this includes WebCT, Moodle, Sakai.
View movie illustration - An instructor's view of LTI (10:46 minutes)
Having a learning application hosted outside the learning environment also introduces opportunities for users coming from different
resource links to share the same working area within the external tool. This is illustrated by the following screencast which shows
how students from different resurce links (which could be from different learning environments and/or different institutions) can be
joined together to work on the same peer assessment exercise.
View movie illustration - Using LTI to allow student collaboration (6:13 minutes)
The Rating tool provider web application is also available as a simple example of how to use these classes.
Using the Java classes
The classes should be may available for use in a Java file by importing the required classes; for example:
import org.oscelot.lti.tp.ToolProvider; import org.oscelot.lti.tp.DataConnector; import org.oscelot.lti.tp.dataconnector.JDBC;
Specifying a data connector
Data is persisted by the classes via a subclass of the DataConnector class. There are two elements to specifying a data connector:
- an optional table name prefix
- a data source (database connection including database username and password, where required)
The following code snippet illustrates how to set up a data connector using a JDBC connection string to a MySqL database with a table name prefix of "MyApp_".
import java.sql.DriverManager; import java.sql.Connection; import org.oscelot.lti.tp.DataConnector; import org.oscelot.lti.tp.dataconnector.JDBC; Connection connection = null; try { // Load driver Class.forName("com.mysql.jdbc.Driver").newInstance(); // Obtain connection connection = DriverManager.getConnection("jdbc:mysql://localhost/AppDb?user=db_user&password=db_pass"); } catch (ClassNotFoundException e) { } catch (InstantiationException e) { } catch (IllegalAccessException e) { } DataConnector dataConnector = new JDBC("MyApp_", connection);
No data connector
To use the classes without any data persistence use the following:
import org.oscelot.lti.tp.DataConnector; import org.oscelot.lti.tp.dataconnector.None; DataConnector dc = new None();
Initialising a tool consumer
When a launch request is received it will be validated with the shared secret associated with the consumer key. The default data structure uses the lti_consumer table to record details of tool consumers. A record may be initialised as follows:
import org.oscelot.lti.tp.ToolConsumer; ToolConsumer toolConsumer = new ToolConsumer("testing.edu", dataConnector, false); toolConsumer.setName("Testing"); toolConsumer.setSecret("ThisIsASecret"); toolConsumer.save();
By default, a tool consumer instance is disabled (i.e. will not be used to accept any incoming requests). A tool consumer may be enabled when it is created or updated as follows:
import org.oscelot.lti.tp.ToolConsumer; ToolConsumer toolConsumer = new ToolConsumer("testing.edu", dataConnector, false); if (toolConsumer.getCreated() != null) { toolConsumer.setEnabled(true); toolConsumer.save(); }
Validating a launch request
The primary use case for the classes is to validate an incoming launch request from a tool consumer. Once a record has been initialised for the tool consumer (see above), the verification of the authenticity of the LTI launch request is handled automatically by the ToolProvider class; a callback is made for processing the request if it is valid (see below). For example, if the instance of the callback class is named "DoLaunch" the following code will check the incoming request (the request and response parameters are from the HTTP connection).
import org.oscelot.lti.tp.Callback; import org.oscelot.lti.tp.ToolProvider; Callback doLaunch = new DoLaunch(); ToolProvider toolProvider = new ToolProvider(request, response, doLaunch, dataConnector); toolProvider.setParameterConstraint("user_id", true, 50); toolProvider.setParameterConstraint("roles", true, null); toolProvider.execute(); public class DoLaunch implements Callback { @Override public boolean execute(ToolProvider toolProvider) { boolean ok; // Insert code here to handle incoming connections - use the user // and resource_link properties of the toolProvider parameter // to access the current user and resource link. return ok; } }
The above example also includes automatic checks for the user_id and roles parameters which will ensure that the both are received in the launch request and the former is no longer than 50 characters. The execute method checks the authenticity of the incoming request by verifying the OAuth signature (using the shared secret recorded for the tool consumer), the timestamp is within a defined limit of the current time, the nonce value has not been previously used, and all required parameters are present. Only if the request passes all these checks is the callback class executed. The following parameters are automatically saved for future use in the ResourceLink object when a launch request is executed with the LTI services:
- ext_resource_link_content
- ext_resource_link_content_signature
- lis_result_sourcedid
- ext_ims_lis_basic_outcome_url
- ext_ims_lis_resultvalue_sourcedids
- ext_ims_lis_memberships_id
- ext_ims_lis_memberships_url
- ext_ims_lti_tool_setting
- ext_ims_lti_tool_setting_id
- ext_ims_lti_tool_setting_url
(The ResourceLink methods of getSetting, setSetting and saveSettings may also be used with user-defined setting values.)
When a launch is not valid, a message is returned to the tool consumer ("Sorry, there was an error connecting you to the application."); this message can be changed in the ToolProvider class file. If a custom parameter of debug=true is included in the launch then a more precise reason for the failure is returned:
- Tool consumer instance has not been enabled by the tool provider.
- Invalid nonce.
- OAuth signature check failed - perhaps an incorrect secret.
- Your sharing request has been refused because sharing is not being permitted.
- An error occurred initialising your share arrangement.
- You have requested to share a resource link but none is available.
- It is not possible to share your resource link with yourself.
- You have requested to share a resource link but none is available.
- You have requested to share a resource link but none is available.
- Your share request is waiting to be approved.
- You have not requested to share a resource link but an arrangement is currently in place.
- Unable to load resource link being shared.
- Invalid parameter(s): ...
If you wish to handle an invalid launch request in a different way, then a error callback method can also be declared. In this case both the launch and error callback methods must be passed to the ToolProvider instance. For example, if the error callback class is named "DoError", the following code will make both callbacks available for use with valid and invalid launch requests:
import java.util.Map; import java.util.HashMap; import org.oscelot.lti.tp.Callback; import org.oscelot.lti.tp.ToolProvider; Mapcallbacks = new HashMap (); callbacks.put("connect", new DoLaunch()); callbacks.put("error", new DoError()); ToolProvider toolProvider = new ToolProvider(request, response, callbacks, dataConnector); ...
Protecting a consumer key
The connection between a tool consumer and a tool provider is secured using a consumer key and a shared secret. However, there are some risks to this mechanism:
- launch requests will continue to be accepted even if a licence as expired;
- if the consumer key is used to submit launch requests from more than one tool conumser, there is a risk of clashing resource link and user IDs being received from each.
The first risk can be avoided by manually removing or disabling the consumer key as soon as the licence expires. Alternatively, the dates for any licence may be recorded for the tool consumer so that the appropriate check is made automatically when a launch request is received.
The second risk can be alleviated by setting the protected property of the tool consumer. This will cause launch requests to be only accepted from tool consumers with the same tool_consumer_guid parameter. The value of this parameter is recorded from the first launch request received using the associated consumer key. Note that this facility depends upon the tool consumer sending a value for the tool_consumer_guid parameter and each tool consumer having a unique value for this parameter.
The following code illustrates how these options may be set:
import java.util.Calendar; // load the tool consumer record ToolConsumer toolConsumer = new ToolConsumer("testing.edu", dataConnector, false); // set an expiry date for 30 days time Calendar cal = Calendar.getInstance(); cal.add(Calendar.DAY_OF_MONTH, 30); toolConsumer.setEnableUntil(cal); // protect use of the consumer key to a single tool consumer toolConsumer.setProtect(true); // save the changes toolConsumer.save();
Note that the default value of the enable_from property is NULL which means that access is available immediately. A NULL value for the enable_until property means that access does not automatically expire (this is also the default).
Callback classes
A callback class is associated with the launch action according to the instance passed to the constructor of the ToolProvider object. The ToolProvider object is passed into the execute method of this class with the resource link and user properties populated with values received via the launch request.
public class DoLaunch implements Callback { @Override public boolean execute(ToolProvider toolProvider) { boolean ok; // Get consumer key String consumerKey = toolProvider.getConsumer().getKey(); // Get user ID String userId = toolProvider.getUser().getId(); // Get resource link ID String resourceLinkId = toolProvider.getResourceLink().getId(); // ... return ok; } }
The callback function may be used to:
- create the user account if it does not already exist (or update it if it does);
- create the resource link area if it does not already exist (or update it if it does);
- set up a new session for the user (or otherwise log the user into the tool provider application);
- keep a record of the return URL for the tool consumer (for example, as a session variable);
- set the URL for the home page of the application so the user may be redirected to it;
- return false if the connection request is not to be accepted (optionally setting an error message to be returned).
To reject a valid launch request use code like the following:
public class DoLaunch implements Callback { @Override public boolean execute(ToolProvider toolProvider) { boolean ok; // ... toolProvider.setReason("Incomplete data"); ok = false; return ok; } }
Services
The ResourceLink class provides support for the following services:
- Outcomes
- Memberships
- Setting
These services are unofficial extensions of Basic LTI (LTI 1.0). An Outcomes service is also included in the LTI 1.1 specification; the classes support both versions of the Outcomes service.
In order to submit a service request, the relevant ResourceLink object is required; this object is identified by the consumer key and resource link ID:
ToolConsumer toolConsumer = new ToolConsumer(consumerKey, dataConnector); ResourceLink resourceLink = new ResourceLink(toolConsumer, resourceLinkId);
Outcomes service
A grade book for a specific user is identified by their associated result sourcedid; these values are passed when the user launches the tool and are automatically retained by the classes for future use. The result sourcedid for the current user could be saved as a session variable as part of the callback function on launch. Alternatively, the value may be obtained by loading the User record from its user ID:
User user = new User(resourceLink, userId);
The user's grade may be retrieved from the grade book in the tool consumer using a read operation:
Outcome outcome = new Outcome(); if (resourceLink.doOutcomesService(ResourceLink.EXT_READ, outcome, user)) { String score = outcome.getValue(); }
The user's grade may be saved to the grade book in the tool consumer using a write operation:
Outcome outcome = new Outcome(score); boolean ok = resourceLink.doOutcomesService(ResourceLink.EXT_WRITE, outcome, user);
The score should normally be a decimal value between 0 and 1, but the doOutcomesService method makes every effort to convert the value passed to a format which is accepted by the tool consumer; for example, a percentage of 65% may be converted to a decimal 0.65. The delete operation can be used to remove a grade from the grade book.
Memberships service
The doMembershipsService() method allows a tool provider to request a list of users with access to the resource link from the tool consumer. The method returns a list of User objects.
Listusers = resourceLink.doMembershipsService();
Setting service
The doSettingService(action) and doSettingService(action, value) methods are used to retrieve, save and delete data in the tool consumer resource link.
String setting = resourceLink.doSettingService(ResourceLink.EXT_READ);
The above line is equivalen to:
String setting = ""; boolean ok = resourceLink.doSettingService(ResourceLink.EXT_READ, null); if (ok) { setting = resourceLink.getSetting("ext_ims_lti_tool_setting"); }
String setting = "Please remember this ..."; boolean ok = resourceLink.doSettingService(ResourceLink.EXT_WRITE, setting);
boolean ok = resourceLink.doSettingService(ResourceLink.EXT_DELETE);
Version history
Version | Date | Description |
---|---|---|
1.0.00 | 2 January 2013 | Initial release |
1.1.00 | 13 April 2013 | Added option for error callback method Updated to support latest release of OAuth class library |
1.1.01 | 18 June 2013 |
Altered order of checks in authenticate Fixed bug with not updating a resource link before redirecting to a shared resource link Fixed bug causing user records not to be saved Tightened up setting of roles - now case sensitive and use fully qualified URN Fixed bug with OutcomesService when a resource link is shared with a different tool consumer Separated User from Outcome object |
Licence
This work is written by Stephen Vickers and is released under a GNU Lesser General Public Licence. The LTI Tool Provider package is available for download from OSCELOT where it is also possible to report bugs and submit feature requests.