Skip to content
Stephen Vickers edited this page Jun 26, 2020 · 13 revisions

Class definitions

This page describes the usage of version 4 of the library; follow this link for details relating to [version 3](Usage version 3).

Please refer to the class definition documentation for details of the classes defined in this library. The sample Rating application provides a working example of the usage of this library.

Specifying a data connector

A data connector instance is required to initialise the entities defined in this library. This is a mechanism for abstracting the data persistence from the application code and allows support for different databases as well as bespoke implementations based on an existing table structure within an application. It is also possible to set up a dummy application which does not persist any data.

MySQL database

When using the PHP MySQL library, connect to the database in the normal way (using a server name, user name and password) and use the MySQL link identifier which is returned to create the data connector. A table name prefix (e.g. 'app1_') can be optionally included.

use ceLTIc\LTI\DataConnector;

$db = mysql_connect($dbServer, $dbUser, $dbPassword);
mysql_select_db($dbSchema);

$dataConnector = DataConnector\DataConnector::getDataConnector($db, 'app1_');

MySQLi database

When using the PHP MySQLi library, connect to the database in the normal way (using a server name, user name and password) and use the MySQLi link identifier which is returned to create the data connector. A table name prefix (e.g. 'app1_') can be optionally included.

use ceLTIc\LTI\DataConnector;

$db = mysqli_connect($dbServer, $dbUser, $dbPassword);
mysqli_select_db($dbSchema);

$dataConnector = DataConnector\DataConnector::getDataConnector($db, 'app1_');

Microsoft SQL Server database

When using the PHP sqlsrv library, connect to the database in the normal way (using a server name, user name and password) and use the sqlsrv link identifier which is returned to create the data connector. A table name prefix (e.g. 'app1_') can be optionally included.

use ceLTIc\LTI\DataConnector;

/*
 * Example: $dbServer = '(local)';
 */
$connectionInfo = array('Database' => $dbServer, 'UID' => $dbUser, 'PWD' => $dbPassword);
$db = sqlsrv_connect($dbSchema, $connectionInfo);

$dataConnector = DataConnector\DataConnector::getDataConnector($db, 'app1_', 'sqlsrv');

(This has been tested with SQL Sever 2017.)

Oracle database

When using the PHP oci library, connect to the database in the normal way (using a server name, user name and password) and use the oci link identifier which is returned to create the data connector. A table name prefix (e.g. 'app1_') can be optionally included.

use ceLTIc\LTI\DataConnector;

/*
 * Example: $dbSchema = '//localhost:1521/orcl';
 */
$db = oci_connect($dbSchema, $dbUser, $dbPassword);

$dataConnector = DataConnector\DataConnector::getDataConnector($db, 'app1_', 'oci');

(This has been tested with Oracle 12.2.)

PostgreSQL database

When using the PHP pg library, connect to the database in the normal way (using a server name, user name and password) and use the pg link identifier which is returned to create the data connector. A table name prefix (e.g. 'app1_') can be optionally included.

use ceLTIc\LTI\DataConnector;

/*
  Example: $dbServer= 'host=localhost port=5432 dbname=test';
*/
$db = pg_connect($dbServer, $dbUser, $dbPassword);

$dataConnector = DataConnector\DataConnector::getDataConnector($db, 'app1_', 'pg');

(This has been tested with PostgreSQL 9.5.3.)

PHP Data Objects (PDO) interface

Database connections can be also be made using the PHP PDO interface. Here are some examples.

MySQL

use ceLTIc\LTI\DataConnector;

$db = new PDO("mysql:host={$dbHost};dbname={$dbSchema}", $dbUser, $dbPassword);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  // optional

$dataConnector = DataConnector\DataConnector::getDataConnector($db);

SQLite

use ceLTIc\LTI\DataConnector;

$db = new PDO('sqlite::memory:');
$dataConnector = DataConnector\DataConnector::getDataConnector($db);

Microsoft SQL Server

use ceLTIc\LTI\DataConnector;

$db = new PDO("mssql:host={$dbHost};dbname={$dbSchema}", $dbUser, $dbPassword);

$dataConnector = DataConnector\DataConnector::getDataConnector($db);

(This has been tested with SQL Sever 2017.)

Oracle

use ceLTIc\LTI\DataConnector;

$db = new PDO("oci:dbname={$dbSchema}", $dbUser, $dbPassword);
$dataConnector = DataConnector\DataConnector::getDataConnector($db);

(This has been tested with Oracle 12.2.)

PostgreSQL

use ceLTIc\LTI\DataConnector;

$db = new PDO("pgsql:host=localhost;port=5432;dbname=test", $dbUser, $dbPassword);

$dataConnector = DataConnector\DataConnector::getDataConnector($db);

(This has been tested with PostgreSQL 9.5.3.)

Other connectors

Bespoke connectors can be created by writing a sub-class of the DataConnector\DataConnector class. This allows the data for the different LTI classes to be held in tables of your own design; those classes which are not required by your application can be ignored (just using the default method definition which assumes no data persistence).

Initializing a platform

LTI 1.0/1.1/1.2/2.0

When a launch request is received by a tool it will be validated with the shared secret associated with the consumer key. The default data structure uses the lti2_consumer table to record details of platforms. A record may be initialized in this table for an LTI 1.0/1.1/1.2 platform as follows (records for LTI 2 platforms will be created as part of the registration process):

use ceLTIc\LTI;

$platform = LTI\Platform::fromConsumerKey('testing.edu', $dataConnector);
$platform->name = 'Testing';
$platform->secret = 'ThisIsASecret!';
$platform->enabled = true;
$platform->save();

LTI 1.3

...

use ceLTIc\LTI;

$platform = LTI\Platform::fromPlatformId('https://platform.edu', 'dlirkvlkvlk', 'epodvvlklrlkrto', $dataConnector);
$platform->name = 'Testing';
$platform->authorizationServerId = null;  // defaults to the Access Token URL
$platform->authenticationUrl = 'https://platform.edu/auth';
$platform->accessTokenUrl = 'https://platform.edu/token';
$platform->rsaKey = null;
$platform->jku = 'https://platform.edu/jwks';
$platform->signatureMethod = 'RS256';
$platform->enabled = true;
$platform->save();

The rsaKey property is used to record a platform's public key; this may be specified in either PEM format or JSON format (as returned from a JWKS endpoint). It is not required if a jku endpoint is available, although the library does not currently cache public keys retrieved from such an endpoint so there is a performance gain from recording the public key in the platform's record. If a signature verification fails when using the public key specified and a jku endpoint is also defined, then a second verification will be attempted using th public key retrieved from this endpoint. Thus, the verification should not fail when a key is changed; the system will fallback to the retrieving the new one. The failure of the verification with the public key will be reported as an error, so if you have error-level logging enabled, you can check your log files for reports of outdated keys.

All versions of LTI

A platform may be defined which supports all versions of LTI by combining the above examples to record both sets of details. It is important that, when unused, the Platform ID, Client ID and Deployment ID properties are set to a value of null (the default) rather than an empty string to ensure that unique database constraints are not violated.

Validating a launch request

The primary use case for the classes is to validate an incoming launch request from a platform. Once a record has been initialised for the platform (see above), the verification of the authenticity of the LTI launch request is handled automatically by the Tool class. A sub-class is created and the onLaunch method overridden to define the code to be run when a valid launch request is received.

use ceLTIc\LTI;

class MyApp extends LTI\Tool {

    function onLaunch() {

        // Insert code here to handle incoming connections - use the user,
        // context and resourceLink properties of the class instance
        // to access the current user, context and resource link.

    }

}

$tool = new MyApp($dataConnector);
$tool->handleRequest();

The handleRequest method checks the authenticity of the incoming request. For LTI 1.0/1.1/1.2/2.0 it does this by verifying:

  • the OAuth signature (using the shared secret recorded for the platform),
  • the timestamp is within a defined limit of the current time, and
  • the nonce value has not been previously used.

For requests using LTI 1.3 a request is automatically sent to the platform's Authentication URL with JWT returned being verified using the platform's public key (either as recorded in the database or as retrieved from the JSON Web Key URL) and the current time.

Only if the request passes all the checks is the onLaunch method called. The process also captures various standard launch parameters to allow access to services.

When a launch is not valid, a message is returned to the platform with a more detailed reason to be logged. If the tool's debug mode is set then the more detailed reason for the failure is also displayed to the user. Debug mode can be set in your code; for example:

use ceLTIc\LTI;

class MyApp extends LTI\Tool {

    function __construct($dataConnector)
    {
        parent::__construct($dataConnector);
        $this->debugMode = true;
    }

...

}

or, alternatively:

$tool = new MyApp($dataConnector);
$tool->debugMode = true;
$tool->handleRequest();

In adition a platform can trigger debug mode by including a custom parameter of debug=true in its launch message.

When debug mode is active more detailed messages are also sent to the PHP error log. These debug log messages can also be activated on a per-platform basis by saving a platform with its debug mode set; for example:

use ceLTIc\LTI;

$platform = LTI\Platform::fromConsumerKey('testing.edu', $dataConnector);
$platform->name = 'Testing';
$platform->secret = 'ThisIsASecret!';
$platform->enabled = true;
$platform->debugMode = true;
$platform->save();

This allows debug-level logging to be activated for specific platforms only.

The onLaunch method

The onLaunch method may be used to:

  • create the user account if it does not already exist (or update it if it does);
  • create any workspace required for the resource link if it does not already exist (or update it if it does);
  • establish a new session for the user (or otherwise log the user into the tool application);
  • keep a record of the return URL for the platform (for example, in a session variable);
  • set the URL for the home page of the application so the user may be redirected to it.

Even though a request may be in accordance with the LTI specification, a tool may still choose to reject it because, for example, not all of the required data has been passed. A request may be rejected as follows:

  • set the ok property to false;
  • optionally set an error message to return to the user (if the platform supports this facility).

For example:

function onLaunch() {

    ...

    $this->ok = false;
    $this->reason = 'Incomplete data in launch message';

}

Content-item/deep linking and registration messages

If your tool also supports the Content-Item (Deep Linking) message or LTI 2 registration messages, then their associated method should also be overridden; for example:

use ceLTIc\LTI;

class MyApp extends LTI\Tool {

    function onLaunch() {

      // Insert code here to handle incoming launches - use the user, context
      // and resourceLink properties to access the current user, context and resource link.

    }

    function onContentItem() {

      // Insert code here to handle incoming content-item requests - use the user and context
      // properties to access the current user and context.

    }

    function onRegister() {

      // Insert code here to handle incoming registration requests - use the user
      // property to access the current user.

    }

    function onError() {

      // Insert code here to handle errors on incoming connections - do not expect
      // the user, context and resourceLink properties to be populated but check the reason
      // property for the cause of the error.  Return TRUE if the error was fully
      // handled by this method.

    }

}

$tool = new MyApp($dataConnector);
$tool->handleRequest();

Protecting a consumer key

When a connection between a platform and a tool is secured using a consumer key and a shared secret there are some risks to be aware of:

  • launch requests will continue to be accepted even if the platform's license has expired;
  • if the consumer key is used to submit launch requests from more than one platform, 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 license expires. Alternatively, the dates for any license may be recorded for the platform so that the library automatically makes the appropriate check when a launch request is received.

The second risk can be alleviated by using LTI 1.3 or by setting the protected property of the tool consumer. This will cause launch requests to be only accepted from platforms 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 platform sending a value for the tool_consumer_guid parameter and each tool consumer instance having a unique value for this parameter.

The following code illustrates how these options may be set:

use ceLTIc\LTI;

// load the platform record
$platform = LTI\Platform::fromConsumerKey('testing.edu', $dataConnector);

// set an expiry date for 30 days time
$platform->enableUntil = time() + (30 * 24 * 60 * 60);

// protect use of the consumer key to a single platform
$platform->protected = true;

// save the changes
$platform->save();

Note that the default value of the enableFrom property is null which means that access is available immediately. A null value for the enableUntil property means that access does not automatically expire (this is also the default).

Content-item/deep linking resource link IDs

One of the differences in handling a content-item/deep linking message request is that any LTI links your tool passes back to be created will not yet have an associated resource link ID. One solution to this is to create an internal resource link ID for the resource and add this as a custom parameter to the link with a name of content_item_id. When a launch request is received from a resource link ID which is not recognised and this custom parameter is present, a check is made for a resource link with the value of the parameter. If found, the resource link ID is updated with the resource link ID from the launch request and the custom parameter will be ignored on any subsequent launches. In this way, the resource created via a content-item request will be automatically connected to the resource link created in the platform. For example, here is some sample code based on this workflow implemented in the sample Rating application:

...
      $item = new LTI\ContentItem('LtiLink');
      $item->setMediaType(LTI\ContentItem::LTI_LINK_MEDIA_TYPE);
      $item->setTitle($_SESSION['title']);
      $item->setText($_SESSION['text']);
      $item->icon = new LTI\ContentItemImage(getAppUrl() . 'images/icon50.png', 50, 50);
      $item->custom = array('content_item_id' => $_SESSION['resource_id']);
      $formParams['content_items'] = LTI\ContentItem::toJson($item);
      if (!is_null($_SESSION['data'])) {
          $formParams['data'] = $_SESSION['data'];
      }
      $dataConnector = LTI\DataConnector\DataConnector::getDataConnector($db, DB_TABLENAME_PREFIX);
      $platform = LTI\Platform::fromRecordId($_SESSION['consumer_pk'], $dataConnector);
      $formParams = $platform->signParameters($_SESSION['return_url'], 'ContentItemSelection', $_SESSION['lti_version'], $formParams);
      $page = LTI\Util::sendForm($_SESSION['return_url'], $formParams);
      echo $page;
      exit;
...

The $_SESSION['resource_id'] variable contains a GUID generated on launch; this is used as the placeholder until the first launch of this item is performed and the validation of the request will automatically replace this resource link ID with the one passed in the launch parameters.

Share keys

There is typically a one-to-one relationship between a link created in a platform and a resource in a tool. When the resurces offered by a tool are sharable items, such as content, this might be a many-to-one relationship, where many links in one or more platforms can be safely associated with the same resource in the tool. However, when the resources offered by a tool are not suitable for sharing (for example, a group activity) the relationship is likely to be restricted to one-to-one, with each link in the platform being associated with its own acitvity within the tool. But there may be times when an instructor would like to have students from more than one course or from more than one platform (i.e. from more than one link) to engage together in an activity. In such cases a tool could implement a mechanism by which an instructor identifies which links are to be grouped together and this mapping used to direct students into the shared activity, but this may require giving access to data from other platforms and having a convenient mechansim for identifying the links (which may have still to be created). Thus, this library implements a simple mechanism of share keys which can be used to enable users launching from multiple links to be directed into a single resource at the tool end. The process works as follows:

  1. One of the resource links is chosen to be the primary connection from which the sharing is controlled;
  2. An instructor launched from the primary link generates a share key;
  3. The instructor passes the share key to an instructor of a (secondary) link to be included in the shared activity;
  4. The instructor adds the share key to the custom parameters for their secondary link within the platforms;
  5. When the secondary link is launched, the library reports the user as having launched from the primary link and so a tool will automatically allow them to share the activity;
  6. A share key can only be used once; a new one should be generated for each link which is to be allowed to share the primary activity.

The secondary links may exist on different platforms provided they are all configured to use the same instance of the tool. When generating a share key, an instructor may choose to automatically approve the link which uses it, or not. In the latter case, all launches from the secondary link will fail until the instructor approves it; at least one launch will be required to activate the share arrangement before it can be approved.

A share key is of the form share_key=xyz, where xyz is the unique value generated. A share key value may have a length of up to 32 characters. The share key custom parameter is activated on the first launch from the secondary link; thereafter its value is ignored but its presence is required to indicate that the sharing option is still required.

When a sharing arrangement is in place, in the Tool's onLaunch method, the resoureLink property will refer to the primary link. The resourceLink property of the userResult object will refer to the link from which the user actually launched. It may be necessary to keep a record of both in your application.

An example implementation of share keys can be found in the sample LTI Rating application. Note that the mechanism does depend upon a platform allowing custom parameters to be specified for individual LTI links; thus, it cannot be used with platforms such as Canvas which do not currently support this.

Clone this wiki locally