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

The library acts as an interface between the two systems to be connected using LTI. It captures data en route from a platform to a tool and saves these in a storage device, such as a database. Support for a range of databases is included in the library:

  • MySQL/MySQLi
  • SQL Server
  • Oracle
  • PostgreSQL
  • PHP PDO drivers

But a bespoke data connector can be defined to handle other forms of storage or to accommodate different database structures.

The handling of LTI messages and service requests are covered in separate sections.

Library class definitions

This page describes the usage of version 4 of the library; follow this link for details relating to 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

LTI 1.3 replaces the OAuth 1.0A security model with the use of JSON Web Tokens (JWTs) signed with an RSA private key. The matching public key is used to verify the signed JWT. Platforms and tools may share their public keys with the other party out-of-band, or via a JSON Web Key Set (JWKS) URL (JKU).

A tool should initialise its platform object with the data provided by it; for example:

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;  // a public key is not required if a JKU is available
$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 property value 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 the public key retrieved from this endpoint. Thus, the verification should not fail when a key is changed; the system will fallback to 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.

The tool should be initialised with its private key for use with signing service requests of return messages to the platform. The private key must be kept secure; for the sake of this example, a PHP named constant is used; for example:

define('PRIVATE_KEY',  <<< EOD
-----BEGIN RSA PRIVATE KEY-----
[Insert private key here]
-----END RSA PRIVATE KEY-----
EOD;

The tool can be initialised in its __construct method, for example:

use ceLTIc\LTI;

class MyApp extends LTI\Tool {

    function __construct($dataConnector)
    {
        parent::__construct($dataConnector);
        $this->rsaKey = PRIVATE_KEY;
    }
...
}

$tool = new MyApp($dataConnector);

or, alternatively, by assignment:

use ceLTIc\LTI;

class MyApp extends LTI\Tool {
...
}

$tool = new MyApp($dataConnector);
$tool->rsaKey = PRIVATE_KEY;

Dynamic registration

Moodle 3.10 introduced a mechnism for dynamically registering an LTI 1.3 tool in its platform. This is supported via the onRegistration method of the Tool class; see the Messages page for more information. This allows a tool to automatically create a platform definition based on details it provides during this process.

Supporting 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.

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).

Clone this wiki locally