-
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/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. 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. 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;
$db = mysql_connect($dbServer, $dbUser, $dbPassword);
mysql_select_db($dbSchema);
$dataConnector = DataConnector\DataConnector::getDataConnector($db, 'app1_');
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_');
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.)
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 18.0.)
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.)
Database connections can be also be made using the PHP PDO interface. Here are some examples.
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);
use ceLTIc\LTI\DataConnector;
$db = new PDO('sqlite::memory:');
$dataConnector = DataConnector\DataConnector::getDataConnector($db);
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.)
use ceLTIc\LTI\DataConnector;
$db = new PDO("oci:dbname={$dbSchema}", $dbUser, $dbPassword);
$dataConnector = DataConnector\DataConnector::getDataConnector($db);
(This has been tested with Oracle 18.0.)
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.)
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).
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, 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.
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
);
Moodle 3.10 introduced a mechanism 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. 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.