-
Notifications
You must be signed in to change notification settings - Fork 38
Usage
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 (deprecated, removed in version 5+)
- 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 (see the WordPress LTI Platform plugin for an example).
The handling of LTI messages and service requests are covered in separate sections. Whilst originally written for use by Tools, the library has been extended to provide support for Platforms.
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 (or see for version 4). The sample Rating application provides a working example of the usage of this library.
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.
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\DataConnector;
$db = mysql_connect($dbServer, $dbUser, $dbPassword);
mysql_select_db($dbSchema);
$dataConnector = DataConnector::getDataConnector($db, 'app1_');
(This has been tested with MySQL Server 8.0.)
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\DataConnector;
$db = mysqli_connect($dbServer, $dbUser, $dbPassword);
mysqli_select_db($dbSchema);
$dataConnector = DataConnector::getDataConnector($db, 'app1_');
(This has been tested with MySQL Server 8.0.)
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\DataConnector;
/*
* Example: $dbServer = '(local)';
*/
$connectionInfo = array('Database' => $dbServer, 'UID' => $dbUser, 'PWD' => $dbPassword);
$db = sqlsrv_connect($dbSchema, $connectionInfo);
$dataConnector = DataConnector::getDataConnector($db, 'app1_', 'sqlsrv');
(This has been tested with SQL Sever 2019.)
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\DataConnector;
/*
* Example: $dbSchema = '//localhost:1521/orcl';
*/
$db = oci_connect($dbSchema, $dbUser, $dbPassword);
$dataConnector = DataConnector::getDataConnector($db, 'app1_', 'oci');
(This has been tested with Oracle 21c.)
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\DataConnector;
/*
Example: $dbServer= 'host=localhost port=5432 dbname=test';
*/
$db = pg_connect($dbServer, $dbUser, $dbPassword);
$dataConnector = DataConnector::getDataConnector($db, 'app1_', 'pg');
(This has been tested with PostgreSQL 14.2.)
Database connections can be also be made using the PHP PDO interface. Here are some examples.
use ceLTIc\LTI\DataConnector\DataConnector;
$db = new PDO("mysql:host={$dbHost};dbname={$dbSchema}", $dbUser, $dbPassword);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // optional
$dataConnector = DataConnector::getDataConnector($db);
(This has been tested with MySQL Server 8.0.)
use ceLTIc\LTI\DataConnector\DataConnector;
$db = new PDO('sqlite::memory:');
$dataConnector = DataConnector::getDataConnector($db);
use ceLTIc\LTI\DataConnector\DataConnector;
$db = new PDO("mssql:host={$dbHost};dbname={$dbSchema}", $dbUser, $dbPassword);
$dataConnector = DataConnector::getDataConnector($db);
(This has been tested with SQL Sever 2019.)
use ceLTIc\LTI\DataConnector\DataConnector;
$db = new PDO("oci:dbname={$dbSchema}", $dbUser, $dbPassword);
$dataConnector = DataConnector::getDataConnector($db);
(This has been tested with Oracle 21c.)
use ceLTIc\LTI\DataConnector\DataConnector;
$db = new PDO("pgsql:host=localhost;port=5432;dbname=test", $dbUser, $dbPassword);
$dataConnector = DataConnector::getDataConnector($db);
(This has been tested with PostgreSQL 14.2.)
Bespoke connectors can be created by writing a sub-class of the DataConnector\DataConnector
class (see the WordPress LTI Platform plugin for an example). 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).
Since nonce values have a short lifespan, their storage in a database table is an overhead which can be avoided by using a module such as Memcache. When the Memcache option is enabled, the data connectors will use it instead of the platform nonce table. A Memcache server on the default port of localhost can be enabled as follows:
use ceLTIc\LTI\DataConnector\DataConnector;
$enabled = DataConnector::useMemcache('localhost');
Alternatively, a port can be specified as follows:
use ceLTIc\LTI\DataConnector\DataConnector;
$enabled = DataConnector::useMemcache('localhost', 11211);
The return value of the useMemcache
method confirms whether the option was successfully enabled. The request will fail (and the database used instead) if, for example, the Memcache module is not installed or if it was not possible to connect to the specified server. The option can be disabled by passing an empty string as the host value, for example:
use ceLTIc\LTI\DataConnector\DataConnector;
DataConnector::useMemcache('');
The current status of the option can be requested by omitting the parameters:
use ceLTIc\LTI\DataConnector\DataConnector;
$isEnabled = DataConnector::useMemcache();
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 initialised 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 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. The library will update this property with any public keys retrieved from a JKU endpoint unless this is disabled by setting the Util::$disableFetchedPublicKeysSave
property to false
. 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.
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.
No initialisation is required for tools when using LTI 1.0, 1.1 or 1.2, apart from defining the callback methods for handling the messages to be supported (see the Messages section for more details).
LTI 2 involves a registration process during which the tool must provide a description of itself in a Tool Proxy document. This document is generated automatically from the tool definition and so it must be configured with the required information. This example is an extract from the sample Rating application:
class RatingTool extends LTI\Tool
{
function __construct($dataConnector)
{
parent::__construct($dataConnector);
$this->baseUrl = getAppUrl();
$this->vendor = new Profile\Item('celtic', 'ceLTIc Project', 'ceLTIc Project', 'https://www.celtic-project.org/');
$this->product = new Profile\Item('d751f24f-140e-470f-944c-2d92b114db40', 'Rating',
'Sample LTI tool to create lists of items to be rated.', 'http://www.spvsoftwareproducts.com/php/rating/', VERSION);
$requiredMessages = array(new Profile\Message('basic-lti-launch-request', 'connect.php', array('User.id', 'Membership.role')));
$optionalMessages = array(new Profile\Message('ContentItemSelectionRequest', 'connect.php',
array('User.id', 'Membership.role')),
new Profile\Message('DashboardRequest', 'connect.php', array('User.id'))
);
$this->resourceHandlers[] = new Profile\ResourceHandler(
new Profile\Item('rating', 'Rating app', 'An example tool which generates lists of items for rating.'),
'images/icon50.png', $requiredMessages, $optionalMessages);
$this->requiredServices[] = new Profile\ServiceDefinition(array('application/vnd.ims.lti.v2.toolproxy+json'), array('POST'));
}
...
It includes details of the:
- vendor
- product
- a resource handler for each tool comprising:
- messages supported (see below):
- tool details including its name and a path to an icon
- LTI messages required by the tool
- LTI messages which are optional (ones which are supported but are not required for the operation of the tool)
- messages supported (see below):
- services used (see below):
- services required by the tool(s)
- optional services (ones which would be used if available but are not essential for the operation of the tool)
A message definition includes:
- LTI message type
- path to which the message should be sent by the platform
- an array of capabilities required
- custom parameters to be included with the message:
- variable parameters (ones where the value of the parameter is the name of a custom substitution variable)
- fixed parameters (ones where the value of the parameter is a constant)
A service definition includes:
- the media type of the service
- the HTTP methods supported/used
All paths are relative to the baseUrl
.
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('SIGNATURE_METHOD', 'RS256');
define('KID', ''); // A random string to identify the key value
define('PRIVATE_KEY', <<< EOD
-----BEGIN RSA PRIVATE KEY-----
[Insert private key here]
-----END RSA PRIVATE KEY-----
EOD;
Multiple private keys will need to be defined if more than one signature method is to be supported.
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->signatureMethod = SIGNATURE_METHOD;
$this->jku = 'https://example.tool.com/jwks.php';
$this->kid = KID;
$this->rsaKey = PRIVATE_KEY;
$this->requiredScopes = array(
LTI\Service\LineItem::$SCOPE,
LTI\Service\Score::$SCOPE,
LTI\Service\Membership::$SCOPE
);
}
...
}
$tool = new MyApp($dataConnector);
or, alternatively, by assignment:
use ceLTIc\LTI;
class MyApp extends LTI\Tool {
...
}
$tool = new MyApp($dataConnector);
$ool->signatureMethod = SIGNATURE_METHOD;
$tool->jku = 'https://example.tool.com/jwks.php';
$tool->kid = KID;
$tool->rsaKey = PRIVATE_KEY;
$tool->requiredScopes = array(
LTI\Service\LineItem::$SCOPE,
LTI\Service\Score::$SCOPE,
LTI\Service\Membership::$SCOPE
);
The IMS supports the use of HTTP GET or HTTP POST when a tool sends an authentication request to the platform. By default the library uses HTTP POST, but this can be changed to using HTTP GET by setting the $authenticateUsingGet
static property to true
:
Tool::$authenticateUsingGet = true;
The 1EdTech specification for dynamically registering an LTI 1.3 tool has been implemented by some LTI platforms. 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. The configuration information passed to the platform during this process is taken from the tool's initialisation data as used for LTI 2 configurations (see above). Specifically, a tool must be defined with the following details:
- baseUrl
- product:
- name
- description
- resource handler:
- message:
- type
- path
- capabilities (optional)
- custom parameters (optional)
- icon (optional)
- message:
- required service scopes (may be empty)
Note that messages can be defined using the pre LTI 1.3 message types to allow compatibility with earlier versions of LTI, they will be automatically translated to their new type as required.
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).
© 2022 Stephen P Vickers. All Rights Reserved.