MDL-62599 mod_lti: Add platform support for LTI 1.3
This commit is contained in:
committed by
Jake Dallimore
parent
a411b499b9
commit
27cbb5965e
@@ -64,6 +64,7 @@ lib/amd/src/adapter.js
|
||||
lib/validateurlsyntax.php
|
||||
lib/amd/src/popper.js
|
||||
lib/geopattern-php/
|
||||
lib/php-jwt/
|
||||
media/player/videojs/amd/src/video-lazy.js
|
||||
media/player/videojs/amd/src/Youtube-lazy.js
|
||||
media/player/videojs/videojs/
|
||||
|
||||
@@ -65,6 +65,7 @@ lib/amd/src/adapter.js
|
||||
lib/validateurlsyntax.php
|
||||
lib/amd/src/popper.js
|
||||
lib/geopattern-php/
|
||||
lib/php-jwt/
|
||||
media/player/videojs/amd/src/video-lazy.js
|
||||
media/player/videojs/amd/src/Youtube-lazy.js
|
||||
media/player/videojs/videojs/
|
||||
|
||||
@@ -1741,10 +1741,6 @@ class core_plugin_manager {
|
||||
'gregorian'
|
||||
),
|
||||
|
||||
'customfield' => array(
|
||||
'checkbox', 'date', 'select', 'text', 'textarea'
|
||||
),
|
||||
|
||||
'coursereport' => array(
|
||||
// Deprecated!
|
||||
),
|
||||
@@ -1810,7 +1806,7 @@ class core_plugin_manager {
|
||||
),
|
||||
|
||||
'ltiservice' => array(
|
||||
'gradebookservices', 'memberships', 'profile', 'toolproxy', 'toolsettings'
|
||||
'gradebookservices', 'memberships', 'profile', 'toolproxy', 'toolsettings', 'basicoutcomes'
|
||||
),
|
||||
|
||||
'mlbackend' => array(
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
Copyright (c) 2011, Neuman Vong
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of Neuman Vong nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,200 @@
|
||||
[](https://travis-ci.org/firebase/php-jwt)
|
||||
[](https://packagist.org/packages/firebase/php-jwt)
|
||||
[](https://packagist.org/packages/firebase/php-jwt)
|
||||
[](https://packagist.org/packages/firebase/php-jwt)
|
||||
|
||||
PHP-JWT
|
||||
=======
|
||||
A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519).
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use composer to manage your dependencies and download PHP-JWT:
|
||||
|
||||
```bash
|
||||
composer require firebase/php-jwt
|
||||
```
|
||||
|
||||
Example
|
||||
-------
|
||||
```php
|
||||
<?php
|
||||
use \Firebase\JWT\JWT;
|
||||
|
||||
$key = "example_key";
|
||||
$token = array(
|
||||
"iss" => "http://example.org",
|
||||
"aud" => "http://example.com",
|
||||
"iat" => 1356999524,
|
||||
"nbf" => 1357000000
|
||||
);
|
||||
|
||||
/**
|
||||
* IMPORTANT:
|
||||
* You must specify supported algorithms for your application. See
|
||||
* https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
|
||||
* for a list of spec-compliant algorithms.
|
||||
*/
|
||||
$jwt = JWT::encode($token, $key);
|
||||
$decoded = JWT::decode($jwt, $key, array('HS256'));
|
||||
|
||||
print_r($decoded);
|
||||
|
||||
/*
|
||||
NOTE: This will now be an object instead of an associative array. To get
|
||||
an associative array, you will need to cast it as such:
|
||||
*/
|
||||
|
||||
$decoded_array = (array) $decoded;
|
||||
|
||||
/**
|
||||
* You can add a leeway to account for when there is a clock skew times between
|
||||
* the signing and verifying servers. It is recommended that this leeway should
|
||||
* not be bigger than a few minutes.
|
||||
*
|
||||
* Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef
|
||||
*/
|
||||
JWT::$leeway = 60; // $leeway in seconds
|
||||
$decoded = JWT::decode($jwt, $key, array('HS256'));
|
||||
|
||||
?>
|
||||
```
|
||||
Example with RS256 (openssl)
|
||||
----------------------------
|
||||
```php
|
||||
<?php
|
||||
use \Firebase\JWT\JWT;
|
||||
|
||||
$privateKey = <<<EOD
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn
|
||||
vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9
|
||||
5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB
|
||||
AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz
|
||||
bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J
|
||||
Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1
|
||||
cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5
|
||||
5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck
|
||||
ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe
|
||||
k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb
|
||||
qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k
|
||||
eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm
|
||||
B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
EOD;
|
||||
|
||||
$publicKey = <<<EOD
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H
|
||||
4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t
|
||||
0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4
|
||||
ehde/zUxo6UvS7UrBQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
EOD;
|
||||
|
||||
$token = array(
|
||||
"iss" => "example.org",
|
||||
"aud" => "example.com",
|
||||
"iat" => 1356999524,
|
||||
"nbf" => 1357000000
|
||||
);
|
||||
|
||||
$jwt = JWT::encode($token, $privateKey, 'RS256');
|
||||
echo "Encode:\n" . print_r($jwt, true) . "\n";
|
||||
|
||||
$decoded = JWT::decode($jwt, $publicKey, array('RS256'));
|
||||
|
||||
/*
|
||||
NOTE: This will now be an object instead of an associative array. To get
|
||||
an associative array, you will need to cast it as such:
|
||||
*/
|
||||
|
||||
$decoded_array = (array) $decoded;
|
||||
echo "Decode:\n" . print_r($decoded_array, true) . "\n";
|
||||
?>
|
||||
```
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
#### 5.0.0 / 2017-06-26
|
||||
- Support RS384 and RS512.
|
||||
See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)!
|
||||
- Add an example for RS256 openssl.
|
||||
See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)!
|
||||
- Detect invalid Base64 encoding in signature.
|
||||
See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)!
|
||||
- Update `JWT::verify` to handle OpenSSL errors.
|
||||
See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)!
|
||||
- Add `array` type hinting to `decode` method
|
||||
See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)!
|
||||
- Add all JSON error types.
|
||||
See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)!
|
||||
- Bugfix 'kid' not in given key list.
|
||||
See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)!
|
||||
- Miscellaneous cleanup, documentation and test fixes.
|
||||
See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115),
|
||||
[#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and
|
||||
[#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman),
|
||||
[@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)!
|
||||
|
||||
#### 4.0.0 / 2016-07-17
|
||||
- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)!
|
||||
- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)!
|
||||
- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)!
|
||||
- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)!
|
||||
|
||||
#### 3.0.0 / 2015-07-22
|
||||
- Minimum PHP version updated from `5.2.0` to `5.3.0`.
|
||||
- Add `\Firebase\JWT` namespace. See
|
||||
[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to
|
||||
[@Dashron](https://github.com/Dashron)!
|
||||
- Require a non-empty key to decode and verify a JWT. See
|
||||
[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to
|
||||
[@sjones608](https://github.com/sjones608)!
|
||||
- Cleaner documentation blocks in the code. See
|
||||
[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to
|
||||
[@johanderuijter](https://github.com/johanderuijter)!
|
||||
|
||||
#### 2.2.0 / 2015-06-22
|
||||
- Add support for adding custom, optional JWT headers to `JWT::encode()`. See
|
||||
[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to
|
||||
[@mcocaro](https://github.com/mcocaro)!
|
||||
|
||||
#### 2.1.0 / 2015-05-20
|
||||
- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew
|
||||
between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)!
|
||||
- Add support for passing an object implementing the `ArrayAccess` interface for
|
||||
`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)!
|
||||
|
||||
#### 2.0.0 / 2015-04-01
|
||||
- **Note**: It is strongly recommended that you update to > v2.0.0 to address
|
||||
known security vulnerabilities in prior versions when both symmetric and
|
||||
asymmetric keys are used together.
|
||||
- Update signature for `JWT::decode(...)` to require an array of supported
|
||||
algorithms to use when verifying token signatures.
|
||||
|
||||
|
||||
Tests
|
||||
-----
|
||||
Run the tests using phpunit:
|
||||
|
||||
```bash
|
||||
$ pear install PHPUnit
|
||||
$ phpunit --configuration phpunit.xml.dist
|
||||
PHPUnit 3.7.10 by Sebastian Bergmann.
|
||||
.....
|
||||
Time: 0 seconds, Memory: 2.50Mb
|
||||
OK (5 tests, 5 assertions)
|
||||
```
|
||||
|
||||
New Lines in private keys
|
||||
-----
|
||||
|
||||
If your private key contains `\n` characters, be sure to wrap it in double quotes `""`
|
||||
and not single quotes `''` in order to properly interpret the escaped characters.
|
||||
|
||||
License
|
||||
-------
|
||||
[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause).
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||
"homepage": "https://github.com/firebase/php-jwt",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Neuman Vong",
|
||||
"email": "[email protected]",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Anant Narayanan",
|
||||
"email": "[email protected]",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Firebase\\JWT\\": "src"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": " 4.8.35"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
Description of php-jwt library import into Moodle
|
||||
|
||||
Instructions
|
||||
------------
|
||||
1. Visit [https://github.com/firebase/php-jwt].
|
||||
2. Click on 'X releases'.
|
||||
3. Download the latest release.
|
||||
4. Unzip it in lib as php-jwt.
|
||||
5. Update entry for this library in lib/thirdpartylibs.xml.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace Firebase\JWT;
|
||||
|
||||
class BeforeValidException extends \UnexpectedValueException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace Firebase\JWT;
|
||||
|
||||
class ExpiredException extends \UnexpectedValueException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
<?php
|
||||
|
||||
namespace Firebase\JWT;
|
||||
use \DomainException;
|
||||
use \InvalidArgumentException;
|
||||
use \UnexpectedValueException;
|
||||
use \DateTime;
|
||||
|
||||
/**
|
||||
* JSON Web Token implementation, based on this spec:
|
||||
* https://tools.ietf.org/html/rfc7519
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Authentication
|
||||
* @package Authentication_JWT
|
||||
* @author Neuman Vong <[email protected]>
|
||||
* @author Anant Narayanan <[email protected]>
|
||||
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
|
||||
* @link https://github.com/firebase/php-jwt
|
||||
*/
|
||||
class JWT
|
||||
{
|
||||
|
||||
/**
|
||||
* When checking nbf, iat or expiration times,
|
||||
* we want to provide some extra leeway time to
|
||||
* account for clock skew.
|
||||
*/
|
||||
public static $leeway = 180;
|
||||
|
||||
/**
|
||||
* Allow the current timestamp to be specified.
|
||||
* Useful for fixing a value within unit testing.
|
||||
*
|
||||
* Will default to PHP time() value if null.
|
||||
*/
|
||||
public static $timestamp = null;
|
||||
|
||||
public static $supported_algs = array(
|
||||
'HS256' => array('hash_hmac', 'SHA256'),
|
||||
'HS512' => array('hash_hmac', 'SHA512'),
|
||||
'HS384' => array('hash_hmac', 'SHA384'),
|
||||
'RS256' => array('openssl', 'SHA256'),
|
||||
'RS384' => array('openssl', 'SHA384'),
|
||||
'RS512' => array('openssl', 'SHA512'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Decodes a JWT string into a PHP object.
|
||||
*
|
||||
* @param string $jwt The JWT
|
||||
* @param string|array $key The key, or map of keys.
|
||||
* If the algorithm used is asymmetric, this is the public key
|
||||
* @param array $allowed_algs List of supported verification algorithms
|
||||
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
|
||||
*
|
||||
* @return object The JWT's payload as a PHP object
|
||||
*
|
||||
* @throws UnexpectedValueException Provided JWT was invalid
|
||||
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
|
||||
* @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
|
||||
* @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
|
||||
* @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
|
||||
*
|
||||
* @uses jsonDecode
|
||||
* @uses urlsafeB64Decode
|
||||
*/
|
||||
public static function decode($jwt, $key, array $allowed_algs = array())
|
||||
{
|
||||
$timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;
|
||||
|
||||
if (empty($key)) {
|
||||
throw new InvalidArgumentException('Key may not be empty');
|
||||
}
|
||||
$tks = explode('.', $jwt);
|
||||
if (count($tks) != 3) {
|
||||
throw new UnexpectedValueException('Wrong number of segments');
|
||||
}
|
||||
list($headb64, $bodyb64, $cryptob64) = $tks;
|
||||
if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
|
||||
throw new UnexpectedValueException('Invalid header encoding');
|
||||
}
|
||||
if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
|
||||
throw new UnexpectedValueException('Invalid claims encoding');
|
||||
}
|
||||
if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
|
||||
throw new UnexpectedValueException('Invalid signature encoding');
|
||||
}
|
||||
if (empty($header->alg)) {
|
||||
throw new UnexpectedValueException('Empty algorithm');
|
||||
}
|
||||
if (empty(static::$supported_algs[$header->alg])) {
|
||||
throw new UnexpectedValueException('Algorithm not supported');
|
||||
}
|
||||
if (!in_array($header->alg, $allowed_algs)) {
|
||||
throw new UnexpectedValueException('Algorithm not allowed');
|
||||
}
|
||||
if (is_array($key) || $key instanceof \ArrayAccess) {
|
||||
if (isset($header->kid)) {
|
||||
if (!isset($key[$header->kid])) {
|
||||
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
|
||||
}
|
||||
$key = $key[$header->kid];
|
||||
} else {
|
||||
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
|
||||
}
|
||||
}
|
||||
|
||||
// Check the signature
|
||||
if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
|
||||
throw new SignatureInvalidException('Signature verification failed');
|
||||
}
|
||||
|
||||
// Check if the nbf if it is defined. This is the time that the
|
||||
// token can actually be used. If it's not yet that time, abort.
|
||||
if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
|
||||
throw new BeforeValidException(
|
||||
'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
|
||||
);
|
||||
}
|
||||
|
||||
// Check that this token has been created before 'now'. This prevents
|
||||
// using tokens that have been created for later use (and haven't
|
||||
// correctly used the nbf claim).
|
||||
if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
|
||||
throw new BeforeValidException(
|
||||
'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
|
||||
);
|
||||
}
|
||||
|
||||
// Check if this token has expired.
|
||||
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
|
||||
throw new ExpiredException('Expired token');
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts and signs a PHP object or array into a JWT string.
|
||||
*
|
||||
* @param object|array $payload PHP object or array
|
||||
* @param string $key The secret key.
|
||||
* If the algorithm used is asymmetric, this is the private key
|
||||
* @param string $alg The signing algorithm.
|
||||
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
|
||||
* @param mixed $keyId
|
||||
* @param array $head An array with header elements to attach
|
||||
*
|
||||
* @return string A signed JWT
|
||||
*
|
||||
* @uses jsonEncode
|
||||
* @uses urlsafeB64Encode
|
||||
*/
|
||||
public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
|
||||
{
|
||||
$header = array('typ' => 'JWT', 'alg' => $alg);
|
||||
if ($keyId !== null) {
|
||||
$header['kid'] = $keyId;
|
||||
}
|
||||
if ( isset($head) && is_array($head) ) {
|
||||
$header = array_merge($head, $header);
|
||||
}
|
||||
$segments = array();
|
||||
$segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
|
||||
$segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
|
||||
$signing_input = implode('.', $segments);
|
||||
|
||||
$signature = static::sign($signing_input, $key, $alg);
|
||||
$segments[] = static::urlsafeB64Encode($signature);
|
||||
|
||||
return implode('.', $segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a string with a given key and algorithm.
|
||||
*
|
||||
* @param string $msg The message to sign
|
||||
* @param string|resource $key The secret key
|
||||
* @param string $alg The signing algorithm.
|
||||
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
|
||||
*
|
||||
* @return string An encrypted message
|
||||
*
|
||||
* @throws DomainException Unsupported algorithm was specified
|
||||
*/
|
||||
public static function sign($msg, $key, $alg = 'HS256')
|
||||
{
|
||||
if (empty(static::$supported_algs[$alg])) {
|
||||
throw new DomainException('Algorithm not supported');
|
||||
}
|
||||
list($function, $algorithm) = static::$supported_algs[$alg];
|
||||
switch($function) {
|
||||
case 'hash_hmac':
|
||||
return hash_hmac($algorithm, $msg, $key, true);
|
||||
case 'openssl':
|
||||
$signature = '';
|
||||
$success = openssl_sign($msg, $signature, $key, $algorithm);
|
||||
if (!$success) {
|
||||
throw new DomainException("OpenSSL unable to sign data");
|
||||
} else {
|
||||
return $signature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a signature with the message, key and method. Not all methods
|
||||
* are symmetric, so we must have a separate verify and sign method.
|
||||
*
|
||||
* @param string $msg The original message (header and body)
|
||||
* @param string $signature The original signature
|
||||
* @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key
|
||||
* @param string $alg The algorithm
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws DomainException Invalid Algorithm or OpenSSL failure
|
||||
*/
|
||||
private static function verify($msg, $signature, $key, $alg)
|
||||
{
|
||||
if (empty(static::$supported_algs[$alg])) {
|
||||
throw new DomainException('Algorithm not supported');
|
||||
}
|
||||
|
||||
list($function, $algorithm) = static::$supported_algs[$alg];
|
||||
switch($function) {
|
||||
case 'openssl':
|
||||
$success = openssl_verify($msg, $signature, $key, $algorithm);
|
||||
if ($success === 1) {
|
||||
return true;
|
||||
} elseif ($success === 0) {
|
||||
return false;
|
||||
}
|
||||
// returns 1 on success, 0 on failure, -1 on error.
|
||||
throw new DomainException(
|
||||
'OpenSSL error: ' . openssl_error_string()
|
||||
);
|
||||
case 'hash_hmac':
|
||||
default:
|
||||
$hash = hash_hmac($algorithm, $msg, $key, true);
|
||||
if (function_exists('hash_equals')) {
|
||||
return hash_equals($signature, $hash);
|
||||
}
|
||||
$len = min(static::safeStrlen($signature), static::safeStrlen($hash));
|
||||
|
||||
$status = 0;
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$status |= (ord($signature[$i]) ^ ord($hash[$i]));
|
||||
}
|
||||
$status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash));
|
||||
|
||||
return ($status === 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a JSON string into a PHP object.
|
||||
*
|
||||
* @param string $input JSON string
|
||||
*
|
||||
* @return object Object representation of JSON string
|
||||
*
|
||||
* @throws DomainException Provided string was invalid JSON
|
||||
*/
|
||||
public static function jsonDecode($input)
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
|
||||
/** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
|
||||
* to specify that large ints (like Steam Transaction IDs) should be treated as
|
||||
* strings, rather than the PHP default behaviour of converting them to floats.
|
||||
*/
|
||||
$obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
|
||||
} else {
|
||||
/** Not all servers will support that, however, so for older versions we must
|
||||
* manually detect large ints in the JSON string and quote them (thus converting
|
||||
*them to strings) before decoding, hence the preg_replace() call.
|
||||
*/
|
||||
$max_int_length = strlen((string) PHP_INT_MAX) - 1;
|
||||
$json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
|
||||
$obj = json_decode($json_without_bigints);
|
||||
}
|
||||
|
||||
if (function_exists('json_last_error') && $errno = json_last_error()) {
|
||||
static::handleJsonError($errno);
|
||||
} elseif ($obj === null && $input !== 'null') {
|
||||
throw new DomainException('Null result with non-null input');
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a PHP object into a JSON string.
|
||||
*
|
||||
* @param object|array $input A PHP object or array
|
||||
*
|
||||
* @return string JSON representation of the PHP object or array
|
||||
*
|
||||
* @throws DomainException Provided object could not be encoded to valid JSON
|
||||
*/
|
||||
public static function jsonEncode($input)
|
||||
{
|
||||
$json = json_encode($input);
|
||||
if (function_exists('json_last_error') && $errno = json_last_error()) {
|
||||
static::handleJsonError($errno);
|
||||
} elseif ($json === 'null' && $input !== null) {
|
||||
throw new DomainException('Null result with non-null input');
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a string with URL-safe Base64.
|
||||
*
|
||||
* @param string $input A Base64 encoded string
|
||||
*
|
||||
* @return string A decoded string
|
||||
*/
|
||||
public static function urlsafeB64Decode($input)
|
||||
{
|
||||
$remainder = strlen($input) % 4;
|
||||
if ($remainder) {
|
||||
$padlen = 4 - $remainder;
|
||||
$input .= str_repeat('=', $padlen);
|
||||
}
|
||||
return base64_decode(strtr($input, '-_', '+/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a string with URL-safe Base64.
|
||||
*
|
||||
* @param string $input The string you want encoded
|
||||
*
|
||||
* @return string The base64 encode of what you passed in
|
||||
*/
|
||||
public static function urlsafeB64Encode($input)
|
||||
{
|
||||
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a JSON error.
|
||||
*
|
||||
* @param int $errno An error number from json_last_error()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function handleJsonError($errno)
|
||||
{
|
||||
$messages = array(
|
||||
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
|
||||
JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
|
||||
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
|
||||
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
|
||||
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
|
||||
);
|
||||
throw new DomainException(
|
||||
isset($messages[$errno])
|
||||
? $messages[$errno]
|
||||
: 'Unknown JSON error: ' . $errno
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes in cryptographic strings.
|
||||
*
|
||||
* @param string
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function safeStrlen($str)
|
||||
{
|
||||
if (function_exists('mb_strlen')) {
|
||||
return mb_strlen($str, '8bit');
|
||||
}
|
||||
return strlen($str);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace Firebase\JWT;
|
||||
|
||||
class SignatureInvalidException extends \UnexpectedValueException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -323,4 +323,11 @@
|
||||
<license>MIT</license>
|
||||
<version>1.1.1</version>
|
||||
</library>
|
||||
<library>
|
||||
<location>php-jwt</location>
|
||||
<name>A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to RFC 7519</name>
|
||||
<license>BSD</license>
|
||||
<version>5.0.0</version>
|
||||
<licenseversion>3-Clause</licenseversion>
|
||||
</library>
|
||||
</libraries>
|
||||
|
||||
+35
-5
@@ -39,13 +39,43 @@ defined('MOODLE_INTERNAL') || die;
|
||||
require_once($CFG->dirroot . '/mod/lti/OAuth.php');
|
||||
require_once($CFG->dirroot . '/mod/lti/TrivialStore.php');
|
||||
|
||||
function get_oauth_key_from_headers() {
|
||||
/**
|
||||
*
|
||||
* @param int $typeid LTI type ID.
|
||||
* @param string[] $scopes Array of scopes which give permission for the current request.
|
||||
*
|
||||
* @return string|int|boolean The OAuth consumer key, the LTI type ID for the validated bearer token, otherwise false.
|
||||
*/
|
||||
function get_oauth_key_from_headers($typeid = null, $scopes = null) {
|
||||
global $DB;
|
||||
|
||||
$now = time();
|
||||
|
||||
$requestheaders = OAuthUtil::get_headers();
|
||||
|
||||
if (@substr($requestheaders['Authorization'], 0, 6) == "OAuth ") {
|
||||
$headerparameters = OAuthUtil::split_header($requestheaders['Authorization']);
|
||||
if (isset($requestheaders['Authorization'])) {
|
||||
if (substr($requestheaders['Authorization'], 0, 6) == "OAuth ") {
|
||||
$headerparameters = OAuthUtil::split_header($requestheaders['Authorization']);
|
||||
|
||||
return format_string($headerparameters['oauth_consumer_key']);
|
||||
return format_string($headerparameters['oauth_consumer_key']);
|
||||
} else if (empty($scopes)) {
|
||||
return true;
|
||||
} else if (substr($requestheaders['Authorization'], 0, 7) == 'Bearer ') {
|
||||
$tokenvalue = trim(substr($requestheaders['Authorization'], 7));
|
||||
$conditions = array('token' => $tokenvalue);
|
||||
if (!empty($typeid)) {
|
||||
$conditions['typeid'] = intval($typeid);
|
||||
}
|
||||
$token = $DB->get_record('lti_access_tokens', $conditions);
|
||||
if ($token) {
|
||||
// Log token access.
|
||||
$DB->set_field('lti_access_tokens', 'lastaccess', $now, array('id' => $token->id));
|
||||
$permittedscopes = json_decode($token->scope);
|
||||
if ((intval($token->validuntil) > $now) && !empty(array_intersect($scopes, $permittedscopes))) {
|
||||
return intval($token->typeid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -63,7 +93,7 @@ function handle_oauth_body_post($oauthconsumerkey, $oauthconsumersecret, $body,
|
||||
}
|
||||
}
|
||||
|
||||
if (@substr($requestheaders['Authorization'], 0, 6) == "OAuth ") {
|
||||
if (isset($requestheaders['Authorization']) && (substr($requestheaders['Authorization'], 0, 6) == "OAuth ")) {
|
||||
$headerparameters = OAuthUtil::split_header($requestheaders['Authorization']);
|
||||
$oauthbodyhash = $headerparameters['oauth_body_hash'];
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -26,9 +26,10 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 3.1
|
||||
*/
|
||||
define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/tool_type', 'mod_lti/events', 'mod_lti/keys',
|
||||
define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'core/modal_factory',
|
||||
'mod_lti/tool_type', 'mod_lti/events', 'mod_lti/keys',
|
||||
'core/str'],
|
||||
function($, ajax, notification, templates, toolType, ltiEvents, KEYS, str) {
|
||||
function($, ajax, notification, templates, modalFactory, toolType, ltiEvents, KEYS, str) {
|
||||
|
||||
var SELECTORS = {
|
||||
DELETE_BUTTON: '.delete',
|
||||
@@ -631,6 +632,52 @@ define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/t
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up the templates for the tool configuration modal on this tool type card.
|
||||
*
|
||||
* @method registerModal
|
||||
* @private
|
||||
* @param {JQuery} element jQuery object representing the tool card.
|
||||
*/
|
||||
var registerModal = function(element) {
|
||||
var trigger = $('#' + element.data('uniqid') + '-' + element.data('deploymentid'));
|
||||
var context = {
|
||||
'uniqid': element.data('uniqid'),
|
||||
'platformid': element.data('platformid'),
|
||||
'clientid': element.data('clientid'),
|
||||
'deploymentid': element.data('deploymentid'),
|
||||
'urls': {
|
||||
'publickeyset': element.data('publickeyseturl'),
|
||||
'accesstoken': element.data('accesstokenurl'),
|
||||
'authrequest': element.data('authrequesturl')
|
||||
}
|
||||
};
|
||||
var bodyPromise = templates.render('mod_lti/tool_config_modal_body', context);
|
||||
var mailTo = 'mailto:?subject=' + encodeURIComponent(element.data('mailtosubject')) +
|
||||
'&body=' + encodeURIComponent(element.data('platformidstr')) + ':%20' +
|
||||
encodeURIComponent(element.data('platformid')) + '%0D%0A' +
|
||||
encodeURIComponent(element.data('clientidstr')) + ':%20' +
|
||||
encodeURIComponent(element.data('clientid')) + '%0D%0A' +
|
||||
encodeURIComponent(element.data('deploymentidstr')) + ':%20' +
|
||||
encodeURIComponent(element.data('deploymentid')) + '%0D%0A' +
|
||||
encodeURIComponent(element.data('publickeyseturlstr')) + ':%20' +
|
||||
encodeURIComponent(element.data('publickeyseturl')) + '%0D%0A' +
|
||||
encodeURIComponent(element.data('accesstokenurlstr')) + ':%20' +
|
||||
encodeURIComponent(element.data('accesstokenurl')) + '%0D%0A' +
|
||||
encodeURIComponent(element.data('authrequesturlstr')) + ':%20' +
|
||||
encodeURIComponent(element.data('authrequesturl')) + '%0D%0A';
|
||||
context = {
|
||||
'mailto': mailTo
|
||||
};
|
||||
var footerPromise = templates.render('mod_lti/tool_config_modal_footer', context);
|
||||
modalFactory.create({
|
||||
large: true,
|
||||
title: element.data('modaltitle'),
|
||||
body: bodyPromise,
|
||||
footer: footerPromise,
|
||||
}, trigger);
|
||||
};
|
||||
|
||||
return /** @alias module:mod_lti/tool_card_controller */ {
|
||||
|
||||
/**
|
||||
@@ -640,6 +687,7 @@ define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/t
|
||||
*/
|
||||
init: function(element) {
|
||||
registerEventListeners(element);
|
||||
registerModal(element);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file returns an array of available public keys
|
||||
*
|
||||
* @package mod_lti
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->dirroot . '/mod/lti/locallib.php');
|
||||
|
||||
$scope = optional_param('scope', '', PARAM_TEXT);
|
||||
$responsetype = optional_param('response_type', '', PARAM_TEXT);
|
||||
$clientid = optional_param('client_id', '', PARAM_TEXT);
|
||||
$redirecturi = optional_param('redirect_uri', '', PARAM_TEXT);
|
||||
$loginhint = optional_param('login_hint', '', PARAM_TEXT);
|
||||
$ltimessagehint = optional_param('lti_message_hint', 0, PARAM_INT);
|
||||
$state = optional_param('state', '', PARAM_TEXT);
|
||||
$responsemode = optional_param('response_mode', '', PARAM_TEXT);
|
||||
$nonce = optional_param('nonce', '', PARAM_TEXT);
|
||||
$prompt = optional_param('prompt', '', PARAM_TEXT);
|
||||
|
||||
$ok = !empty($scope) && !empty($responsetype) && !empty($clientid) &&
|
||||
!empty($redirecturi) && !empty($loginhint) && !empty($scope) &&
|
||||
!empty($nonce) && !empty($SESSION->lti_message_hint);
|
||||
|
||||
if (!$ok) {
|
||||
$error = 'invalid_request';
|
||||
}
|
||||
if ($ok && ($scope !== 'openid')) {
|
||||
$ok = false;
|
||||
$error = 'invalid_scope';
|
||||
}
|
||||
if ($ok && ($responsetype !== 'id_token')) {
|
||||
$ok = false;
|
||||
$error = 'unsupported_response_type';
|
||||
}
|
||||
if ($ok) {
|
||||
list($courseid, $typeid, $id, $titleb64, $textb64) = explode(',', $SESSION->lti_message_hint, 5);
|
||||
$ok = ($id !== $ltimessagehint);
|
||||
if (!$ok) {
|
||||
$error = 'invalid_request';
|
||||
} else {
|
||||
$config = lti_get_type_type_config($typeid);
|
||||
$ok = ($clientid === $config->lti_clientid);
|
||||
if (!$ok) {
|
||||
$error = 'unauthorized_client';
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($ok && ($loginhint !== $USER->id)) {
|
||||
$ok = false;
|
||||
$error = 'access_denied';
|
||||
}
|
||||
if ($ok) {
|
||||
$uris = array_map("trim", explode("\n", $config->lti_redirectionuris));
|
||||
$ok = in_array($redirecturi, $uris);
|
||||
if (!$ok) {
|
||||
$error = 'invalid_request';
|
||||
$desc = 'Unregistered redirect_uri ' . $redirecturi;
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
if (isset($responsemode)) {
|
||||
$ok = ($responsemode === 'form_post');
|
||||
if (!$ok) {
|
||||
$error = 'invalid_request';
|
||||
$desc = 'Invalid response_mode';
|
||||
}
|
||||
} else {
|
||||
$responsemode = 'query';
|
||||
}
|
||||
}
|
||||
if ($ok && !empty($prompt) && ($prompt !== 'none')) {
|
||||
$ok = false;
|
||||
$error = 'invalid_request';
|
||||
$desc = 'Invalid prompt';
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
if ($id) {
|
||||
$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
|
||||
$context = context_module::instance($cm->id);
|
||||
require_login($course, true, $cm);
|
||||
require_capability('mod/lti:view', $context);
|
||||
$lti = $DB->get_record('lti', array('id' => $cm->instance), '*', MUST_EXIST);
|
||||
list($endpoint, $params) = lti_get_launch_data($lti, $nonce);
|
||||
} else {
|
||||
require_login($course);
|
||||
$context = context_course::instance($courseid);
|
||||
require_capability('moodle/course:manageactivities', $context);
|
||||
require_capability('mod/lti:addcoursetool', $context);
|
||||
// Set the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns.
|
||||
$returnurlparams = [
|
||||
'course' => $courseid,
|
||||
'id' => $typeid,
|
||||
'sesskey' => sesskey()
|
||||
];
|
||||
$returnurl = new \moodle_url('/mod/lti/contentitem_return.php', $returnurlparams);
|
||||
// Prepare the request.
|
||||
$title = base64_decode($titleb64);
|
||||
$text = base64_decode($textb64);
|
||||
$request = lti_build_content_item_selection_request($typeid, $course, $returnurl, $title, $text,
|
||||
[], [], false, false, false, false, false, $nonce);
|
||||
$endpoint = $request->url;
|
||||
$params = $request->params;
|
||||
}
|
||||
} else {
|
||||
$params['error'] = $error;
|
||||
if (!empty($desc)) {
|
||||
$params['error_description'] = $desc;
|
||||
}
|
||||
}
|
||||
if (isset($state)) {
|
||||
$params['state'] = $state;
|
||||
}
|
||||
unset($SESSION->lti_message_hint);
|
||||
if ($responsemode !== 'query') {
|
||||
$r = '<form action="' . $redirecturi . "\" name=\"ltiAuthForm\" id=\"ltiAuthForm\" " .
|
||||
"method=\"post\" enctype=\"application/x-www-form-urlencoded\">\n";
|
||||
if (!empty($params)) {
|
||||
foreach ($params as $key => $value) {
|
||||
$key = htmlspecialchars($key);
|
||||
$value = htmlspecialchars($value);
|
||||
$r .= " <input type=\"hidden\" name=\"{$key}\" value=\"{$value}\"/>\n";
|
||||
}
|
||||
}
|
||||
$r .= "</form>\n";
|
||||
$r .= "<script type=\"text/javascript\">\n" .
|
||||
"//<![CDATA[\n" .
|
||||
"document.ltiAuthForm.submit();\n" .
|
||||
"//]]>\n" .
|
||||
"</script>\n";
|
||||
echo $r;
|
||||
} else {
|
||||
$url = new \moodle_url(redirecturi, $params);
|
||||
redirect($url->out(false));
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file returns an array of available public keys
|
||||
*
|
||||
* @package mod_lti
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('NO_DEBUG_DISPLAY', true);
|
||||
define('NO_MOODLE_COOKIES', true);
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
|
||||
$jwks = array('keys' => array());
|
||||
|
||||
$privatekey = get_config('mod_lti', 'privatekey');
|
||||
$res = openssl_pkey_get_private($privatekey);
|
||||
$details = openssl_pkey_get_details($res);
|
||||
|
||||
$jwk = array();
|
||||
$jwk['kty'] = 'RSA';
|
||||
$jwk['alg'] = 'RS256';
|
||||
$jwk['kid'] = get_config('mod_lti', 'kid');
|
||||
$jwk['e'] = strtr(base64_encode($details['rsa']['e']), '+/', '-_');
|
||||
$jwk['n'] = strtr(base64_encode($details['rsa']['n']), '+/', '-_');
|
||||
$jwk['use'] = 'sig';
|
||||
|
||||
$jwks['keys'][] = $jwk;
|
||||
|
||||
@header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
echo json_encode($jwks, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
@@ -53,11 +53,17 @@ class mod_lti_external extends external_api {
|
||||
'id' => new external_value(PARAM_INT, 'Tool type id'),
|
||||
'name' => new external_value(PARAM_NOTAGS, 'Tool type name'),
|
||||
'description' => new external_value(PARAM_NOTAGS, 'Tool type description'),
|
||||
'platformid' => new external_value(PARAM_TEXT, 'Platform ID'),
|
||||
'clientid' => new external_value(PARAM_TEXT, 'Client ID'),
|
||||
'deploymentid' => new external_value(PARAM_INT, 'Deployment ID'),
|
||||
'urls' => new external_single_structure(
|
||||
array(
|
||||
'icon' => new external_value(PARAM_URL, 'Tool type icon URL'),
|
||||
'edit' => new external_value(PARAM_URL, 'Tool type edit URL'),
|
||||
'course' => new external_value(PARAM_URL, 'Tool type edit URL', VALUE_OPTIONAL),
|
||||
'publickeyset' => new external_value(PARAM_URL, 'Public Keyset URL'),
|
||||
'accesstoken' => new external_value(PARAM_URL, 'Access Token URL'),
|
||||
'authrequest' => new external_value(PARAM_URL, 'Authorisation Request URL'),
|
||||
)
|
||||
),
|
||||
'state' => new external_single_structure(
|
||||
|
||||
@@ -184,13 +184,18 @@ abstract class resource_base {
|
||||
public function get_endpoint() {
|
||||
|
||||
$this->parse_template();
|
||||
$url = $this->get_service()->get_service_path() . $this->get_template();
|
||||
$template = preg_replace('/[\(\)]/', '', $this->get_template());
|
||||
$url = $this->get_service()->get_service_path() . $template;
|
||||
foreach ($this->params as $key => $value) {
|
||||
$url = str_replace('{' . $key . '}', $value, $url);
|
||||
}
|
||||
$toolproxy = $this->get_service()->get_tool_proxy();
|
||||
if (!empty($toolproxy)) {
|
||||
$url = str_replace('{config_type}', 'toolproxy', $url);
|
||||
$url = str_replace('{tool_proxy_id}', $toolproxy->guid, $url);
|
||||
} else {
|
||||
$url = str_replace('{config_type}', 'tool', $url);
|
||||
$url = str_replace('{tool_proxy_id}', $this->get_service()->get_type()->id, $url);
|
||||
}
|
||||
|
||||
return $url;
|
||||
@@ -204,6 +209,52 @@ abstract class resource_base {
|
||||
*/
|
||||
public abstract function execute($response);
|
||||
|
||||
/**
|
||||
* Check to make sure the request is valid.
|
||||
*
|
||||
* @param int $typeid The typeid we want to use
|
||||
* @param string $body Body of HTTP request message
|
||||
* @param string[] $scopes Array of scope(s) required for incoming request
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function check_tool($typeid, $body = null, $scopes = null) {
|
||||
|
||||
$ok = $this->get_service()->check_tool($typeid, $body, $scopes);
|
||||
if ($ok) {
|
||||
if ($this->get_service()->get_tool_proxy()) {
|
||||
$toolproxyjson = $this->get_service()->get_tool_proxy()->toolproxy;
|
||||
}
|
||||
if (!empty($toolproxyjson)) {
|
||||
// Check tool proxy to ensure service being requested is included.
|
||||
$toolproxy = json_decode($toolproxyjson);
|
||||
if (!empty($toolproxy) && isset($toolproxy->security_contract->tool_service)) {
|
||||
$contexts = lti_get_contexts($toolproxy);
|
||||
$tpservices = $toolproxy->security_contract->tool_service;
|
||||
foreach ($tpservices as $service) {
|
||||
$fqid = lti_get_fqid($contexts, $service->service);
|
||||
$id = explode('#', $fqid, 2);
|
||||
if ($this->get_id() === $id[1]) {
|
||||
$ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$ok) {
|
||||
debugging('Requested service not permitted: ' . $this->get_id(), DEBUG_DEVELOPER);
|
||||
}
|
||||
} else {
|
||||
// Check that the scope required for the service request is included in those granted for the
|
||||
// access token being used.
|
||||
$permittedscopes = $this->get_service()->get_permitted_scopes();
|
||||
$ok = is_null($permittedscopes) || empty($scopes) || !empty(array_intersect($permittedscopes, $scopes));
|
||||
}
|
||||
}
|
||||
|
||||
return $ok;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to make sure the request is valid.
|
||||
*
|
||||
@@ -211,9 +262,13 @@ abstract class resource_base {
|
||||
* @param string $body Body of HTTP request message
|
||||
*
|
||||
* @return boolean
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see resource_base::check_tool()
|
||||
*/
|
||||
public function check_tool_proxy($toolproxyguid, $body = null) {
|
||||
|
||||
debugging('check_tool_proxy() is deprecated to allow LTI 1 connections to support services. ' .
|
||||
'Please use resource_base::check_tool() instead.', DEBUG_DEVELOPER);
|
||||
$ok = false;
|
||||
if ($this->get_service()->check_tool_proxy($toolproxyguid, $body)) {
|
||||
$toolproxyjson = $this->get_service()->get_tool_proxy()->toolproxy;
|
||||
@@ -234,7 +289,7 @@ abstract class resource_base {
|
||||
}
|
||||
}
|
||||
if (!$ok) {
|
||||
debugging('Requested service not included in tool proxy: ' . $this->get_id(), DEBUG_DEVELOPER);
|
||||
debugging('Requested service not included in tool proxy: ' . $this->get_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,8 +307,12 @@ abstract class resource_base {
|
||||
* @param string $body Body of HTTP request message
|
||||
*
|
||||
* @return boolean
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see resource_base::check_tool()
|
||||
*/
|
||||
public function check_type($typeid, $contextid, $permissionrequested, $body = null) {
|
||||
debugging('check_type() is deprecated to allow LTI 1 connections to support services. ' .
|
||||
'Please use resource_base::check_tool() instead.', DEBUG_DEVELOPER);
|
||||
$ok = false;
|
||||
if ($this->get_service()->check_type($typeid, $contextid, $body)) {
|
||||
$neededpermissions = $this->get_permissions($typeid);
|
||||
@@ -269,7 +328,6 @@ abstract class resource_base {
|
||||
}
|
||||
}
|
||||
return $ok;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,8 +335,12 @@ abstract class resource_base {
|
||||
*
|
||||
* @param int $ltitype Type of LTI
|
||||
* @return array with the permissions related to this resource by the $ltitype or empty if none.
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see resource_base::check_tool()
|
||||
*/
|
||||
public function get_permissions($ltitype) {
|
||||
debugging('get_permissions() is deprecated to allow LTI 1 connections to support services. ' .
|
||||
'Please use resource_base::check_tool() instead.', DEBUG_DEVELOPER);
|
||||
return array();
|
||||
}
|
||||
|
||||
@@ -304,9 +366,10 @@ abstract class resource_base {
|
||||
|
||||
if (empty($this->params)) {
|
||||
$this->params = array();
|
||||
if (isset($_SERVER['PATH_INFO']) && !empty($_SERVER['PATH_INFO'])) {
|
||||
if (!empty($_SERVER['PATH_INFO'])) {
|
||||
$path = explode('/', $_SERVER['PATH_INFO']);
|
||||
$parts = explode('/', $this->get_template());
|
||||
$template = preg_replace('/\([0-9a-zA-Z_\-,\/]+\)/', '', $this->get_template());
|
||||
$parts = explode('/', $template);
|
||||
for ($i = 0; $i < count($parts); $i++) {
|
||||
if ((substr($parts[$i], 0, 1) == '{') && (substr($parts[$i], -1) == '}')) {
|
||||
$value = '';
|
||||
|
||||
@@ -114,12 +114,16 @@ class response {
|
||||
* @return string
|
||||
*/
|
||||
public function get_reason() {
|
||||
if (empty($this->reason)) {
|
||||
$this->reason = $this->responsecodes[$this->code];
|
||||
$code = $this->code;
|
||||
if (($code < 200) || ($code >= 600)) {
|
||||
$code = 500; // Status code must be between 200 and 599.
|
||||
}
|
||||
if (empty($this->reason) && array_key_exists($code, $this->responsecodes)) {
|
||||
$this->reason = $this->responsecodes[$code];
|
||||
}
|
||||
// Use generic reason for this category (based on first digit) if a specific reason is not defined.
|
||||
if (empty($this->reason)) {
|
||||
$this->reason = $this->responsecodes[intval($this->code / 100) * 100];
|
||||
$this->reason = $this->responsecodes[intval($code / 100) * 100];
|
||||
}
|
||||
return $this->reason;
|
||||
}
|
||||
@@ -222,13 +226,28 @@ class response {
|
||||
foreach ($this->additionalheaders as $header) {
|
||||
header($header);
|
||||
}
|
||||
if (($this->code >= 200) && ($this->code < 300)) {
|
||||
if ((($this->code >= 200) && ($this->code < 300)) || !empty($this->body)) {
|
||||
if (!empty($this->contenttype)) {
|
||||
header("Content-Type: {$this->contenttype};charset=UTF-8");
|
||||
header("Content-Type: {$this->contenttype}; charset=utf-8");
|
||||
}
|
||||
if (!empty($this->body)) {
|
||||
echo $this->body;
|
||||
}
|
||||
} else if ($this->code >= 400) {
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
$body = new \stdClass();
|
||||
$body->status = $this->code;
|
||||
$body->reason = $this->get_reason();
|
||||
$body->request = new \stdClass();
|
||||
$body->request->method = $_SERVER['REQUEST_METHOD'];
|
||||
$body->request->url = $_SERVER['REQUEST_URI'];
|
||||
if (isset($_SERVER['HTTP_ACCEPT'])) {
|
||||
$body->request->accept = $_SERVER['HTTP_ACCEPT'];
|
||||
}
|
||||
if (isset($_SERVER['CONTENT_TYPE'])) {
|
||||
$body->request->contentType = explode(';', $_SERVER['CONTENT_TYPE'], 2)[0];
|
||||
}
|
||||
echo json_encode($body, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ abstract class service_base {
|
||||
|
||||
/** Label representing an LTI 2 message type */
|
||||
const LTI_VERSION2P0 = 'LTI-2p0';
|
||||
/** Service enabled */
|
||||
const SERVICE_ENABLED = 1;
|
||||
|
||||
/** @var string ID for the service. */
|
||||
protected $id;
|
||||
@@ -58,6 +60,10 @@ abstract class service_base {
|
||||
protected $unsigned;
|
||||
/** @var stdClass Tool proxy object for the current service request. */
|
||||
private $toolproxy;
|
||||
/** @var stdClass LTI type object for the current service request. */
|
||||
private $type;
|
||||
/** @var stdClass LTI type config object for the current service request. */
|
||||
private $typeconfig;
|
||||
/** @var array Instances of the resources associated with this service. */
|
||||
protected $resources;
|
||||
|
||||
@@ -71,6 +77,8 @@ abstract class service_base {
|
||||
$this->name = null;
|
||||
$this->unsigned = false;
|
||||
$this->toolproxy = null;
|
||||
$this->type = null;
|
||||
$this->typeconfig = null;
|
||||
$this->resources = null;
|
||||
|
||||
}
|
||||
@@ -86,6 +94,17 @@ abstract class service_base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service compoent ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_component_id() {
|
||||
|
||||
return 'ltiservice_' . $this->id;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service name.
|
||||
*
|
||||
@@ -132,6 +151,54 @@ abstract class service_base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type object.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get_type() {
|
||||
|
||||
return $this->type;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the LTI type object.
|
||||
*
|
||||
* @param object $type The LTI type for this service request
|
||||
*
|
||||
* @var stdClass
|
||||
*/
|
||||
public function set_type($type) {
|
||||
|
||||
$this->type = $type;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type config object.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get_typeconfig() {
|
||||
|
||||
return $this->typeconfig;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the LTI type config object.
|
||||
*
|
||||
* @param object $typeconfig The LTI type config for this service request
|
||||
*
|
||||
* @var stdClass
|
||||
*/
|
||||
public function set_typeconfig($typeconfig) {
|
||||
|
||||
$this->typeconfig = $typeconfig;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resources for this service.
|
||||
*
|
||||
@@ -139,13 +206,27 @@ abstract class service_base {
|
||||
*/
|
||||
abstract public function get_resources();
|
||||
|
||||
/**
|
||||
* Get the scope(s) permitted for this service.
|
||||
*
|
||||
* A null value indicates that no scopes are required to access the service.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function get_permitted_scopes() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration options for this service.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Moodle quickform object definition
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see service_base::get_configuration_elements()
|
||||
*/
|
||||
public function get_configuration_options(&$mform) {
|
||||
|
||||
debugging('get_configuration_options() has been deprecated, ' .
|
||||
'please use service_base::get_configuration_elements() instead.', DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,6 +235,17 @@ abstract class service_base {
|
||||
* @return array Names list of the parameters that the service will be saving in the configuration
|
||||
*/
|
||||
public function get_configuration_parameter_names() {
|
||||
debugging('get_configuration_options() has been deprecated, ' .
|
||||
'please use service_base::get_configuration_elements() instead.', DEBUG_DEVELOPER);
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create form element for gradebook sync add/edit page.
|
||||
*
|
||||
* @return array of \HTML_QuickForm_element Form elements
|
||||
*/
|
||||
public function get_configuration_elements() {
|
||||
return array();
|
||||
}
|
||||
|
||||
@@ -244,6 +336,63 @@ abstract class service_base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the request has been properly signed and is permitted.
|
||||
*
|
||||
* @param string $typeid LTI type ID
|
||||
* @param string $body Request body (null if none)
|
||||
* @param string[] $scopes Array of required scope(s) for incoming request
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function check_tool($typeid, $body = null, $scopes = null) {
|
||||
|
||||
$ok = true;
|
||||
$toolproxy = null;
|
||||
$consumerkey = lti\get_oauth_key_from_headers($typeid, $scopes);
|
||||
if ($consumerkey === false) {
|
||||
$ok = $this->is_unsigned();
|
||||
} else {
|
||||
if (empty($typeid) && is_int($consumerkey)) {
|
||||
$typeid = $consumerkey;
|
||||
}
|
||||
if (!empty($typeid)) {
|
||||
$this->type = lti_get_type($typeid);
|
||||
$this->typeconfig = lti_get_type_config($typeid);
|
||||
$ok = !empty($this->type->id);
|
||||
if ($ok && !empty($this->type->toolproxyid)) {
|
||||
$this->toolproxy = lti_get_tool_proxy($this->type->toolproxyid);
|
||||
}
|
||||
} else {
|
||||
$toolproxy = lti_get_tool_proxy_from_guid($consumerkey);
|
||||
if ($toolproxy !== false) {
|
||||
$this->toolproxy = $toolproxy;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($ok && is_string($consumerkey)) {
|
||||
if (!empty($this->toolproxy)) {
|
||||
$key = $this->toolproxy->guid;
|
||||
$secret = $this->toolproxy->secret;
|
||||
} else {
|
||||
if ($this->type->ltiversion === LTI_VERSION_1P3) {
|
||||
$key = $this->type->clientid; // Should not be needed as access tokens are used.
|
||||
} else {
|
||||
$key = $this->typeconfig['resourcekey'];
|
||||
}
|
||||
$secret = $this->typeconfig['password'];
|
||||
}
|
||||
if (!$this->is_unsigned() && ($key == $consumerkey)) {
|
||||
$ok = $this->check_signature($key, $secret, $body);
|
||||
} else {
|
||||
$ok = $this->is_unsigned();
|
||||
}
|
||||
}
|
||||
|
||||
return $ok;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the request has been properly signed.
|
||||
*
|
||||
@@ -251,9 +400,13 @@ abstract class service_base {
|
||||
* @param string $body Request body (null if none)
|
||||
*
|
||||
* @return boolean
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see service_base::check_tool()
|
||||
*/
|
||||
public function check_tool_proxy($toolproxyguid, $body = null) {
|
||||
|
||||
debugging('check_tool_proxy() is deprecated to allow LTI 1 connections to support services. ' .
|
||||
'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
|
||||
$ok = false;
|
||||
$toolproxy = null;
|
||||
$consumerkey = lti\get_oauth_key_from_headers();
|
||||
@@ -274,7 +427,9 @@ abstract class service_base {
|
||||
if ($ok) {
|
||||
$this->toolproxy = $toolproxy;
|
||||
}
|
||||
|
||||
return $ok;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,8 +440,12 @@ abstract class service_base {
|
||||
* @param string $body Request body (null if none)
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see service_base::check_tool()
|
||||
*/
|
||||
public function check_type($typeid, $courseid, $body = null) {
|
||||
debugging('check_type() is deprecated to allow LTI 1 connections to support services. ' .
|
||||
'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
|
||||
$ok = false;
|
||||
$tool = null;
|
||||
$consumerkey = lti\get_oauth_key_from_headers();
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
*/
|
||||
namespace mod_lti\output;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
require_once($CFG->dirroot.'/mod/lti/locallib.php');
|
||||
|
||||
use moodle_url;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* A scheduled task for lti module.
|
||||
*
|
||||
* @package mod_lti
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace mod_lti\task;
|
||||
|
||||
use core\task\scheduled_task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class containing the scheduled task for lti module.
|
||||
*
|
||||
* @package mod_lti
|
||||
* @copyright 2018 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class clean_access_tokens extends scheduled_task {
|
||||
|
||||
/**
|
||||
* Get a descriptive name for this task (shown to admins).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('cleanaccesstokens', 'mod_lti');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run lti cron.
|
||||
*/
|
||||
public function execute() {
|
||||
global $DB;
|
||||
|
||||
$DB->delete_records_select('lti_access_tokens', 'validuntil < ?', [time()]);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,16 @@ $courseid = required_param('course', PARAM_INT);
|
||||
$title = optional_param('title', '', PARAM_TEXT);
|
||||
$text = optional_param('text', '', PARAM_RAW);
|
||||
|
||||
$config = lti_get_type_type_config($id);
|
||||
if ($config->lti_ltiversion === LTI_VERSION_1P3) {
|
||||
if (!isset($SESSION->lti_initiatelogin_status)) {
|
||||
echo lti_initiatelogin($courseid, 0, null, $config, 'ContentItemSelectionRequest', $title, $text);
|
||||
exit;
|
||||
} else {
|
||||
unset($SESSION->lti_initiatelogin_status);
|
||||
}
|
||||
}
|
||||
|
||||
// Check access and capabilities.
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
require_login($course);
|
||||
|
||||
@@ -28,12 +28,26 @@ require_once($CFG->dirroot . '/mod/lti/locallib.php');
|
||||
|
||||
$id = required_param('id', PARAM_INT);
|
||||
$courseid = required_param('course', PARAM_INT);
|
||||
$messagetype = required_param('lti_message_type', PARAM_TEXT);
|
||||
$version = required_param('lti_version', PARAM_TEXT);
|
||||
$consumerkey = required_param('oauth_consumer_key', PARAM_RAW);
|
||||
$items = optional_param('content_items', '', PARAM_RAW);
|
||||
$errormsg = optional_param('lti_errormsg', '', PARAM_TEXT);
|
||||
$msg = optional_param('lti_msg', '', PARAM_TEXT);
|
||||
|
||||
$jwt = optional_param('JWT', '', PARAM_RAW);
|
||||
|
||||
if (!empty($jwt)) {
|
||||
list($tool, $params) = lti_convert_from_jwt($id, $jwt);
|
||||
$consumerkey = isset($params['oauth_consumer_key']) ? $params['oauth_consumer_key'] : '';
|
||||
$messagetype = isset($params['lti_message_type']) ? $params['lti_message_type'] : '';
|
||||
$version = isset($params['lti_version']) ? $params['lti_version'] : '';
|
||||
$items = isset($params['content_items']) ? $params['content_items'] : '';
|
||||
$errormsg = isset($params['lti_errormsg']) ? $params['lti_errormsg'] : '';
|
||||
$msg = isset($params['lti_msg']) ? $params['lti_msg'] : '';
|
||||
} else {
|
||||
$consumerkey = required_param('oauth_consumer_key', PARAM_RAW);
|
||||
$messagetype = required_param('lti_message_type', PARAM_TEXT);
|
||||
$version = required_param('lti_version', PARAM_TEXT);
|
||||
$items = optional_param('content_items', '', PARAM_RAW);
|
||||
$errormsg = optional_param('lti_errormsg', '', PARAM_TEXT);
|
||||
$msg = optional_param('lti_msg', '', PARAM_TEXT);
|
||||
$tool = lti_verify_oauth_signature($id, $consumerkey);
|
||||
}
|
||||
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
require_login($course);
|
||||
@@ -46,7 +60,7 @@ $redirecturl = null;
|
||||
$returndata = null;
|
||||
if (empty($errormsg) && !empty($items)) {
|
||||
try {
|
||||
$returndata = lti_tool_configuration_from_content_item($id, $messagetype, $version, $consumerkey, $items);
|
||||
$returndata = lti_tool_configuration_from_content_item($tool, $messagetype, $version, $consumerkey, $items);
|
||||
} catch (moodle_exception $e) {
|
||||
$errormsg = $e->getMessage();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Post installation and migration code.
|
||||
*
|
||||
* @package mod_lti
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Stub for database installation.
|
||||
*/
|
||||
function xmldb_lti_install() {
|
||||
global $CFG;
|
||||
|
||||
// Add platform ID configuration setting.
|
||||
set_config('platformid', $CFG->wwwroot, 'mod_lti');
|
||||
|
||||
// Create the private key.
|
||||
$kid = bin2hex(openssl_random_pseudo_bytes(10));
|
||||
set_config('kid', $kid, 'mod_lti');
|
||||
$config = array(
|
||||
"digest_alg" => "sha256",
|
||||
"private_key_bits" => 2048,
|
||||
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||
);
|
||||
$res = openssl_pkey_new($config);
|
||||
openssl_pkey_export($res, $privatekey);
|
||||
set_config('privatekey', $privatekey, 'mod_lti');
|
||||
}
|
||||
+24
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="mod/lti/db" VERSION="20151204" COMMENT="XMLDB file for Moodle mod/lti"
|
||||
<XMLDB PATH="mod/lti/db" VERSION="20190303" COMMENT="XMLDB file for Moodle mod/lti"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -72,6 +72,8 @@
|
||||
<FIELD NAME="state" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="2" SEQUENCE="false" COMMENT="Active = 1, Pending = 2, Rejected = 3"/>
|
||||
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="coursevisible" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="ltiversion" TYPE="char" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="clientid" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="toolproxyid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Primary key of related tool proxy (null for LTI 1 tools)"/>
|
||||
<FIELD NAME="enabledcapability" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Enabled capabilities, one per line (null for LTI 1 tools)"/>
|
||||
<FIELD NAME="parameter" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Launch parameters, one per line (null for LTI 1 tools)"/>
|
||||
@@ -88,6 +90,7 @@
|
||||
<INDEXES>
|
||||
<INDEX NAME="course" UNIQUE="false" FIELDS="course"/>
|
||||
<INDEX NAME="tooldomain" UNIQUE="false" FIELDS="tooldomain"/>
|
||||
<INDEX NAME="clientid" UNIQUE="true" FIELDS="clientid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="lti_types_config" COMMENT="Basic LTI types configuration">
|
||||
@@ -108,6 +111,7 @@
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="toolproxyid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Primary key of related tool proxy"/>
|
||||
<FIELD NAME="typeid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Primary key of course (null for system-wide settings)"/>
|
||||
<FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Primary key of course module - tool link added to course (null for system-wide and context-wide settings)"/>
|
||||
<FIELD NAME="settings" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Setting values as JSON"/>
|
||||
@@ -117,6 +121,7 @@
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="toolproxy" TYPE="foreign" FIELDS="toolproxyid" REFTABLE="lti_tool_proxies" REFFIELDS="id" COMMENT="The tool proxy to which the setting relates"/>
|
||||
<KEY NAME="typeid" TYPE="foreign" FIELDS="typeid" REFTABLE="lti_types" REFFIELDS="id"/>
|
||||
<KEY NAME="course" TYPE="foreign" FIELDS="course" REFTABLE="course" REFFIELDS="id" COMMENT="The course to which the setting relates"/>
|
||||
<KEY NAME="coursemodule" TYPE="foreign" FIELDS="coursemoduleid" REFTABLE="lti" REFFIELDS="id" COMMENT="The module instance to which the setting relates"/>
|
||||
</KEYS>
|
||||
@@ -140,5 +145,23 @@
|
||||
<INDEX NAME="ltiid" UNIQUE="false" FIELDS="ltiid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="lti_access_tokens" COMMENT="Security tokens for accessing of LTI services">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="typeid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Basic LTI type id"/>
|
||||
<FIELD NAME="scope" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Scope values as JSON array"/>
|
||||
<FIELD NAME="token" TYPE="char" LENGTH="128" NOTNULL="true" SEQUENCE="false" COMMENT="security token, aka private access key"/>
|
||||
<FIELD NAME="validuntil" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="timestamp - valid until data"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="created timestamp"/>
|
||||
<FIELD NAME="lastaccess" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="last access timestamp"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="typeid" TYPE="foreign" FIELDS="typeid" REFTABLE="lti_types" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="token" UNIQUE="true" FIELDS="token"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
@@ -24,6 +24,8 @@
|
||||
* @since Moodle 3.0
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$functions = array(
|
||||
|
||||
'mod_lti_get_tool_launch_data' => array(
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file defines tasks performed by the plugin.
|
||||
*
|
||||
* @package mod_lti
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
// List of tasks.
|
||||
$tasks = array(
|
||||
array(
|
||||
'classname' => 'mod_lti\task\clean_access_tokens',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => 'R',
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
)
|
||||
);
|
||||
+109
-1
@@ -61,6 +61,8 @@
|
||||
function xmldb_lti_upgrade($oldversion) {
|
||||
global $CFG, $DB;
|
||||
|
||||
$dbman = $DB->get_manager();
|
||||
|
||||
// Automatically generated Moodle v3.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
@@ -125,6 +127,112 @@ function xmldb_lti_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.6.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
if ($oldversion < 2019031300) {
|
||||
// Define table lti_access_tokens to be updated.
|
||||
$table = new xmldb_table('lti_types');
|
||||
|
||||
// Define field ltiversion to be added to lti_types.
|
||||
$field = new xmldb_field('ltiversion', XMLDB_TYPE_CHAR, 10, null, XMLDB_NOTNULL, null, null, 'coursevisible');
|
||||
|
||||
// Conditionally launch add field ltiversion.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
$DB->set_field_select('lti_types', 'ltiversion', 'LTI-1p0', 'toolproxyid IS NULL');
|
||||
$DB->set_field_select('lti_types', 'ltiversion', 'LTI-2p0', 'toolproxyid IS NOT NULL');
|
||||
}
|
||||
|
||||
// Define field clientid to be added to lti_types.
|
||||
$field = new xmldb_field('clientid', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'ltiversion');
|
||||
|
||||
// Conditionally launch add field clientid.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define index clientid (unique) to be added to lti_types.
|
||||
$index = new xmldb_index('clientid', XMLDB_INDEX_UNIQUE, array('clientid'));
|
||||
|
||||
// Conditionally launch add index clientid.
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Add platform ID configuration setting.
|
||||
set_config('platformid', $CFG->wwwroot, 'mod_lti');
|
||||
|
||||
// Create the private key.
|
||||
$kid = bin2hex(openssl_random_pseudo_bytes(10));
|
||||
set_config('kid', $kid, 'mod_lti');
|
||||
$config = array(
|
||||
"digest_alg" => "sha256",
|
||||
"private_key_bits" => 2048,
|
||||
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||
);
|
||||
$res = openssl_pkey_new($config);
|
||||
openssl_pkey_export($res, $privatekey);
|
||||
set_config('privatekey', $privatekey, 'mod_lti');
|
||||
|
||||
// Lti savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2019031300, 'lti');
|
||||
}
|
||||
|
||||
if ($oldversion < 2019031301) {
|
||||
// Define table lti_access_tokens to be created.
|
||||
$table = new xmldb_table('lti_access_tokens');
|
||||
|
||||
// Adding fields to table lti_access_tokens.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('typeid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('scope', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('token', XMLDB_TYPE_CHAR, '128', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('validuntil', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('lastaccess', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
|
||||
// Adding keys to table lti_access_tokens.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('typeid', XMLDB_KEY_FOREIGN, array('typeid'), 'lti_types', array('id'));
|
||||
|
||||
// Add an index.
|
||||
$table->add_index('token', XMLDB_INDEX_UNIQUE, array('token'));
|
||||
|
||||
// Conditionally launch create table for lti_access_tokens.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Lti savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2019031301, 'lti');
|
||||
}
|
||||
|
||||
if ($oldversion < 2019031302) {
|
||||
$DB->set_field('lti_types_config', 'name', 'ltiservice_memberships', array('name' => 'memberships'));
|
||||
$DB->set_field('lti_types_config', 'name', 'ltiservice_gradebookservices', array('name' => 'gradesynchronization'));
|
||||
|
||||
// Lti savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2019031302, 'lti');
|
||||
}
|
||||
|
||||
if ($oldversion < 2019031303) {
|
||||
// Define field typeid to be added to lti_tool_settings.
|
||||
$table = new xmldb_table('lti_tool_settings');
|
||||
$field = new xmldb_field('typeid', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'toolproxyid');
|
||||
|
||||
// Conditionally launch add field typeid.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define key typeid (foreign) to be added to lti_tool_settings.
|
||||
$table = new xmldb_table('lti_tool_settings');
|
||||
$key = new xmldb_key('typeid', XMLDB_KEY_FOREIGN, ['typeid'], 'lti_types', ['id']);
|
||||
|
||||
// Launch add key typeid.
|
||||
$dbman->add_key($table, $key);
|
||||
|
||||
// Lti savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2019031303, 'lti');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+83
-4
@@ -68,11 +68,13 @@ class mod_lti_edit_types_form extends moodleform {
|
||||
* Define this form.
|
||||
*/
|
||||
public function definition() {
|
||||
global $CFG;
|
||||
global $CFG, $PAGE;
|
||||
|
||||
$mform =& $this->_form;
|
||||
|
||||
$istool = $this->_customdata && $this->_customdata->istool;
|
||||
$istool = $this->_customdata && isset($this->_customdata->istool) && $this->_customdata->istool;
|
||||
$typeid = $this->_customdata->id ?? '';
|
||||
$clientid = $this->_customdata->clientid ?? '';
|
||||
|
||||
// Add basiclti elements.
|
||||
$mform->addElement('header', 'setup', get_string('tool_settings', 'lti'));
|
||||
@@ -96,14 +98,54 @@ class mod_lti_edit_types_form extends moodleform {
|
||||
}
|
||||
|
||||
if (!$istool) {
|
||||
$options = array(
|
||||
LTI_VERSION_1 => get_string('oauthsecurity', 'lti'),
|
||||
LTI_VERSION_1P3 => get_string('jwtsecurity', 'lti'),
|
||||
);
|
||||
$mform->addElement('select', 'lti_ltiversion', get_string('ltiversion', 'lti'), $options);
|
||||
$mform->setType('lti_ltiversion', PARAM_TEXT);
|
||||
$mform->addHelpButton('lti_ltiversion', 'ltiversion', 'lti');
|
||||
$mform->setDefault('lti_ltiversion', LTI_VERSION_1);
|
||||
|
||||
$mform->addElement('text', 'lti_resourcekey', get_string('resourcekey_admin', 'lti'));
|
||||
$mform->setType('lti_resourcekey', PARAM_TEXT);
|
||||
$mform->addHelpButton('lti_resourcekey', 'resourcekey_admin', 'lti');
|
||||
$mform->hideIf('lti_resourcekey', 'lti_ltiversion', 'eq', LTI_VERSION_1P3);
|
||||
$mform->setForceLtr('lti_resourcekey');
|
||||
|
||||
$mform->addElement('passwordunmask', 'lti_password', get_string('password_admin', 'lti'));
|
||||
$mform->setType('lti_password', PARAM_TEXT);
|
||||
$mform->addHelpButton('lti_password', 'password_admin', 'lti');
|
||||
$mform->hideIf('lti_password', 'lti_ltiversion', 'eq', LTI_VERSION_1P3);
|
||||
|
||||
if (!empty($typeid)) {
|
||||
$mform->addElement('text', 'lti_clientid_disabled', get_string('clientidadmin', 'lti'));
|
||||
$mform->setType('lti_clientid_disabled', PARAM_TEXT);
|
||||
$mform->addHelpButton('lti_clientid_disabled', 'clientidadmin', 'lti');
|
||||
$mform->hideIf('lti_clientid_disabled', 'lti_ltiversion', 'neq', LTI_VERSION_1P3);
|
||||
$mform->disabledIf('lti_clientid_disabled', null);
|
||||
$mform->setForceLtr('lti_clientid_disabled');
|
||||
$mform->addElement('hidden', 'lti_clientid');
|
||||
$mform->setType('lti_clientid', PARAM_TEXT);
|
||||
}
|
||||
|
||||
$mform->addElement('textarea', 'lti_publickey', get_string('publickey', 'lti'), array('rows' => 8, 'cols' => 60));
|
||||
$mform->setType('lti_publickey', PARAM_TEXT);
|
||||
$mform->addHelpButton('lti_publickey', 'publickey', 'lti');
|
||||
$mform->hideIf('lti_publickey', 'lti_ltiversion', 'neq', LTI_VERSION_1P3);
|
||||
$mform->setForceLtr('lti_publickey');
|
||||
|
||||
$mform->addElement('text', 'lti_initiatelogin', get_string('initiatelogin', 'lti'), array('size' => '64'));
|
||||
$mform->setType('lti_initiatelogin', PARAM_URL);
|
||||
$mform->addHelpButton('lti_initiatelogin', 'initiatelogin', 'lti');
|
||||
$mform->hideIf('lti_initiatelogin', 'lti_ltiversion', 'neq', LTI_VERSION_1P3);
|
||||
|
||||
$mform->addElement('textarea', 'lti_redirectionuris', get_string('redirectionuris', 'lti'),
|
||||
array('rows' => 3, 'cols' => 60));
|
||||
$mform->setType('lti_redirectionuris', PARAM_TEXT);
|
||||
$mform->addHelpButton('lti_redirectionuris', 'redirectionuris', 'lti');
|
||||
$mform->hideIf('lti_redirectionuris', 'lti_ltiversion', 'neq', LTI_VERSION_1P3);
|
||||
$mform->setForceLtr('lti_redirectionuris');
|
||||
}
|
||||
|
||||
if ($istool) {
|
||||
@@ -185,8 +227,7 @@ class mod_lti_edit_types_form extends moodleform {
|
||||
$mform->addHelpButton('lti_secureicon', 'secure_icon_url', 'lti');
|
||||
|
||||
if (!$istool) {
|
||||
// Display the lti advantage services.
|
||||
$this->get_lti_advantage_services($mform);
|
||||
$this->get_lti_services($mform);
|
||||
}
|
||||
|
||||
if (!$istool) {
|
||||
@@ -287,8 +328,12 @@ class mod_lti_edit_types_form extends moodleform {
|
||||
* Generates the lti advantage extra configuration adding it to the mform
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see mod_lti_edit_types_form::get_lti_services()
|
||||
*/
|
||||
public function get_lti_advantage_services(&$mform) {
|
||||
debugging('get_lti_advantage_services() has been deprecated, ' .
|
||||
'please use mod_lti_edit_types_form::get_lti_services() instead.', DEBUG_DEVELOPER);
|
||||
// For each service add the label and get the array of configuration.
|
||||
$services = lti_get_services();
|
||||
$mform->addElement('header', 'services', get_string('services', 'lti'));
|
||||
@@ -297,4 +342,38 @@ class mod_lti_edit_types_form extends moodleform {
|
||||
$service->get_configuration_options($mform);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the lti services extra configuration adding it to the mform
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
public function get_lti_services(&$mform) {
|
||||
$services = lti_get_services();
|
||||
$mform->addElement('header', 'services', get_string('services', 'lti'));
|
||||
foreach ($services as $service) {
|
||||
$elements = $service->get_configuration_elements();
|
||||
if (!empty($elements)) {
|
||||
foreach ($elements as $name => $element) {
|
||||
$id = $service->get_component_id();
|
||||
if (!empty($name)) {
|
||||
$id = "{$id}_{$name}";
|
||||
}
|
||||
$element->setName($id);
|
||||
$mform->addelement($element);
|
||||
$type = PARAM_TEXT;
|
||||
if ($element instanceof \MoodleQuickForm_select) {
|
||||
if (is_int($element->_options[0]['attr']['value'])) {
|
||||
$type = PARAM_INT;
|
||||
}
|
||||
}
|
||||
$mform->setType($id, $type);
|
||||
$mform->setDefault($id, 0);
|
||||
if (empty($name)) {
|
||||
$mform->addHelpButton($id, $id, $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,12 +64,18 @@ if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) {
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
$form = new mod_lti_edit_types_form();
|
||||
if ($action == 'edit') {
|
||||
$type = lti_get_type_type_config($typeid);
|
||||
} else {
|
||||
$type = new stdClass();
|
||||
$type->lti_clientid = null;
|
||||
}
|
||||
|
||||
$form = new mod_lti_edit_types_form($url, (object)array('id' => $typeid, 'clientid' => $type->lti_clientid));
|
||||
|
||||
// If the user just opened an add or edit form.
|
||||
if ($action == 'add' || $action == 'edit') {
|
||||
if ($action == 'edit') {
|
||||
$type = lti_get_type_type_config($typeid);
|
||||
$form->set_data($type);
|
||||
}
|
||||
echo $OUTPUT->heading(get_string('toolsetup', 'lti'));
|
||||
|
||||
@@ -93,7 +93,12 @@ $string['cannot_edit'] = 'You may not edit this tool configuration.';
|
||||
$string['capabilities'] = 'Capabilities';
|
||||
$string['capabilitiesrequired'] = 'This tool requires access to the following data in order to activate:';
|
||||
$string['capabilities_help'] = 'Select those capabilities which you wish to offer to the tool provider. More than one capability can be selected.';
|
||||
$string['cleanaccesstokens'] = 'External tool removal of expired access tokens';
|
||||
$string['click_to_continue'] = '<a href="{$a->link}" target="_top">Click to continue</a>';
|
||||
$string['clientidadmin'] = 'Client ID';
|
||||
$string['clientidadmin_help'] = 'The client ID can be thought of as a unique value used to identify a tool.
|
||||
It is created automatically for each tool which uses the JWT security profile introduced in LTI 1.3 and should
|
||||
be part of the details passed to the provider of the tool so that they can configure the connection at their end.';
|
||||
$string['comment'] = 'Comment';
|
||||
$string['configpassword'] = 'Default remote tool password';
|
||||
$string['configpreferheight'] = 'Default preferred height';
|
||||
@@ -217,7 +222,10 @@ $string['indicator:cognitivedepth'] = 'LTI cognitive';
|
||||
$string['indicator:cognitivedepth_help'] = 'This indicator is based on the cognitive depth reached by the student in an LTI activity.';
|
||||
$string['indicator:socialbreadth'] = 'LTI social';
|
||||
$string['indicator:socialbreadth_help'] = 'This indicator is based on the social breadth reached by the student in an LTI activity.';
|
||||
$string['initiatelogin'] = 'Initiate Login URL';
|
||||
$string['initiatelogin_help'] = 'The tool URL to which requests for initiating a login are to be sent. This URL is required before a message can be successfully sent to the tool.';
|
||||
$string['invalidid'] = 'LTI ID was incorrect';
|
||||
$string['jwtsecurity'] = 'LTI 1.3';
|
||||
$string['launch_in_moodle'] = 'Launch tool in Moodle';
|
||||
$string['launch_in_popup'] = 'Launch tool in a pop-up';
|
||||
$string['launch_url'] = 'Tool URL';
|
||||
@@ -262,6 +270,8 @@ $string['lti_launch_error_unsigned_help'] = '<p>This error may be a result of a
|
||||
$string['lti_tool_request_added'] = 'Tool configuration request successfully submitted. You may need to contact an administrator to complete the tool configuration.';
|
||||
$string['lti_tool_request_existing'] = 'A tool configuration for the tool domain has already been submitted.';
|
||||
$string['ltiunknownserviceapicall'] = 'LTI unknown service API call.';
|
||||
$string['ltiversion'] = 'LTI version';
|
||||
$string['ltiversion_help'] = 'The version of LTI being used for signing messages and service requests: LTI 1.0/1.1 and LTI 2.0 use the OAuth 1.0A security profile; LTI 1.3.0 uses JWTs.';
|
||||
$string['main_admin'] = 'General help';
|
||||
$string['main_admin_help'] = 'External tools allow Moodle users to seamlessly interact with learning resources hosted remotely. Through a special
|
||||
launch protocol, the remote tool will have access to some general information about the launching user. For example,
|
||||
@@ -316,6 +326,7 @@ $string['noprofileservice'] = 'Profile service not found';
|
||||
$string['noservers'] = 'No servers found';
|
||||
$string['notypes'] = 'There are currently no LTI tools set up in Moodle. Click the Install link above to add some.';
|
||||
$string['noviewusers'] = 'No users were found with permissions to use this tool';
|
||||
$string['oauthsecurity'] = 'LTI 1.0/1.1';
|
||||
$string['optionalsettings'] = 'Optional settings';
|
||||
$string['organization'] = 'Organization details';
|
||||
$string['organizationdescr'] = 'Organization description';
|
||||
@@ -382,9 +393,13 @@ $string['privacy:metadata:timemodified'] = 'The time when the record was modifie
|
||||
$string['privacy:metadata:userid'] = 'The ID of the user accessing the LTI Consumer';
|
||||
$string['privacy:metadata:useridnumber'] = 'The ID number of the user accessing the LTI Consumer';
|
||||
$string['privacy:metadata:username'] = 'The username of the user accessing the LTI Consumer';
|
||||
$string['publickey'] = 'Public key';
|
||||
$string['publickey_help'] = 'The public key (in PEM format) provided by the tool to allow signatures of incoming messages and service requests to be verified.';
|
||||
$string['quickgrade'] = 'Allow quick grading';
|
||||
$string['quickgrade_help'] = 'If enabled, multiple tools can be graded on one page. Add grades and comments then click the "Save all my feedback" button to save all changes for that page.';
|
||||
$string['redirect'] = 'You will be redirected in few seconds. If you are not, press the button.';
|
||||
$string['redirectionuris'] = 'Redirection URI(s)';
|
||||
$string['redirectionuris_help'] = 'A list of URIs (one per line) which the tool uses when making authorisation requests. At least one must be registered before a message can be successfully sent to the tool.';
|
||||
$string['register'] = 'Register';
|
||||
$string['register_warning'] = 'The registration page seems to be taking a while to open. If it does not appear, check that you entered the correct URL in the configuration settings. If Moodle is using https, ensure the tool you are configuring supports https and you are using https in the URL.';
|
||||
$string['registertype'] = 'Configure a new external tool registration';
|
||||
@@ -486,6 +501,16 @@ $string['tooldescription'] = 'Tool description';
|
||||
$string['tooldescription_help'] = 'The description of the tool that will be displayed to teachers in the activity list.
|
||||
|
||||
This should describe what the tool is for and what it does and any additional information the teacher may need to know.';
|
||||
$string['tooldetailsaccesstokenurl'] = 'Access Token URL';
|
||||
$string['tooldetailsauthrequesturl'] = 'Authentication request URL';
|
||||
$string['tooldetailsclientid'] = 'Client ID';
|
||||
$string['tooldetailsdeploymentid'] = 'Deployment ID';
|
||||
$string['tooldetailsmailtosubject'] = 'LTI Tool Configuration';
|
||||
$string['tooldetailsmodalemail'] = 'Email';
|
||||
$string['tooldetailsmodallink'] = 'View Configuration Details';
|
||||
$string['tooldetailsmodaltitle'] = 'Tool Configuration Details';
|
||||
$string['tooldetailsplatformid'] = 'Platform ID';
|
||||
$string['tooldetailspublickeyseturl'] = 'Public Keyset URL';
|
||||
$string['toolisbeingused'] = 'This tool is being used {$a} times';
|
||||
$string['toolisnotbeingused'] = 'This tool has not yet been used';
|
||||
$string['toolproxy'] = 'External tool registrations';
|
||||
|
||||
@@ -55,6 +55,20 @@ $triggerview = optional_param('triggerview', 1, PARAM_BOOL);
|
||||
|
||||
$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
|
||||
$lti = $DB->get_record('lti', array('id' => $cm->instance), '*', MUST_EXIST);
|
||||
|
||||
$typeid = $lti->typeid;
|
||||
if ($typeid) {
|
||||
$config = lti_get_type_type_config($typeid);
|
||||
if ($config->lti_ltiversion === LTI_VERSION_1P3) {
|
||||
if (!isset($SESSION->lti_initiatelogin_status)) {
|
||||
echo lti_initiatelogin($cm->course, $id, $lti, $config);
|
||||
exit;
|
||||
} else {
|
||||
unset($SESSION->lti_initiatelogin_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
|
||||
|
||||
$context = context_module::instance($cm->id);
|
||||
|
||||
+1102
-117
File diff suppressed because it is too large
Load Diff
@@ -112,7 +112,7 @@ class mod_lti_mod_form extends moodleform_mod {
|
||||
$toolproxy[] = $type->id;
|
||||
$attributes = array( 'globalTool' => 1, 'toolproxy' => 1);
|
||||
$enabledcapabilities = explode("\n", $type->enabledcapability);
|
||||
if (!in_array('Result.autocreate', $enabledcapabilities)) {
|
||||
if (!in_array('Result.autocreate', $enabledcapabilities) || in_array('BasicOutcome.url', $enabledcapabilities)) {
|
||||
$attributes['nogrades'] = 1;
|
||||
}
|
||||
if (!in_array('Person.name.full', $enabledcapabilities) && !in_array('Person.name.family', $enabledcapabilities) &&
|
||||
|
||||
+19
-16
@@ -33,6 +33,7 @@ require_once($CFG->dirroot.'/mod/lti/servicelib.php');
|
||||
// TODO: Switch to core oauthlib once implemented - MDL-30149.
|
||||
use mod_lti\service_exception_handler;
|
||||
use moodle\mod\lti as lti;
|
||||
use ltiservice_basicoutcomes\local\service\basicoutcomes;
|
||||
|
||||
$rawbody = file_get_contents("php://input");
|
||||
|
||||
@@ -46,24 +47,26 @@ if ($logrequests) {
|
||||
lti_log_request($rawbody);
|
||||
}
|
||||
|
||||
foreach (lti\OAuthUtil::get_headers() as $name => $value) {
|
||||
if ($name === 'Authorization') {
|
||||
// TODO: Switch to core oauthlib once implemented - MDL-30149.
|
||||
$oauthparams = lti\OAuthUtil::split_header($value);
|
||||
$ok = true;
|
||||
$type = null;
|
||||
$toolproxy = false;
|
||||
|
||||
$consumerkey = $oauthparams['oauth_consumer_key'];
|
||||
break;
|
||||
$consumerkey = lti\get_oauth_key_from_headers(null, array(basicoutcomes::SCOPE_BASIC_OUTCOMES));
|
||||
if ($consumerkey === false) {
|
||||
throw new Exception('Missing or invalid consumer key or access token.');
|
||||
} else if (is_string($consumerkey)) {
|
||||
$toolproxy = lti_get_tool_proxy_from_guid($consumerkey);
|
||||
if ($toolproxy !== false) {
|
||||
$secrets = array($toolproxy->secret);
|
||||
} else if (!empty($tool)) {
|
||||
$secrets = array($typeconfig['password']);
|
||||
} else {
|
||||
$secrets = lti_get_shared_secrets_by_key($consumerkey);
|
||||
}
|
||||
$sharedsecret = lti_verify_message($consumerkey, lti_get_shared_secrets_by_key($consumerkey), $rawbody);
|
||||
if ($sharedsecret === false) {
|
||||
throw new Exception('Message signature not valid');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($consumerkey)) {
|
||||
throw new Exception('Consumer key is missing.');
|
||||
}
|
||||
|
||||
$sharedsecret = lti_verify_message($consumerkey, lti_get_shared_secrets_by_key($consumerkey), $rawbody);
|
||||
|
||||
if ($sharedsecret === false) {
|
||||
throw new Exception('Message signature not valid');
|
||||
}
|
||||
|
||||
// TODO MDL-46023 Replace this code with a call to the new library.
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file contains a class definition for the Basic Outcomes resource
|
||||
*
|
||||
* @package ltiservice_basicoutcomes
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace ltiservice_basicoutcomes\local\resources;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* A resource implementing the Basic Outcomes service.
|
||||
*
|
||||
* @package ltiservice_basicoutcomes
|
||||
* @since Moodle 3.7
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class basicoutcomes extends \mod_lti\local\ltiservice\resource_base {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param \mod_lti\local\ltiservice\service_base $service Service instance
|
||||
*/
|
||||
public function __construct($service) {
|
||||
|
||||
parent::__construct($service);
|
||||
$this->id = 'Outcomes.LTI1';
|
||||
$this->template = '';
|
||||
$this->formats[] = 'application/vnd.ims.lti.v1.outcome+xml';
|
||||
$this->methods[] = 'POST';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource fully qualified endpoint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_endpoint() {
|
||||
|
||||
$url = new \moodle_url('/mod/lti/service.php');
|
||||
return $url->out(false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the request for this resource.
|
||||
*
|
||||
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
|
||||
*/
|
||||
public function execute($response) {
|
||||
// Should never be called as the endpoint sends requests to the LTI 1 service endpoint.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file contains a class definition for the Basic Outcomes service
|
||||
*
|
||||
* @package ltiservice_basicoutcomes
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace ltiservice_basicoutcomes\local\service;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* A service implementing Basic Outcomes.
|
||||
*
|
||||
* @package ltiservice_basicoutcomes
|
||||
* @since Moodle 3.7
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class basicoutcomes extends \mod_lti\local\ltiservice\service_base {
|
||||
|
||||
/** Scope for accessing the service */
|
||||
const SCOPE_BASIC_OUTCOMES = 'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
parent::__construct();
|
||||
$this->id = 'basicoutcomes';
|
||||
$this->name = 'Basic Outcomes';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resources for this service.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_resources() {
|
||||
|
||||
if (empty($this->resources)) {
|
||||
$this->resources = array();
|
||||
$this->resources[] = new \ltiservice_basicoutcomes\local\resources\basicoutcomes($this);
|
||||
}
|
||||
|
||||
return $this->resources;
|
||||
|
||||
}
|
||||
/**
|
||||
* Get the scope(s) permitted for the tool relevant to this service.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_permitted_scopes() {
|
||||
|
||||
$scopes = array();
|
||||
if (!isset($this->get_typeconfig()['acceptgrades']) || ($this->get_typeconfig()['acceptgrades'] != LTI_SETTING_NEVER)) {
|
||||
$scopes[] = self::SCOPE_BASIC_OUTCOMES;
|
||||
}
|
||||
|
||||
return $scopes;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of key/values to add to the launch parameters.
|
||||
*
|
||||
* @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
|
||||
* @param string $courseid The course id.
|
||||
* @param string $user The user id.
|
||||
* @param string $typeid The tool lti type id.
|
||||
* @param string $modlti The id of the lti activity.
|
||||
*
|
||||
* The type is passed to check the configuration
|
||||
* and not return parameters for services not used.
|
||||
*
|
||||
* @return array of key/value pairs to add as launch parameters.
|
||||
*/
|
||||
public function get_launch_parameters($messagetype, $courseid, $user, $typeid, $modlti = null) {
|
||||
global $COURSE;
|
||||
|
||||
$launchparameters = array();
|
||||
$tool = lti_get_type_type_config($typeid);
|
||||
if (isset($tool->{$this->get_component_id()})) {
|
||||
if ($tool->{$this->get_component_id()} == parent::SERVICE_ENABLED) {
|
||||
if ($messagetype === 'basic-lti-launch-request') {
|
||||
$launchparameters['lis_outcomes_service_url'] = '$BasicOutcome.url';
|
||||
$launchparameters['lis_result_sourcedid'] = '$BasicOutcome.sourcedId';
|
||||
}
|
||||
}
|
||||
}
|
||||
return $launchparameters;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Privacy Subsystem implementation for ltiservice_basicoutcomes.
|
||||
*
|
||||
* @package ltiservice_basicoutcomes
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace ltiservice_basicoutcomes\privacy;
|
||||
|
||||
use \core_privacy\local\metadata\collection;
|
||||
use \core_privacy\local\request\contextlist;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\userlist;
|
||||
use \core_privacy\local\request\approved_userlist;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Privacy Subsystem for ltiservice_basicoutcomes.
|
||||
*
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\core_privacy\local\request\core_userlist_provider,
|
||||
\core_privacy\local\request\plugin\provider {
|
||||
|
||||
/**
|
||||
* Returns meta data about this system.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->link_external_location('External LTI provider.', [
|
||||
'userid' => 'privacy:metadata:userid',
|
||||
'grade' => 'privacy:metadata:grade',
|
||||
], 'privacy:metadata:externalpurpose');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
return new contextlist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of users who have data within a context.
|
||||
*
|
||||
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
|
||||
*/
|
||||
public static function get_users_in_context(userlist $userlist) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data which matches the specified context.
|
||||
*
|
||||
* @param \context $context A user context.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete multiple users within a single context.
|
||||
*
|
||||
* @param approved_userlist $userlist The approved context and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_users(approved_userlist $userlist) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Strings for component 'ltiservice_basicoutcomes', language 'en'
|
||||
*
|
||||
* @package ltiservice_basicoutcomes
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['allow'] = 'Use this service to accept grades from the tool';
|
||||
$string['ltiservice_basicoutcomes'] = 'Basic Outcomes';
|
||||
$string['ltiservice_basicoutcomes_help'] = 'Allow the tool to save and retrieve its grades to a gradebook column associated with each link.';
|
||||
$string['notallow'] = 'Do not use this service';
|
||||
$string['pluginname'] = 'Basic Outcomes Service';
|
||||
$string['privacy:metadata:externalpurpose'] = 'This information is sent to an external LTI provider.';
|
||||
$string['privacy:metadata:grade'] = 'The tool\'s grades of the user using the LTI consumer.';
|
||||
$string['privacy:metadata:userid'] = 'The ID of the user using the LTI consumer.';
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Version information for the ltiservice_basicoutcomes service.
|
||||
*
|
||||
* @package ltiservice_basicoutcomes
|
||||
* @copyright 2019 Stephen Vickers
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
$plugin->version = 2019030500;
|
||||
$plugin->requires = 2018112800;
|
||||
$plugin->component = 'ltiservice_basicoutcomes';
|
||||
@@ -68,86 +68,51 @@ class lineitem extends resource_base {
|
||||
$params = $this->parse_template();
|
||||
$contextid = $params['context_id'];
|
||||
$itemid = $params['item_id'];
|
||||
if ($response->get_request_method() === 'GET') {
|
||||
$contenttype = $response->get_accept();
|
||||
} else {
|
||||
$contenttype = $response->get_content_type();
|
||||
}
|
||||
$isget = $response->get_request_method() === self::HTTP_GET;
|
||||
// We will receive typeid when working with LTI 1.x, if not then we are in LTI 2.
|
||||
$typeid = optional_param('type_id', null, PARAM_ALPHANUM);
|
||||
if (is_null($typeid)) {
|
||||
if (!$this->check_tool_proxy(null, $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid tool proxy specified.");
|
||||
return;
|
||||
$typeid = optional_param('type_id', null, PARAM_INT);
|
||||
|
||||
$scopes = array(gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM);
|
||||
if ($response->get_request_method() === self::HTTP_GET) {
|
||||
$scopes[] = gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$this->check_tool($typeid, $response->get_request_data(), $scopes)) {
|
||||
throw new \Exception(null, 401);
|
||||
}
|
||||
} else {
|
||||
$typeid = $this->get_service()->get_type()->id;
|
||||
if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) {
|
||||
throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404);
|
||||
}
|
||||
if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) {
|
||||
throw new \Exception('Not allowed in context', 403);
|
||||
}
|
||||
if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
|
||||
throw new \Exception("Not Found: Grade item {$itemid} doesn't exist", 404);
|
||||
}
|
||||
$item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
|
||||
if ($item === false) {
|
||||
throw new \Exception('Line item does not exist', 404);
|
||||
}
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
switch ($response->get_request_method()) {
|
||||
case self::HTTP_GET:
|
||||
if (!$this->check_type($typeid, $contextid, 'LineItem.item:get', $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("This resource does not support GET requests.");
|
||||
return;
|
||||
}
|
||||
$this->get_request($response, $item, $typeid);
|
||||
break;
|
||||
case self::HTTP_PUT:
|
||||
if (!$this->check_type($typeid, $contextid, 'LineItem.item:put', $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("This resource does not support PUT requests.");
|
||||
return;
|
||||
}
|
||||
$json = $this->process_put_request($response->get_request_data(), $item, $typeid);
|
||||
$response->set_body($json);
|
||||
$response->set_code(200);
|
||||
break;
|
||||
case self::HTTP_DELETE:
|
||||
if (!$this->check_type($typeid, $contextid, 'LineItem.item:delete', $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("This resource does not support DELETE requests.");
|
||||
return;
|
||||
}
|
||||
$this->process_delete_request($item);
|
||||
$response->set_code(204);
|
||||
break;
|
||||
default: // Should not be possible.
|
||||
$response->set_code(405);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (empty($contextid) || (!empty($contenttype) && !in_array($contenttype, $this->formats))) {
|
||||
$response->set_code(400);
|
||||
$response->set_reason("Invalid request made.");
|
||||
return;
|
||||
}
|
||||
if (!$DB->record_exists('course', array('id' => $contextid))) {
|
||||
$response->set_code(404);
|
||||
$response->set_reason("Not Found: Course $contextid doesn't exist.");
|
||||
return;
|
||||
}
|
||||
if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
|
||||
$response->set_code(404);
|
||||
$response->set_reason("Not Found: Grade item $itemid doesn't exist.");
|
||||
return;
|
||||
}
|
||||
$item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
|
||||
if ($item === false) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Line item does not exist.");
|
||||
return;
|
||||
}
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
switch ($response->get_request_method()) {
|
||||
case 'GET':
|
||||
$this->get_request($response, $item, $typeid);
|
||||
break;
|
||||
case 'PUT':
|
||||
$json = $this->process_put_request($response->get_request_data(), $item, $typeid);
|
||||
$response->set_body($json);
|
||||
$response->set_code(200);
|
||||
break;
|
||||
case 'DELETE':
|
||||
$this->process_delete_request($item);
|
||||
$response->set_code(204);
|
||||
break;
|
||||
default: // Should not be possible.
|
||||
$response->set_code(405);
|
||||
$response->set_reason("Invalid request method specified.");
|
||||
return;
|
||||
} catch (\Exception $e) {
|
||||
$response->set_code($e->getCode());
|
||||
$response->set_reason($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +183,24 @@ class lineitem extends resource_base {
|
||||
$gbs->tag = $tag;
|
||||
}
|
||||
$ltilinkid = null;
|
||||
if (isset($json->ltiLinkId)) {
|
||||
if (isset($json->resourceLinkId)) {
|
||||
if (is_numeric($json->resourceLinkId)) {
|
||||
$ltilinkid = $json->resourceLinkId;
|
||||
if ($gbs) {
|
||||
if (intval($gbs->ltilinkid) !== intval($json->resourceLinkId)) {
|
||||
$gbs->ltilinkid = $json->resourceLinkId;
|
||||
$upgradegradebookservices = true;
|
||||
}
|
||||
} else {
|
||||
if (intval($item->iteminstance) !== intval($json->resourceLinkId)) {
|
||||
$item->iteminstance = intval($json->resourceLinkId);
|
||||
$updategradeitem = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new \Exception(null, 400);
|
||||
}
|
||||
} else if (isset($json->ltiLinkId)) {
|
||||
if (is_numeric($json->ltiLinkId)) {
|
||||
$ltilinkid = $json->ltiLinkId;
|
||||
if ($gbs) {
|
||||
@@ -316,24 +298,6 @@ class lineitem extends resource_base {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get permissions from the config of the tool for that resource
|
||||
*
|
||||
* @param int $typeid
|
||||
*
|
||||
* @return array with the permissions related to this resource by the $lti_type or null if none.
|
||||
*/
|
||||
public function get_permissions($typeid) {
|
||||
$tool = lti_get_type_type_config($typeid);
|
||||
if ($tool->ltiservice_gradesynchronization == '1') {
|
||||
return array('LineItem.item:get');
|
||||
} else if ($tool->ltiservice_gradesynchronization == '2') {
|
||||
return array('LineItem.item:get', 'LineItem.item:put', 'LineItem.item:delete');
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a value for custom parameter substitution variables.
|
||||
*
|
||||
@@ -348,7 +312,13 @@ class lineitem extends resource_base {
|
||||
require_once($CFG->libdir . '/gradelib.php');
|
||||
|
||||
$this->params['context_id'] = $COURSE->id;
|
||||
if ($tool = $this->get_service()->get_type()) {
|
||||
$this->params['tool_code'] = $tool->id;
|
||||
}
|
||||
$id = optional_param('id', 0, PARAM_INT); // Course Module ID.
|
||||
if (empty($id)) {
|
||||
$id = optional_param('lti_message_hint', 0, PARAM_INT);
|
||||
}
|
||||
if (!empty($id)) {
|
||||
$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
|
||||
$id = $cm->instance;
|
||||
@@ -356,6 +326,7 @@ class lineitem extends resource_base {
|
||||
if ($item && $item->items) {
|
||||
$this->params['item_id'] = $item->items[0]->id;
|
||||
$resolved = parent::get_endpoint();
|
||||
$resolved .= "?type_id={$tool->id}";
|
||||
}
|
||||
}
|
||||
$value = str_replace('$LineItem.url', $resolved, $value);
|
||||
|
||||
@@ -74,51 +74,35 @@ class lineitems extends resource_base {
|
||||
$contenttype = $response->get_content_type();
|
||||
}
|
||||
$container = empty($contenttype) || ($contenttype === $this->formats[0]);
|
||||
// We will receive typeid when working with LTI 1.x, if not the we are in LTI 2.
|
||||
$typeid = optional_param('type_id', null, PARAM_ALPHANUM);
|
||||
if (is_null($typeid)) {
|
||||
if (!$this->check_tool_proxy(null, $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid tool proxy specified.");
|
||||
return;
|
||||
// We will receive typeid when working with LTI 1.x, if not then we are in LTI 2.
|
||||
$typeid = optional_param('type_id', null, PARAM_INT);
|
||||
|
||||
$scopes = array(gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM);
|
||||
if ($response->get_request_method() === self::HTTP_GET) {
|
||||
$scopes[] = gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$this->check_tool($typeid, $response->get_request_data(), $scopes)) {
|
||||
throw new \Exception(null, 401);
|
||||
}
|
||||
} else {
|
||||
switch ($response->get_request_method()) {
|
||||
case self::HTTP_GET:
|
||||
if (!$this->check_type($typeid, $contextid, 'LineItem.collection:get', $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("This resource does not support GET requests.");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case self::HTTP_POST:
|
||||
if (!$this->check_type($typeid, $contextid, 'LineItem.collection:post', $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("This resource does not support POST requests.");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default: // Should not be possible.
|
||||
$response->set_code(405);
|
||||
$response->set_reason("Invalid request method specified.");
|
||||
return;
|
||||
$typeid = $this->get_service()->get_type()->id;
|
||||
if (empty($contextid) || !($container ^ ($response->get_request_method() === self::HTTP_POST)) ||
|
||||
(!empty($contenttype) && !in_array($contenttype, $this->formats))) {
|
||||
throw new \Exception('No context or unsupported content type', 400);
|
||||
}
|
||||
}
|
||||
if (empty($contextid) || !($container ^ ($response->get_request_method() === self::HTTP_POST)) ||
|
||||
(!empty($contenttype) && !in_array($contenttype, $this->formats))) {
|
||||
$response->set_code(400);
|
||||
$response->set_reason("Invalid request made.");
|
||||
return;
|
||||
}
|
||||
if (!$DB->record_exists('course', array('id' => $contextid))) {
|
||||
$response->set_code(404);
|
||||
$response->set_reason("Not Found: Course $contextid doesn't exist.");
|
||||
return;
|
||||
}
|
||||
switch ($response->get_request_method()) {
|
||||
case self::HTTP_GET:
|
||||
if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) {
|
||||
throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404);
|
||||
}
|
||||
if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) {
|
||||
throw new \Exception('Not allowed in context', 403);
|
||||
}
|
||||
if ($response->get_request_method() !== self::HTTP_POST) {
|
||||
$resourceid = optional_param('resource_id', null, PARAM_TEXT);
|
||||
$ltilinkid = optional_param('lti_link_id', null, PARAM_TEXT);
|
||||
$ltilinkid = optional_param('resource_link_id', null, PARAM_TEXT);
|
||||
if (is_null($ltilinkid)) {
|
||||
$ltilinkid = optional_param('lti_link_id', null, PARAM_TEXT);
|
||||
}
|
||||
$tag = optional_param('tag', null, PARAM_TEXT);
|
||||
$limitnum = optional_param('limit', 0, PARAM_INT);
|
||||
$limitfrom = optional_param('from', 0, PARAM_INT);
|
||||
@@ -129,23 +113,18 @@ class lineitems extends resource_base {
|
||||
$json = $this->get_json_for_get_request($items, $resourceid, $ltilinkid, $tag, $limitfrom,
|
||||
$limitnum, $totalcount, $typeid, $response);
|
||||
$response->set_content_type($this->formats[0]);
|
||||
break;
|
||||
case self::HTTP_POST:
|
||||
try {
|
||||
$json = $this->get_json_for_post_request($response->get_request_data(), $contextid, $typeid);
|
||||
$response->set_code(201);
|
||||
$response->set_content_type($this->formats[1]);
|
||||
} catch (\Exception $e) {
|
||||
$response->set_code($e->getCode());
|
||||
$response->set_reason($e->getMessage());
|
||||
}
|
||||
break;
|
||||
default: // Should not be possible.
|
||||
$response->set_code(405);
|
||||
$response->set_reason("Invalid request method specified.");
|
||||
return;
|
||||
} else {
|
||||
$json = $this->get_json_for_post_request($response->get_request_data(), $contextid, $typeid);
|
||||
$response->set_code(201);
|
||||
$response->set_content_type($this->formats[1]);
|
||||
}
|
||||
$response->set_body($json);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$response->set_code($e->getCode());
|
||||
$response->set_reason($e->getMessage());
|
||||
}
|
||||
$response->set_body($json);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,7 +165,7 @@ class lineitems extends resource_base {
|
||||
$baseurl->param('resource_id', $resourceid);
|
||||
}
|
||||
if (isset($ltilinkid)) {
|
||||
$baseurl->param('lti_link_id', $ltilinkid);
|
||||
$baseurl->param('resource_link_id', $ltilinkid);
|
||||
}
|
||||
if (isset($tag)) {
|
||||
$baseurl->param('tag', $tag);
|
||||
@@ -217,7 +196,7 @@ class lineitems extends resource_base {
|
||||
}
|
||||
}
|
||||
|
||||
$jsonitems=[];
|
||||
$jsonitems = [];
|
||||
$endpoint = parent::get_endpoint();
|
||||
foreach ($items as $item) {
|
||||
array_push($jsonitems, gradebookservices::item_for_json($item, $endpoint, $typeid));
|
||||
@@ -255,7 +234,7 @@ class lineitems extends resource_base {
|
||||
if (empty($json) ||
|
||||
!isset($json->scoreMaximum) ||
|
||||
!isset($json->label)) {
|
||||
throw new \Exception(null, 400);
|
||||
throw new \Exception('No label or Score Maximum', 400);
|
||||
}
|
||||
if (is_numeric($json->scoreMaximum)) {
|
||||
$max = $json->scoreMaximum;
|
||||
@@ -264,7 +243,10 @@ class lineitems extends resource_base {
|
||||
}
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
$resourceid = (isset($json->resourceId)) ? $json->resourceId : '';
|
||||
$ltilinkid = (isset($json->ltiLinkId)) ? $json->ltiLinkId : null;
|
||||
$ltilinkid = (isset($json->resourceLinkId)) ? $json->resourceLinkId : null;
|
||||
if ($ltilinkid == null) {
|
||||
$ltilinkid = (isset($json->ltiLinkId)) ? $json->ltiLinkId : null;
|
||||
}
|
||||
if ($ltilinkid != null) {
|
||||
if (is_null($typeid)) {
|
||||
if (!gradebookservices::check_lti_id($ltilinkid, $contextid, $this->get_service()->get_tool_proxy()->id)) {
|
||||
@@ -313,24 +295,6 @@ class lineitems extends resource_base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* get permissions from the config of the tool for that resource
|
||||
*
|
||||
* @param string $typeid
|
||||
*
|
||||
* @return array with the permissions related to this resource by the lti type or null if none.
|
||||
*/
|
||||
public function get_permissions($typeid) {
|
||||
$tool = lti_get_type_type_config($typeid);
|
||||
if ($tool->ltiservice_gradesynchronization == '1') {
|
||||
return array('LineItem.collection:get');
|
||||
} else if ($tool->ltiservice_gradesynchronization == '2') {
|
||||
return array('LineItem.collection:get', 'LineItem.collection:post');
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a value for custom parameter substitution variables.
|
||||
*
|
||||
@@ -343,7 +307,11 @@ class lineitems extends resource_base {
|
||||
|
||||
if (strpos($value, '$LineItems.url') !== false) {
|
||||
$this->params['context_id'] = $COURSE->id;
|
||||
$value = str_replace('$LineItems.url', parent::get_endpoint(), $value);
|
||||
$query = '';
|
||||
if (($tool = $this->get_service()->get_type())) {
|
||||
$query = "?type_id={$tool->id}";
|
||||
}
|
||||
$value = str_replace('$LineItems.url', parent::get_endpoint() . $query, $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
@@ -31,7 +31,7 @@ use mod_lti\local\ltiservice\resource_base;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* A resource implementing LISResults container.
|
||||
* A resource implementing LISResult container.
|
||||
*
|
||||
* @package ltiservice_gradebookservices
|
||||
* @copyright 2017 Cengage Learning http://www.cengage.com
|
||||
@@ -66,90 +66,76 @@ class results extends resource_base {
|
||||
$contextid = $params['context_id'];
|
||||
$itemid = $params['item_id'];
|
||||
|
||||
$isget = $response->get_request_method() === 'GET';
|
||||
if ($isget) {
|
||||
$contenttype = $response->get_accept();
|
||||
} else {
|
||||
$contenttype = $response->get_content_type();
|
||||
}
|
||||
$isget = $response->get_request_method() === self::HTTP_GET;
|
||||
// We will receive typeid when working with LTI 1.x, if not the we are in LTI 2.
|
||||
$typeid = optional_param('type_id', null, PARAM_ALPHANUM);
|
||||
if (is_null($typeid)) {
|
||||
if (!$this->check_tool_proxy(null, $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid tool proxy specified.");
|
||||
return;
|
||||
$typeid = optional_param('type_id', null, PARAM_INT);
|
||||
|
||||
$scope = gradebookservices::SCOPE_GRADEBOOKSERVICES_RESULT_READ;
|
||||
|
||||
try {
|
||||
if (!$this->check_tool($typeid, $response->get_request_data(), array($scope))) {
|
||||
throw new \Exception(null, 401);
|
||||
}
|
||||
} else {
|
||||
if (!$this->check_type($typeid, $contextid, 'Result.collection:get', $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("This resource does not support GET requests.");
|
||||
return;
|
||||
$typeid = $this->get_service()->get_type()->id;
|
||||
if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) {
|
||||
throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404);
|
||||
}
|
||||
}
|
||||
if (empty($contextid) || (!empty($contenttype) && !in_array($contenttype, $this->formats))) {
|
||||
$response->set_code(400);
|
||||
$response->set_reason("Invalid request made.");
|
||||
return;
|
||||
}
|
||||
if (!$DB->record_exists('course', array('id' => $contextid))) {
|
||||
$response->set_code(404);
|
||||
$response->set_reason("Not Found: Course $contextid doesn't exist.");
|
||||
return;
|
||||
}
|
||||
if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
|
||||
$response->set_code(404);
|
||||
$response->set_reason("Not Found: Grade item $itemid doesn't exist.");
|
||||
return;
|
||||
}
|
||||
$item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
|
||||
if ($item === false) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Line item does not exist.");
|
||||
return;
|
||||
}
|
||||
$gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($itemid);
|
||||
$ltilinkid = null;
|
||||
if (isset($item->iteminstance)) {
|
||||
$ltilinkid = $item->iteminstance;
|
||||
} else if ($gbs && isset($gbs->ltilinkid)) {
|
||||
$ltilinkid = $gbs->ltilinkid;
|
||||
}
|
||||
if ($ltilinkid != null) {
|
||||
if (is_null($typeid)) {
|
||||
if (isset($item->iteminstance) && (!gradebookservices::check_lti_id($ltilinkid, $item->courseid,
|
||||
$this->get_service()->get_tool_proxy()->id))) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid LTI id supplied.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (isset($item->iteminstance) && (!gradebookservices::check_lti_1x_id($ltilinkid, $item->courseid,
|
||||
$typeid))) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid LTI id supplied.");
|
||||
return;
|
||||
if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) {
|
||||
throw new \Exception('Not allowed in context', 403);
|
||||
}
|
||||
if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
|
||||
throw new \Exception("Not Found: Grade item {$itemid} doesn't exist", 404);
|
||||
}
|
||||
$item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
|
||||
if ($item === false) {
|
||||
throw new \Exception('Line item does not exist', 404);
|
||||
}
|
||||
$gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($itemid);
|
||||
$ltilinkid = null;
|
||||
if (isset($item->iteminstance)) {
|
||||
$ltilinkid = $item->iteminstance;
|
||||
} else if ($gbs && isset($gbs->ltilinkid)) {
|
||||
$ltilinkid = $gbs->ltilinkid;
|
||||
}
|
||||
if ($ltilinkid != null) {
|
||||
if (is_null($typeid)) {
|
||||
if (isset($item->iteminstance) && (!gradebookservices::check_lti_id($ltilinkid, $item->courseid,
|
||||
$this->get_service()->get_tool_proxy()->id))) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid LTI id supplied.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (isset($item->iteminstance) && (!gradebookservices::check_lti_1x_id($ltilinkid, $item->courseid,
|
||||
$typeid))) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid LTI id supplied.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
switch ($response->get_request_method()) {
|
||||
case 'GET':
|
||||
$useridfilter = optional_param('user_id', 0, PARAM_INT);
|
||||
$limitnum = optional_param('limit', 0, PARAM_INT);
|
||||
$limitfrom = optional_param('from', 0, PARAM_INT);
|
||||
$typeid = optional_param('type_id', null, PARAM_TEXT);
|
||||
$json = $this->get_json_for_get_request($item->id, $limitfrom, $limitnum,
|
||||
$useridfilter, $typeid, $response);
|
||||
$response->set_content_type($this->formats[0]);
|
||||
$response->set_body($json);
|
||||
break;
|
||||
default: // Should not be possible.
|
||||
$response->set_code(405);
|
||||
$response->set_reason("Invalid request method specified.");
|
||||
return;
|
||||
}
|
||||
$response->set_body($json);
|
||||
} catch (\Exception $e) {
|
||||
$response->set_code($e->getCode());
|
||||
$response->set_reason($e->getMessage());
|
||||
}
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
switch ($response->get_request_method()) {
|
||||
case 'GET':
|
||||
$useridfilter = optional_param('user_id', 0, PARAM_INT);
|
||||
$limitnum = optional_param('limit', 0, PARAM_INT);
|
||||
$limitfrom = optional_param('from', 0, PARAM_INT);
|
||||
$typeid = optional_param('type_id', null, PARAM_TEXT);
|
||||
$json = $this->get_json_for_get_request($item->id, $limitfrom, $limitnum,
|
||||
$useridfilter, $typeid, $response);
|
||||
$response->set_content_type($this->formats[0]);
|
||||
$response->set_body($json);
|
||||
break;
|
||||
default: // Should not be possible.
|
||||
$response->set_code(405);
|
||||
$response->set_reason("Invalid request method specified.");
|
||||
return;
|
||||
}
|
||||
$response->set_body($json);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,24 +241,6 @@ class results extends resource_base {
|
||||
return json_encode($jsonresults);
|
||||
}
|
||||
|
||||
/**
|
||||
* get permissions from the config of the tool for that resource
|
||||
*
|
||||
* @param int $typeid
|
||||
*
|
||||
* @return array with the permissions related to this resource by the $lti_type or null if none.
|
||||
*/
|
||||
public function get_permissions($typeid) {
|
||||
$tool = lti_get_type_type_config($typeid);
|
||||
if ($tool->ltiservice_gradesynchronization == '1') {
|
||||
return array('Result.collection:get');
|
||||
} else if ($tool->ltiservice_gradesynchronization == '2') {
|
||||
return array('Result.collection:get');
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a value for custom parameter substitution variables.
|
||||
*
|
||||
|
||||
@@ -80,96 +80,82 @@ class scores extends resource_base {
|
||||
$container = empty($contenttype) || ($contenttype === $this->formats[0]);
|
||||
// We will receive typeid when working with LTI 1.x, if not the we are in LTI 2.
|
||||
$typeid = optional_param('type_id', null, PARAM_ALPHANUM);
|
||||
if (is_null($typeid)) {
|
||||
if (!$this->check_tool_proxy(null, $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
return;
|
||||
|
||||
$scope = gradebookservices::SCOPE_GRADEBOOKSERVICES_SCORE;
|
||||
|
||||
try {
|
||||
if (!$this->check_tool($typeid, $response->get_request_data(), array($scope))) {
|
||||
throw new \Exception(null, 401);
|
||||
}
|
||||
} else {
|
||||
$typeid = $this->get_service()->get_type()->id;
|
||||
if (empty($contextid) || !($container ^ ($response->get_request_method() === self::HTTP_POST)) ||
|
||||
(!empty($contenttype) && !in_array($contenttype, $this->formats))) {
|
||||
throw new \Exception('No context or unsupported content type', 400);
|
||||
}
|
||||
if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) {
|
||||
throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404);
|
||||
}
|
||||
if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) {
|
||||
throw new \Exception('Not allowed in context', 403);
|
||||
}
|
||||
if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
|
||||
throw new \Exception("Not Found: Grade item {$itemid} doesn't exist", 404);
|
||||
}
|
||||
$item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
|
||||
if ($item === false) {
|
||||
throw new \Exception('Line item does not exist', 404);
|
||||
}
|
||||
$gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($itemid);
|
||||
$ltilinkid = null;
|
||||
if (isset($item->iteminstance)) {
|
||||
$ltilinkid = $item->iteminstance;
|
||||
} else if ($gbs && isset($gbs->ltilinkid)) {
|
||||
$ltilinkid = $gbs->ltilinkid;
|
||||
}
|
||||
if ($ltilinkid != null) {
|
||||
if (is_null($typeid)) {
|
||||
if (isset($item->iteminstance) && (!gradebookservices::check_lti_id($ltilinkid, $item->courseid,
|
||||
$this->get_service()->get_tool_proxy()->id))) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid LTI id supplied.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (isset($item->iteminstance) && (!gradebookservices::check_lti_1x_id($ltilinkid, $item->courseid,
|
||||
$typeid))) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid LTI id supplied.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
$json = '[]';
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
switch ($response->get_request_method()) {
|
||||
case 'GET':
|
||||
$response->set_code(405);
|
||||
$response->set_reason("GET requests are not allowed.");
|
||||
return;
|
||||
break;
|
||||
case 'POST':
|
||||
if (!$this->check_type($typeid, $contextid, 'Score.collection:post', $response->get_request_data())) {
|
||||
$response->set_code(401);
|
||||
$response->set_reason("This resource does not support POST requests.");
|
||||
return;
|
||||
try {
|
||||
$json = $this->get_json_for_post_request($response, $response->get_request_data(), $item, $contextid,
|
||||
$typeid);
|
||||
$response->set_content_type($this->formats[1]);
|
||||
} catch (\Exception $e) {
|
||||
$response->set_code($e->getCode());
|
||||
$response->set_reason($e->getMessage());
|
||||
}
|
||||
break;
|
||||
default: // Should not be possible.
|
||||
$response->set_code(405);
|
||||
$response->set_reason("Invalid request method specified.");
|
||||
return;
|
||||
}
|
||||
$response->set_body($json);
|
||||
} catch (\Exception $e) {
|
||||
$response->set_code($e->getCode());
|
||||
$response->set_reason($e->getMessage());
|
||||
}
|
||||
if (empty($contextid) || !($container ^ ($response->get_request_method() === 'POST')) ||
|
||||
(!empty($contenttype) && !in_array($contenttype, $this->formats))) {
|
||||
$response->set_code(400);
|
||||
return;
|
||||
}
|
||||
if (!$DB->record_exists('course', array('id' => $contextid))) {
|
||||
$response->set_code(404);
|
||||
$response->set_reason("Not Found: Course $contextid doesn't exist.");
|
||||
return;
|
||||
}
|
||||
if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
|
||||
$response->set_code(404);
|
||||
$response->set_reason("Not Found: Grade item $itemid doesn't exist.");
|
||||
return;
|
||||
}
|
||||
$item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
|
||||
if ($item === false) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Line item does not exist.");
|
||||
return;
|
||||
}
|
||||
$gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($itemid);
|
||||
$ltilinkid = null;
|
||||
if (isset($item->iteminstance)) {
|
||||
$ltilinkid = $item->iteminstance;
|
||||
} else if ($gbs && isset($gbs->ltilinkid)) {
|
||||
$ltilinkid = $gbs->ltilinkid;
|
||||
}
|
||||
if ($ltilinkid != null) {
|
||||
if (is_null($typeid)) {
|
||||
if (isset($item->iteminstance) && (!gradebookservices::check_lti_id($ltilinkid, $item->courseid,
|
||||
$this->get_service()->get_tool_proxy()->id))) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid LTI id supplied.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (isset($item->iteminstance) && (!gradebookservices::check_lti_1x_id($ltilinkid, $item->courseid,
|
||||
$typeid))) {
|
||||
$response->set_code(403);
|
||||
$response->set_reason("Invalid LTI id supplied.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
$json = '[]';
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
switch ($response->get_request_method()) {
|
||||
case 'GET':
|
||||
$response->set_code(405);
|
||||
$response->set_reason("GET requests are not allowed.");
|
||||
break;
|
||||
case 'POST':
|
||||
try {
|
||||
$json = $this->get_json_for_post_request($response, $response->get_request_data(), $item, $contextid, $typeid);
|
||||
$response->set_content_type($this->formats[1]);
|
||||
} catch (\Exception $e) {
|
||||
$response->set_code($e->getCode());
|
||||
$response->set_reason($e->getMessage());
|
||||
}
|
||||
break;
|
||||
default: // Should not be possible.
|
||||
$response->set_code(405);
|
||||
$response->set_reason("Invalid request method specified.");
|
||||
return;
|
||||
}
|
||||
$response->set_body($json);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,10 +179,11 @@ class scores extends resource_base {
|
||||
!isset($score->timestamp) ||
|
||||
isset($score->timestamp) && !gradebookservices::validate_iso8601_date($score->timestamp) ||
|
||||
(isset($score->scoreGiven) && !is_numeric($score->scoreGiven)) ||
|
||||
(isset($score->scoreGiven) && !isset($score->scoreMaximum)) ||
|
||||
(isset($score->scoreMaximum) && !is_numeric($score->scoreMaximum)) ||
|
||||
(!gradebookservices::is_user_gradable_in_course($contextid, $score->userId))
|
||||
) {
|
||||
throw new \Exception('Incorrect score received' . $score, 400);
|
||||
throw new \Exception('Incorrect score received' . $body, 400);
|
||||
}
|
||||
$score->timemodified = intval($score->timestamp);
|
||||
|
||||
@@ -216,25 +203,7 @@ class scores extends resource_base {
|
||||
$score->scoreGiven = null;
|
||||
}
|
||||
}
|
||||
gradebookservices::save_score($item, $score, $score->userId, $typeid);
|
||||
}
|
||||
|
||||
/**
|
||||
* get permissions from the config of the tool for that resource
|
||||
*
|
||||
* @param int $typeid
|
||||
*
|
||||
* @return array with the permissions related to this resource by the $lti_type or null if none.
|
||||
*/
|
||||
public function get_permissions($typeid) {
|
||||
$tool = lti_get_type_type_config($typeid);
|
||||
if ($tool->ltiservice_gradesynchronization == '1') {
|
||||
return array('Score.collection:post');
|
||||
} else if ($tool->ltiservice_gradesynchronization == '2') {
|
||||
return array('Score.collection:post');
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
$this->get_service()->save_grade_item($item, $score, $score->userId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,8 +43,19 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class gradebookservices extends service_base {
|
||||
|
||||
/** Internal service name */
|
||||
const SERVICE_NAME = 'ltiservice_gradebookservices';
|
||||
/** Read-only access to Gradebook services */
|
||||
const GRADEBOOKSERVICES_READ = 1;
|
||||
/** Full access to Gradebook services */
|
||||
const GRADEBOOKSERVICES_FULL = 2;
|
||||
/** Scope for full access to Lineitem service */
|
||||
const SCOPE_GRADEBOOKSERVICES_LINEITEM = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem';
|
||||
/** Scope for full access to Lineitem service */
|
||||
const SCOPE_GRADEBOOKSERVICES_LINEITEM_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly';
|
||||
/** Scope for access to Result service */
|
||||
const SCOPE_GRADEBOOKSERVICES_RESULT_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly';
|
||||
/** Scope for access to Score service */
|
||||
const SCOPE_GRADEBOOKSERVICES_SCORE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score';
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
@@ -53,7 +64,7 @@ class gradebookservices extends service_base {
|
||||
|
||||
parent::__construct();
|
||||
$this->id = 'gradebookservices';
|
||||
$this->name = $this->get_string('servicename');
|
||||
$this->name = get_string($this->get_component_id(), $this->get_component_id());
|
||||
|
||||
}
|
||||
|
||||
@@ -77,13 +88,40 @@ class gradebookservices extends service_base {
|
||||
return $this->resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scope(s) permitted for this service.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_permitted_scopes() {
|
||||
|
||||
$scopes = array();
|
||||
$ok = !empty($this->get_type());
|
||||
if ($ok && isset($this->get_typeconfig()[$this->get_component_id()])) {
|
||||
if (!empty($setting = $this->get_typeconfig()[$this->get_component_id()])) {
|
||||
$scopes[] = self::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ;
|
||||
$scopes[] = self::SCOPE_GRADEBOOKSERVICES_RESULT_READ;
|
||||
$scopes[] = self::SCOPE_GRADEBOOKSERVICES_SCORE;
|
||||
if ($setting == self::GRADEBOOKSERVICES_FULL) {
|
||||
$scopes[] = self::SCOPE_GRADEBOOKSERVICES_LINEITEM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $scopes;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds form elements for gradebook sync add/edit page.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform Moodle quickform object definition
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see gradebookservices::get_configuration_elements()
|
||||
*/
|
||||
public function get_configuration_options(&$mform) {
|
||||
|
||||
debugging('get_configuration_options() has been deprecated, ' .
|
||||
'please use service_base::get_configuration_elements() instead.', DEBUG_DEVELOPER);
|
||||
$selectelementname = 'ltiservice_gradesynchronization';
|
||||
$identifier = 'grade_synchronization';
|
||||
$options = [
|
||||
@@ -91,7 +129,6 @@ class gradebookservices extends service_base {
|
||||
$this->get_string('partialgs'),
|
||||
$this->get_string('alwaysgs')
|
||||
];
|
||||
|
||||
$mform->addElement('select', $selectelementname, $this->get_string($identifier), $options);
|
||||
$mform->setType($selectelementname, 'int');
|
||||
$mform->setDefault($selectelementname, 0);
|
||||
@@ -99,23 +136,21 @@ class gradebookservices extends service_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves string from lang file
|
||||
* Create form element for gradebook sync add/edit page.
|
||||
*
|
||||
* @param string $identifier
|
||||
* @return string
|
||||
* @return array of \HTML_QuickForm_element Form elements
|
||||
*/
|
||||
private function get_string($identifier) {
|
||||
return get_string($identifier, self::SERVICE_NAME);
|
||||
}
|
||||
public function get_configuration_elements() {
|
||||
$elements = array();
|
||||
$options = [
|
||||
get_string('nevergs', $this->get_component_id()),
|
||||
get_string('partialgs', $this->get_component_id()),
|
||||
get_string('alwaysgs', $this->get_component_id())
|
||||
];
|
||||
$elements[''] = new \MoodleQuickForm_select($this->get_component_id(), get_string($this->get_component_id(),
|
||||
$this->get_component_id()), $options);
|
||||
|
||||
/**
|
||||
* Return an array with the names of the parameters that the service will be saving in the configuration
|
||||
*
|
||||
* @return array with the names of the parameters that the service will be saving in the configuration
|
||||
*
|
||||
*/
|
||||
public function get_configuration_parameter_names() {
|
||||
return array('ltiservice_gradesynchronization');
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,15 +169,15 @@ class gradebookservices extends service_base {
|
||||
*/
|
||||
public function get_launch_parameters($messagetype, $courseid, $user, $typeid, $modlti = null) {
|
||||
global $DB;
|
||||
|
||||
$launchparameters = array();
|
||||
$tool = lti_get_type_type_config($typeid);
|
||||
$this->set_type(lti_get_type($typeid));
|
||||
$this->set_typeconfig(lti_get_type_config($typeid));
|
||||
// Only inject parameters if the service is enabled for this tool.
|
||||
if (isset($tool->ltiservice_gradesynchronization)) {
|
||||
if ($tool->ltiservice_gradesynchronization == '1' || $tool->ltiservice_gradesynchronization == '2') {
|
||||
if (isset($this->get_typeconfig()[$this->get_component_id()])) {
|
||||
if ($this->get_typeconfig()[$this->get_component_id()] == self::GRADEBOOKSERVICES_READ ||
|
||||
$this->get_typeconfig()[$this->get_component_id()] == self::GRADEBOOKSERVICES_FULL) {
|
||||
// Check for used in context is only needed because there is no explicit site tool - course relation.
|
||||
if ($this->is_allowed_in_context($typeid, $courseid)) {
|
||||
$endpoint = $this->get_service_path() . "/{$courseid}/lineitems";
|
||||
if (is_null($modlti)) {
|
||||
$id = null;
|
||||
} else {
|
||||
@@ -164,9 +199,10 @@ class gradebookservices extends service_base {
|
||||
$id = null;
|
||||
}
|
||||
}
|
||||
$launchparameters['custom_lineitems_url'] = $endpoint . "?type_id={$typeid}";
|
||||
$launchparameters['gradebookservices_scope'] = implode(',', $this->get_permitted_scopes());
|
||||
$launchparameters['lineitems_url'] = '$LineItems.url';
|
||||
if (!is_null($id)) {
|
||||
$launchparameters['custom_lineitem_url'] = $endpoint . "/{$id}/lineitem?type_id={$typeid}";
|
||||
$launchparameters['lineitem_url'] = '$LineItem.url';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,10 +352,26 @@ class gradebookservices extends service_base {
|
||||
* @param int $userid User ID
|
||||
*
|
||||
* @throws \Exception
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see gradebookservices::save_grade_item($gradeitem, $score, $userid)
|
||||
*/
|
||||
public static function save_score($gradeitem, $score, $userid) {
|
||||
$service = new gradebookservices();
|
||||
$service->save_grade_item($gradeitem, $score, $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a grade item.
|
||||
*
|
||||
* @param object $gradeitem Grade Item record
|
||||
* @param object $score Result object
|
||||
* @param int $userid User ID
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function save_grade_item($gradeitem, $score, $userid) {
|
||||
global $DB, $CFG;
|
||||
$source = 'mod' . self::SERVICE_NAME;
|
||||
$source = 'mod' . $this->get_component_id();
|
||||
if ($DB->get_record('user', array('id' => $userid)) === false) {
|
||||
throw new \Exception(null, 400);
|
||||
}
|
||||
@@ -406,11 +458,13 @@ class gradebookservices extends service_base {
|
||||
if ($gbs) {
|
||||
$lineitem->tag = (!empty($gbs->tag)) ? $gbs->tag : '';
|
||||
if (isset($gbs->ltilinkid)) {
|
||||
$lineitem->resourceLinkId = strval($gbs->ltilinkid);
|
||||
$lineitem->ltiLinkId = strval($gbs->ltilinkid);
|
||||
}
|
||||
} else {
|
||||
$lineitem->tag = '';
|
||||
if (isset($item->iteminstance)) {
|
||||
$lineitem->resourceLinkId = strval($item->iteminstance);
|
||||
$lineitem->ltiLinkId = strval($item->iteminstance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
*/
|
||||
|
||||
$string['alwaysgs'] = 'Use this service for grade sync and column management ';
|
||||
$string['grade_synchronization'] = 'IMS LTI Assignment and Grade Services';
|
||||
$string['grade_synchronization_help'] = 'Whether to use the IMS LTI Assignment and Grade Services to synchronise grades instead of the Basic Outcomes service.
|
||||
$string['ltiservice_gradebookservices'] = 'IMS LTI Assignment and Grade Services';
|
||||
$string['ltiservice_gradebookservices_help'] = 'Whether to use the IMS LTI Assignment and Grade Services to synchronise grades instead of the Basic Outcomes service.
|
||||
|
||||
* **Do not use this service** - Basic Outcomes features and configuration will be used
|
||||
* **Use this service for grade sync only** - The service will populate the grades in an already existing gradebook column, but it will not be able to create new columns
|
||||
@@ -40,5 +40,4 @@ $string['privacy:metadata:grade'] = 'The grade the user received in Moodle for t
|
||||
$string['privacy:metadata:maxgrade'] = 'The max grade that can be achieved for this LTI activity.';
|
||||
$string['privacy:metadata:timemodified'] = 'The last time the grade was updated';
|
||||
$string['privacy:metadata:userid'] = 'The ID of the user using the LTI consumer.';
|
||||
$string['servicename'] = 'LTI Assignment and Grade Services';
|
||||
$string['taskcleanup'] = 'LTI Assignment and Grade Services table cleanup';
|
||||
|
||||
@@ -54,7 +54,8 @@ class contextmemberships extends resource_base {
|
||||
$this->template = '/{context_type}/{context_id}/bindings/{tool_code}/memberships';
|
||||
$this->variables[] = 'ToolProxyBinding.memberships.url';
|
||||
$this->formats[] = 'application/vnd.ims.lis.v2.membershipcontainer+json';
|
||||
$this->methods[] = 'GET';
|
||||
$this->formats[] = 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json';
|
||||
$this->methods[] = self::HTTP_GET;
|
||||
|
||||
}
|
||||
|
||||
@@ -79,19 +80,23 @@ class contextmemberships extends resource_base {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!($course = $DB->get_record('course', array('id' => $params['context_id']), 'id', IGNORE_MISSING))) {
|
||||
if (!$this->check_tool($params['tool_code'], $response->get_request_data(),
|
||||
array(memberships::SCOPE_MEMBERSHIPS_READ))) {
|
||||
throw new \Exception(null, 401);
|
||||
}
|
||||
if (!($course = $DB->get_record('course', array('id' => $params['context_id']), 'id,shortname,fullname',
|
||||
IGNORE_MISSING))) {
|
||||
throw new \Exception("Not Found: Course {$params['context_id']} doesn't exist", 404);
|
||||
}
|
||||
if (!$this->get_service()->is_allowed_in_context($params['tool_code'], $course->id)) {
|
||||
throw new \Exception(null, 404);
|
||||
}
|
||||
if (!($context = \context_course::instance($course->id))) {
|
||||
throw new \Exception(null, 404);
|
||||
}
|
||||
if (!($tool = $DB->get_record('lti_types', array('id' => $params['tool_code']),
|
||||
'id,toolproxyid,enabledcapability,parameter', IGNORE_MISSING))) {
|
||||
throw new \Exception(null, 404);
|
||||
throw new \Exception("Not Found: Course instance {$course->id} doesn't exist", 404);
|
||||
}
|
||||
if (!empty($linkid)) {
|
||||
if (!($lti = $DB->get_record('lti', array('id' => $linkid), 'id,course,typeid,servicesalt', IGNORE_MISSING))) {
|
||||
throw new \Exception(null, 404);
|
||||
throw new \Exception("Not Found: LTI link {$linkid} doesn't exist", 404);
|
||||
}
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$cm = get_coursemodule_from_instance('lti', $linkid, $lti->course, false, MUST_EXIST);
|
||||
@@ -101,39 +106,15 @@ class contextmemberships extends resource_base {
|
||||
$modinfo = null;
|
||||
}
|
||||
}
|
||||
if ($tool->toolproxyid == 0) {
|
||||
if (!$this->check_type($params['tool_code'], $params['context_id'],
|
||||
'ToolProxyBinding.memberships.url:get', null)) {
|
||||
throw new \Exception(null, 403);
|
||||
}
|
||||
} else {
|
||||
$toolproxy = $DB->get_record('lti_tool_proxies', array('id' => $tool->toolproxyid), 'guid', IGNORE_MISSING);
|
||||
if (!$this->check_tool_proxy($toolproxy->guid, $response->get_request_data())) {
|
||||
throw new \Exception(null, 403);
|
||||
}
|
||||
}
|
||||
$json = memberships::get_users_json($this, $context, $course->id, $tool, $role, $limitfrom, $limitnum, $lti, $modinfo);
|
||||
|
||||
$response->set_content_type($this->formats[0]);
|
||||
$json = $this->get_service()->get_members_json($this, $context, $course, $role, $limitfrom, $limitnum, $lti,
|
||||
$modinfo, $response);
|
||||
|
||||
$response->set_body($json);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$response->set_code($e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get permissions from the config of the tool for that resource
|
||||
*
|
||||
* @param int $typeid
|
||||
* @return array with the permissions related to this resource by the $lti_type or null if none.
|
||||
*/
|
||||
public function get_permissions($typeid) {
|
||||
$tool = lti_get_type_type_config($typeid);
|
||||
if ($tool->ltiservice_memberships == '1') {
|
||||
return array('ToolProxyBinding.memberships.url:get');
|
||||
} else {
|
||||
return array();
|
||||
$response->set_reason($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,14 +135,8 @@ class contextmemberships extends resource_base {
|
||||
$this->params['context_type'] = 'CourseSection';
|
||||
}
|
||||
$this->params['context_id'] = $COURSE->id;
|
||||
|
||||
$id = optional_param('id', 0, PARAM_INT); // Course Module ID.
|
||||
if (!empty($id)) {
|
||||
$cm = get_coursemodule_from_id('lti', $id, 0, false, IGNORE_MISSING);
|
||||
$lti = $DB->get_record('lti', array('id' => $cm->instance), 'typeid', IGNORE_MISSING);
|
||||
if ($lti && !empty($lti->typeid)) {
|
||||
$this->params['tool_code'] = $lti->typeid;
|
||||
}
|
||||
if ($tool = $this->get_service()->get_type()) {
|
||||
$this->params['tool_code'] = $tool->id;
|
||||
}
|
||||
$value = str_replace('$ToolProxyBinding.memberships.url', parent::get_endpoint(), $value);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@ class linkmemberships extends resource_base {
|
||||
$limitfrom = 0;
|
||||
}
|
||||
|
||||
if (!$this->check_tool(null, $response->get_request_data(), memberships::SCOPE_MEMBERSHIPS_READ)) {
|
||||
$response->set_code(403);
|
||||
return;
|
||||
}
|
||||
if (empty($linkid)) {
|
||||
$response->set_code(404);
|
||||
return;
|
||||
@@ -87,19 +91,6 @@ class linkmemberships extends resource_base {
|
||||
$response->set_code(404);
|
||||
return;
|
||||
}
|
||||
$tool = $DB->get_record('lti_types', array('id' => $lti->typeid));
|
||||
if ($tool->toolproxyid == 0) { // We wil use the same permission for this and contextmembers.
|
||||
if (!$this->check_type($lti->typeid, $lti->course, 'ToolProxyBinding.memberships.url:get', null)) {
|
||||
$response->set_code(403);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$toolproxy = $DB->get_record('lti_tool_proxies', array('id' => $tool->toolproxyid));
|
||||
if (!$this->check_tool_proxy($toolproxy->guid, $response->get_request_data())) {
|
||||
$response->set_code(403);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!($course = $DB->get_record('course', array('id' => $lti->course), 'id', IGNORE_MISSING))) {
|
||||
$response->set_code(404);
|
||||
return;
|
||||
@@ -115,7 +106,8 @@ class linkmemberships extends resource_base {
|
||||
if ($info->is_available_for_all()) {
|
||||
$info = null;
|
||||
}
|
||||
$json = memberships::get_users_json($this, $context, $lti->course, $tool, $role, $limitfrom, $limitnum, $lti, $info);
|
||||
$json = $this->get_service()->get_members_json($this, $context, $lti->course, $role,
|
||||
$limitfrom, $limitnum, $lti, $info, $response);
|
||||
|
||||
$response->set_content_type($this->formats[0]);
|
||||
$response->set_body($json);
|
||||
|
||||
@@ -45,10 +45,6 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
const CONTEXT_ROLE_LEARNER = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner';
|
||||
/** Capability used to identify Instructors */
|
||||
const INSTRUCTOR_CAPABILITY = 'moodle/course:manageactivities';
|
||||
/** Name of LTI service component */
|
||||
const LTI_SERVICE_COMPONENT = 'ltiservice_memberships';
|
||||
/** Membership services enabled */
|
||||
const MEMBERSHIP_ENABLED = 1;
|
||||
/** Always include field */
|
||||
const ALWAYS_INCLUDE_FIELD = 1;
|
||||
/** Allow the instructor to decide if included */
|
||||
@@ -57,6 +53,8 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
const INSTRUCTOR_INCLUDED = 1;
|
||||
/** Instructor delegated and approved for include */
|
||||
const INSTRUCTOR_DELEGATE_INCLUDED = array(self::DELEGATE_TO_INSTRUCTOR && self::INSTRUCTOR_INCLUDED);
|
||||
/** Scope for reading membership data */
|
||||
const SCOPE_MEMBERSHIPS_READ = 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
@@ -65,7 +63,7 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
|
||||
parent::__construct();
|
||||
$this->id = 'memberships';
|
||||
$this->name = get_string('servicename', self::LTI_SERVICE_COMPONENT);
|
||||
$this->name = get_string($this->get_component_id(), $this->get_component_id());
|
||||
|
||||
}
|
||||
|
||||
@@ -86,6 +84,24 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scope(s) permitted for the tool relevant to this service.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_permitted_scopes() {
|
||||
|
||||
$scopes = array();
|
||||
$ok = !empty($this->get_type());
|
||||
if ($ok && isset($this->get_typeconfig()[$this->get_component_id()]) &&
|
||||
($this->get_typeconfig()[$this->get_component_id()] == parent::SERVICE_ENABLED)) {
|
||||
$scopes[] = self::SCOPE_MEMBERSHIPS_READ;
|
||||
}
|
||||
|
||||
return $scopes;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON for members.
|
||||
*
|
||||
@@ -101,8 +117,44 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
* for LTI instance (null if context-level request)
|
||||
*
|
||||
* @return string
|
||||
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
|
||||
* @see memberships::get_members_json($resource, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response)
|
||||
*/
|
||||
public static function get_users_json($resource, $context, $contextid, $tool, $role, $limitfrom, $limitnum, $lti, $info) {
|
||||
global $DB;
|
||||
|
||||
debugging('get_users_json() has been deprecated, ' .
|
||||
'please use memberships::get_members_json() instead.', DEBUG_DEVELOPER);
|
||||
|
||||
$course = $DB->get_record('course', array('id' => $contextid), 'id,shortname,fullname', IGNORE_MISSING);
|
||||
|
||||
$memberships = new memberships();
|
||||
$memberships->check_tool($tool->id, null, array(self::SCOPE_MEMBERSHIPS_READ));
|
||||
|
||||
$response = new \mod_lti\local\ltiservice\response();
|
||||
|
||||
$json = $memberships->get_members_json($resource, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response);
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON for members.
|
||||
*
|
||||
* @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request
|
||||
* @param \context_course $context Course context
|
||||
* @param \course $course Course
|
||||
* @param string $role User role requested (empty if none)
|
||||
* @param int $limitfrom Position of first record to be returned
|
||||
* @param int $limitnum Maximum number of records to be returned
|
||||
* @param object $lti LTI instance record
|
||||
* @param \core_availability\info_module $info Conditional availability information
|
||||
* for LTI instance (null if context-level request)
|
||||
* @param \mod_lti\local\ltiservice\response $response Response object for the request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_members_json($resource, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response) {
|
||||
|
||||
$withcapability = '';
|
||||
$exclude = array();
|
||||
@@ -117,18 +169,20 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
null, null, null, true));
|
||||
}
|
||||
}
|
||||
$users = get_enrolled_users($context, $withcapability, 0, 'u.*', null, $limitfrom, $limitnum, true);
|
||||
if (count($users) < $limitnum) {
|
||||
$limitfrom = 0;
|
||||
$limitnum = 0;
|
||||
$users = get_enrolled_users($context, $withcapability, 0, 'u.*', null, 0, 0, true);
|
||||
if (($response->get_accept() === 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json') ||
|
||||
(($response->get_accept() !== 'application/vnd.ims.lis.v2.membershipcontainer+json') &&
|
||||
($this->get_type()->ltiversion === LTI_VERSION_1P3))) {
|
||||
$json = $this->users_to_json($resource, $users, $course, $exclude, $limitfrom, $limitnum, $lti, $info, $response);
|
||||
} else {
|
||||
$json = $this->users_to_jsonld($resource, $users, $course->id, $exclude, $limitfrom, $limitnum, $lti, $info, $response);
|
||||
}
|
||||
$json = self::users_to_json($resource, $users, $contextid, $tool, $exclude, $limitfrom, $limitnum, $lti, $info);
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON representation of the users.
|
||||
* Get the JSON-LD representation of the users.
|
||||
*
|
||||
* Note that when a limit is set and the exclude array is not empty, then the number of memberships
|
||||
* returned may be less than the limit.
|
||||
@@ -136,34 +190,28 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
* @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request
|
||||
* @param array $users Array of user records
|
||||
* @param string $contextid Course ID
|
||||
* @param object $tool Tool instance object
|
||||
* @param array $exclude Array of user records to be excluded from the response
|
||||
* @param int $limitfrom Position of first record to be returned
|
||||
* @param int $limitnum Maximum number of records to be returned
|
||||
* @param object $lti LTI instance record
|
||||
* @param \core_availability\info_module $info Conditional availability information for LTI instance
|
||||
* @param \core_availability\info_module $info Conditional availability information
|
||||
* for LTI instance (null if context-level request)
|
||||
* @param \mod_lti\local\ltiservice\response $response Response object for the request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function users_to_json($resource, $users, $contextid, $tool, $exclude, $limitfrom, $limitnum,
|
||||
$lti, $info) {
|
||||
private function users_to_jsonld($resource, $users, $contextid, $exclude, $limitfrom, $limitnum,
|
||||
$lti, $info, $response) {
|
||||
global $DB;
|
||||
|
||||
$tool = $this->get_type();
|
||||
$toolconfig = $this->get_typeconfig();
|
||||
$arrusers = [
|
||||
'@context' => 'http://purl.imsglobal.org/ctx/lis/v2/MembershipContainer',
|
||||
'@type' => 'Page',
|
||||
'@id' => $resource->get_endpoint(),
|
||||
];
|
||||
|
||||
if ($limitnum > 0) {
|
||||
$limitfrom += $limitnum;
|
||||
$nextpage = "{$resource->get_endpoint()}?limit={$limitnum}&from={$limitfrom}";
|
||||
if (!is_null($lti)) {
|
||||
$nextpage .= "&rlid={$lti->id}";
|
||||
}
|
||||
$arrusers['nextPage'] = $nextpage;
|
||||
}
|
||||
|
||||
$arrusers['pageOf'] = [
|
||||
'@type' => 'LISMembershipContainer',
|
||||
'membershipSubject' => [
|
||||
@@ -175,6 +223,8 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
|
||||
$enabledcapabilities = lti_get_enabled_capabilities($tool);
|
||||
$islti2 = $tool->toolproxyid > 0;
|
||||
$n = 0;
|
||||
$more = false;
|
||||
foreach ($users as $user) {
|
||||
if (in_array($user->id, $exclude)) {
|
||||
continue;
|
||||
@@ -182,6 +232,16 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
if (!empty($info) && !$info->is_user_visible($info->get_course_module(), $user->id)) {
|
||||
continue;
|
||||
}
|
||||
$n++;
|
||||
if ($limitnum > 0) {
|
||||
if ($n <= $limitfrom) {
|
||||
continue;
|
||||
}
|
||||
if (count($arrusers['pageOf']['membershipSubject']['membership']) >= $limitnum) {
|
||||
$more = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$member = new \stdClass();
|
||||
$member->{"@type" } = 'LISPerson';
|
||||
@@ -189,13 +249,12 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
$membership->status = 'Active';
|
||||
$membership->role = explode(',', lti_get_ims_role($user->id, null, $contextid, true));
|
||||
|
||||
$toolconfig = lti_get_type_type_config($tool->id);
|
||||
$instanceconfig = null;
|
||||
if (!is_null($lti)) {
|
||||
$instanceconfig = lti_get_type_config_from_instance($lti->id);
|
||||
}
|
||||
$isallowedlticonfig = self::is_allowed_field_set($toolconfig, $instanceconfig,
|
||||
['name' => 'lti_sendname', 'email' => 'lti_sendemailaddr']);
|
||||
['name' => 'sendname', 'email' => 'sendemailaddr']);
|
||||
|
||||
$includedcapabilities = [
|
||||
'User.id' => ['type' => 'id',
|
||||
@@ -208,7 +267,7 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
'member.field' => 'name',
|
||||
'source.value' => format_string("{$user->firstname} {$user->lastname}")],
|
||||
'Person.name.given' => ['type' => 'name',
|
||||
'member.field' => 'givenName',
|
||||
'member.field' => 'giveName',
|
||||
'source.value' => format_string($user->firstname)],
|
||||
'Person.name.family' => ['type' => 'name',
|
||||
'member.field' => 'familyName',
|
||||
@@ -253,6 +312,161 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
|
||||
$arrusers['pageOf']['membershipSubject']['membership'][] = $membership;
|
||||
}
|
||||
if ($more) {
|
||||
$nextlimitfrom = $limitfrom + $limitnum;
|
||||
$nextpage = "{$resource->get_endpoint()}?limit={$limitnum}&from={$nextlimitfrom}";
|
||||
if (!is_null($lti)) {
|
||||
$nextpage .= "&rlid={$lti->id}";
|
||||
}
|
||||
$arrusers['nextPage'] = $nextpage;
|
||||
}
|
||||
|
||||
$response->set_content_type('application/vnd.ims.lis.v2.membershipcontainer+json');
|
||||
|
||||
return json_encode($arrusers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NRP service JSON representation of the users.
|
||||
*
|
||||
* Note that when a limit is set and the exclude array is not empty, then the number of memberships
|
||||
* returned may be less than the limit.
|
||||
*
|
||||
* @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request
|
||||
* @param array $users Array of user records
|
||||
* @param \course $course Course
|
||||
* @param array $exclude Array of user records to be excluded from the response
|
||||
* @param int $limitfrom Position of first record to be returned
|
||||
* @param int $limitnum Maximum number of records to be returned
|
||||
* @param object $lti LTI instance record
|
||||
* @param \core_availability\info_module $info Conditional availability information for LTI instance
|
||||
* @param \mod_lti\local\ltiservice\response $response Response object for the request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function users_to_json($resource, $users, $course, $exclude, $limitfrom, $limitnum,
|
||||
$lti, $info, $response) {
|
||||
global $DB, $CFG;
|
||||
|
||||
$tool = $this->get_type();
|
||||
$toolconfig = $this->get_typeconfig();
|
||||
|
||||
$context = new \stdClass();
|
||||
$context->id = $course->id;
|
||||
$context->label = trim(html_to_text($course->shortname, 0));
|
||||
$context->title = trim(html_to_text($course->fullname, 0));
|
||||
|
||||
$arrusers = [
|
||||
'id' => $resource->get_endpoint(),
|
||||
'context' => $context,
|
||||
'members' => []
|
||||
];
|
||||
|
||||
$islti2 = $tool->toolproxyid > 0;
|
||||
$n = 0;
|
||||
$more = false;
|
||||
foreach ($users as $user) {
|
||||
if (in_array($user->id, $exclude)) {
|
||||
continue;
|
||||
}
|
||||
if (!empty($info) && !$info->is_user_visible($info->get_course_module(), $user->id)) {
|
||||
continue;
|
||||
}
|
||||
$n++;
|
||||
if ($limitnum > 0) {
|
||||
if ($n <= $limitfrom) {
|
||||
continue;
|
||||
}
|
||||
if (count($arrusers['members']) >= $limitnum) {
|
||||
$more = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$member = new \stdClass();
|
||||
$member->status = 'Active';
|
||||
$member->roles = explode(',', lti_get_ims_role($user->id, null, $course->id, true));
|
||||
|
||||
$instanceconfig = null;
|
||||
if (!is_null($lti)) {
|
||||
$instanceconfig = lti_get_type_config_from_instance($lti->id);
|
||||
}
|
||||
if (!$islti2) {
|
||||
$isallowedlticonfig = self::is_allowed_field_set($toolconfig, $instanceconfig,
|
||||
['name' => 'sendname', 'givenname' => 'sendname', 'familyname' => 'sendname',
|
||||
'email' => 'sendemailaddr']);
|
||||
} else {
|
||||
$isallowedlticonfig = self::is_allowed_capability_set($tool,
|
||||
['name' => 'Person.name.full', 'givenname' => 'Person.name.given',
|
||||
'familyname' => 'Person.name.family', 'email' => 'Person.email.primary']);
|
||||
}
|
||||
$includedcapabilities = [
|
||||
'User.id' => ['type' => 'id',
|
||||
'member.field' => 'user_id',
|
||||
'source.value' => $user->id],
|
||||
'Person.sourcedId' => ['type' => 'id',
|
||||
'member.field' => 'lis_person_sourcedid',
|
||||
'source.value' => format_string($user->idnumber)],
|
||||
'Person.name.full' => ['type' => 'name',
|
||||
'member.field' => 'name',
|
||||
'source.value' => format_string("{$user->firstname} {$user->lastname}")],
|
||||
'Person.name.given' => ['type' => 'givenname',
|
||||
'member.field' => 'given_name',
|
||||
'source.value' => format_string($user->firstname)],
|
||||
'Person.name.family' => ['type' => 'familyname',
|
||||
'member.field' => 'family_name',
|
||||
'source.value' => format_string($user->lastname)],
|
||||
'Person.email.primary' => ['type' => 'email',
|
||||
'member.field' => 'email',
|
||||
'source.value' => format_string($user->email)]
|
||||
];
|
||||
|
||||
if (!is_null($lti)) {
|
||||
$message = new \stdClass();
|
||||
$message->{'https://purl.imsglobal.org/spec/lti/claim/message_type'} = 'LtiResourceLinkRequest';
|
||||
$conditions = array('courseid' => $course->id, 'itemtype' => 'mod',
|
||||
'itemmodule' => 'lti', 'iteminstance' => $lti->id);
|
||||
|
||||
if (!empty($lti->servicesalt) && $DB->record_exists('grade_items', $conditions)) {
|
||||
$basicoutcome = new \stdClass();
|
||||
$basicoutcome->lis_result_sourcedid = json_encode(lti_build_sourcedid($lti->id,
|
||||
$user->id,
|
||||
$lti->servicesalt,
|
||||
$lti->typeid));
|
||||
// Add outcome service URL.
|
||||
$serviceurl = new \moodle_url('/mod/lti/service.php');
|
||||
$serviceurl = $serviceurl->out();
|
||||
$forcessl = false;
|
||||
if (!empty($CFG->mod_lti_forcessl)) {
|
||||
$forcessl = true;
|
||||
}
|
||||
if ((isset($toolconfig['forcessl']) && ($toolconfig['forcessl'] == '1')) or $forcessl) {
|
||||
$serviceurl = lti_ensure_url_is_https($serviceurl);
|
||||
}
|
||||
$basicoutcome->lis_outcome_service_url = $serviceurl;
|
||||
$message->{'https://purl.imsglobal.org/spec/lti-bos/claim/basicoutcomesservice'} = $basicoutcome;
|
||||
}
|
||||
$member->message = [$message];
|
||||
}
|
||||
|
||||
foreach ($includedcapabilities as $capabilityname => $capability) {
|
||||
if (($capability['type'] === 'id') || $isallowedlticonfig[$capability['type']]) {
|
||||
$member->{$capability['member.field']} = $capability['source.value'];
|
||||
}
|
||||
}
|
||||
|
||||
$arrusers['members'][] = $member;
|
||||
}
|
||||
if ($more) {
|
||||
$nextlimitfrom = $limitfrom + $limitnum;
|
||||
$nextpage = "{$resource->get_endpoint()}?limit={$limitnum}&from={$nextlimitfrom}";
|
||||
if (!is_null($lti)) {
|
||||
$nextpage .= "&rlid={$lti->id}";
|
||||
}
|
||||
$response->add_additional_header("Link: {$nextpage};rel=next");
|
||||
}
|
||||
|
||||
$response->set_content_type('application/vnd.ims.lti-nrps.v2.membershipcontainer+json');
|
||||
|
||||
return json_encode($arrusers);
|
||||
}
|
||||
@@ -267,11 +481,11 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
private static function is_allowed_field_set($toolconfig, $instanceconfig, $fields) {
|
||||
$isallowedstate = [];
|
||||
foreach ($fields as $key => $field) {
|
||||
$allowed = self::ALWAYS_INCLUDE_FIELD == $toolconfig->{$field};
|
||||
if (!$allowed) {
|
||||
if (self::DELEGATE_TO_INSTRUCTOR == $toolconfig->{$field} && !is_null($instanceconfig)) {
|
||||
$allowed = $instanceconfig->{$field} == self::INSTRUCTOR_INCLUDED;
|
||||
}
|
||||
$allowed = isset($toolconfig[$field]) && (self::ALWAYS_INCLUDE_FIELD == $toolconfig[$field]);
|
||||
if (!$allowed && isset($toolconfig[$field]) && (self::DELEGATE_TO_INSTRUCTOR == $toolconfig[$field]) &&
|
||||
!is_null($instanceconfig)) {
|
||||
$allowed = isset($instanceconfig->{"lti_{$field}"}) &&
|
||||
($instanceconfig->{"lti_{$field}"} == self::INSTRUCTOR_INCLUDED);
|
||||
}
|
||||
$isallowedstate[$key] = $allowed;
|
||||
}
|
||||
@@ -279,31 +493,20 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds form elements for membership add/edit page.
|
||||
* Create form element for membership add/edit page.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return array of \HTML_QuickForm_element Form elements
|
||||
*/
|
||||
public function get_configuration_options(&$mform) {
|
||||
$elementname = 'ltiservice_memberships';
|
||||
public function get_configuration_elements() {
|
||||
$elements = array();
|
||||
$options = [
|
||||
get_string('notallow', self::LTI_SERVICE_COMPONENT),
|
||||
get_string('allow', self::LTI_SERVICE_COMPONENT)
|
||||
get_string('notallow', $this->get_component_id()),
|
||||
get_string('allow', $this->get_component_id())
|
||||
];
|
||||
$elements[''] = new \MoodleQuickForm_select($this->get_component_id(), get_string($this->get_component_id(),
|
||||
$this->get_component_id()), $options);
|
||||
|
||||
$mform->addElement('select', $elementname, get_string($elementname, self::LTI_SERVICE_COMPONENT), $options);
|
||||
$mform->setType($elementname, 'int');
|
||||
$mform->setDefault($elementname, 0);
|
||||
$mform->addHelpButton($elementname, $elementname, self::LTI_SERVICE_COMPONENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with the names of the parameters that the service will be saving in the configuration
|
||||
*
|
||||
* @return array with the names of the parameters that the service will be saving in the configuration
|
||||
*
|
||||
*/
|
||||
public function get_configuration_parameter_names() {
|
||||
return array(self::LTI_SERVICE_COMPONENT);
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,16 +528,10 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
|
||||
|
||||
$launchparameters = array();
|
||||
$tool = lti_get_type_type_config($typeid);
|
||||
if (isset($tool->ltiservice_memberships)) {
|
||||
if ($tool->ltiservice_memberships == '1' && $this->is_used_in_context($typeid, $courseid)) {
|
||||
$endpoint = $this->get_service_path();
|
||||
if ($COURSE->id === SITEID) {
|
||||
$contexttype = 'Group';
|
||||
} else {
|
||||
$contexttype = 'CourseSection';
|
||||
}
|
||||
$launchparameters['custom_context_memberships_url'] = $endpoint .
|
||||
"/{$contexttype}/{$courseid}/bindings/{$typeid}/memberships";
|
||||
if (isset($tool->{$this->get_component_id()})) {
|
||||
if ($tool->{$this->get_component_id()} == parent::SERVICE_ENABLED && $this->is_used_in_context($typeid, $courseid)) {
|
||||
$launchparameters['context_memberships_url'] = '$ToolProxyBinding.memberships.url';
|
||||
$launchparameters['context_memberships_versions'] = '1.0,2.0';
|
||||
}
|
||||
}
|
||||
return $launchparameters;
|
||||
|
||||
@@ -24,10 +24,14 @@
|
||||
*/
|
||||
|
||||
$string['allow'] = 'Use this service to retrieve members\' information as per privacy settings';
|
||||
$string['ltiservice_memberships'] = 'IMS LTI Membership: ';
|
||||
$string['ltiservice_memberships_help'] = 'Allow the tool to retrieve member\'s info from the course using the IMS LTI Membership Service. The privacy settings will apply.';
|
||||
$string['ltiservice_memberships'] = 'IMS LTI Names and Role Provisioning';
|
||||
$string['ltiservice_memberships_help'] = 'Allow the tool to retrieve member\'s info from the course ' .
|
||||
'using the IMS LTI Names and Role Provisioning Service. The privacy settings will apply. For course-level ' .
|
||||
'requests these will be based on the tool confguration settings. If you wish to always send such details ' .
|
||||
'do not delegate the choice to teachers. Link-level requests will always use the privacy settings which ' .
|
||||
'apply to the link.';
|
||||
$string['notallow'] = 'Do not use this service';
|
||||
$string['pluginname'] = 'Memberships LTI Service';
|
||||
$string['pluginname'] = 'Names and Role Provisioning LTI Service';
|
||||
$string['privacy:metadata:email'] = 'The email of the user using the LTI consumer.';
|
||||
$string['privacy:metadata:externalpurpose'] = 'This information is sent to an external LTI provider.';
|
||||
$string['privacy:metadata:firstname'] = 'The firstname of the user using the LTI consumer.';
|
||||
@@ -35,4 +39,3 @@ $string['privacy:metadata:fullname'] = 'The fullname of the user using the LTI c
|
||||
$string['privacy:metadata:lastname'] = 'The lastname of the user using the LTI consumer.';
|
||||
$string['privacy:metadata:userid'] = 'The ID of the user using the LTI consumer.';
|
||||
$string['privacy:metadata:useridnumber'] = 'The ID number of the user using the LTI consumer';
|
||||
$string['servicename'] = 'Memberships';
|
||||
|
||||
@@ -83,13 +83,15 @@ class profile extends \mod_lti\local\ltiservice\resource_base {
|
||||
|
||||
$version = service_base::LTI_VERSION2P0;
|
||||
$params = $this->parse_template();
|
||||
$ok = $this->get_service()->check_tool_proxy($params['tool_proxy_id']);
|
||||
if (!$ok) {
|
||||
$response->set_code(404);
|
||||
} else if (optional_param('lti_version', '', PARAM_ALPHANUMEXT) != $version) {
|
||||
if (optional_param('lti_version', service_base::LTI_VERSION2P0, PARAM_ALPHANUMEXT) != $version) {
|
||||
$ok = false;
|
||||
$response->set_code(400);
|
||||
} else {
|
||||
$toolproxy = $this->get_service()->get_tool_proxy();
|
||||
$toolproxy = lti_get_tool_proxy_from_guid($params['tool_proxy_id']);
|
||||
$ok = $toolproxy !== false;
|
||||
}
|
||||
if ($ok) {
|
||||
$this->get_service()->set_tool_proxy($toolproxy);
|
||||
$response->set_content_type($this->formats[0]);
|
||||
|
||||
$servicepath = $this->get_service()->get_service_path();
|
||||
@@ -111,7 +113,12 @@ class profile extends \mod_lti\local\ltiservice\resource_base {
|
||||
$formats = implode("\", \"", $resource->get_formats());
|
||||
$methods = implode("\", \"", $resource->get_methods());
|
||||
$capabilityofferedarr = array_merge($capabilityofferedarr, $resource->get_variables());
|
||||
$path = $servicepath . preg_replace('/\{?.*\}$/', '', $resource->get_path());
|
||||
$template = $resource->get_path();
|
||||
if (!empty($template)) {
|
||||
$path = $servicepath . preg_replace('/[\(\)]/', '', $template);
|
||||
} else {
|
||||
$path = $resource->get_endpoint();
|
||||
}
|
||||
$serviceoffered .= <<< EOD
|
||||
{$sep}
|
||||
{
|
||||
@@ -199,17 +206,6 @@ EOD;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource fully qualified endpoint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_endpoint() {
|
||||
|
||||
return parent::get_endpoint() . '?lti_version=' . service_base::LTI_VERSION2P0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a value for custom parameter substitution variables.
|
||||
*
|
||||
@@ -218,7 +214,7 @@ EOD;
|
||||
* @return string
|
||||
*/
|
||||
public function parse_value($value) {
|
||||
if (strpos($value, '$ToolConsumerProfile.url') !== false) {
|
||||
if (!empty($this->get_service()->get_tool_proxy()) && (strpos($value, '$ToolConsumerProfile.url') !== false)) {
|
||||
$value = str_replace('$ToolConsumerProfile.url', $this->get_endpoint(), $value);
|
||||
}
|
||||
return $value;
|
||||
|
||||
@@ -66,10 +66,12 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base {
|
||||
*/
|
||||
public function execute($response) {
|
||||
|
||||
$ok = $this->check_tool_proxy(null, $response->get_request_data());
|
||||
$ok = $this->check_tool(null, $response->get_request_data());
|
||||
$ok = $ok && ($this->get_service()->get_tool_proxy());
|
||||
if ($ok) {
|
||||
$toolproxy = $this->get_service()->get_tool_proxy();
|
||||
} else {
|
||||
}
|
||||
if (!$ok) {
|
||||
$toolproxy = null;
|
||||
$response->set_code(401);
|
||||
}
|
||||
@@ -118,6 +120,7 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base {
|
||||
}
|
||||
|
||||
// Check all services requested were offered (only tool services currently supported).
|
||||
$requestsbasicoutcomes = false;
|
||||
if ($ok && isset($toolproxyjson->security_contract->tool_service)) {
|
||||
$contexts = lti_get_contexts($toolproxyjson);
|
||||
$profileservice = lti_get_service_by_name('profile');
|
||||
@@ -129,6 +132,7 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base {
|
||||
$errors = array();
|
||||
foreach ($tpservices as $service) {
|
||||
$fqid = lti_get_fqid($contexts, $service->service);
|
||||
$requestsbasicoutcomes = $requestsbasicoutcomes || (substr($fqid, -13) === 'Outcomes.LTI1');
|
||||
if (substr($fqid, 0, strlen($context)) !== $context) {
|
||||
$errors[] = $service->service;
|
||||
} else {
|
||||
@@ -219,7 +223,15 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base {
|
||||
|
||||
$type = new \stdClass();
|
||||
$type->state = LTI_TOOL_STATE_PENDING;
|
||||
$type->ltiversion = LTI_VERSION_2;
|
||||
$type->toolproxyid = $toolproxy->id;
|
||||
// Ensure gradebook column is created.
|
||||
if ($requestsbasicoutcomes && !in_array('BasicOutcome.url', $launchrequest->enabled_capability)) {
|
||||
$launchrequest->enabled_capability[] = 'BasicOutcome.url';
|
||||
}
|
||||
if ($requestsbasicoutcomes && !in_array('BasicOutcome.sourcedId', $launchrequest->enabled_capability)) {
|
||||
$launchrequest->enabled_capability[] = 'BasicOutcome.sourcedId';
|
||||
}
|
||||
$type->enabledcapability = implode("\n", $launchrequest->enabled_capability);
|
||||
$type->parameter = self::lti_extract_parameters($launchrequest->parameter);
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class contextsettings extends \mod_lti\local\ltiservice\resource_base {
|
||||
|
||||
parent::__construct($service);
|
||||
$this->id = 'ToolProxyBindingSettings';
|
||||
$this->template = '/{context_type}/{context_id}/bindings/{vendor_code}/{product_code}';
|
||||
$this->template = '/{context_type}/{context_id}/bindings/{vendor_code}/{product_code}(/custom)';
|
||||
$this->variables[] = 'ToolProxyBinding.custom.url';
|
||||
$this->formats[] = 'application/vnd.ims.lti.v2.toolsettings+json';
|
||||
$this->formats[] = 'application/vnd.ims.lti.v2.toolsettings.simple+json';
|
||||
@@ -71,85 +71,106 @@ class contextsettings extends \mod_lti\local\ltiservice\resource_base {
|
||||
$vendorcode = $params['vendor_code'];
|
||||
$productcode = $params['product_code'];
|
||||
$bubble = optional_param('bubble', '', PARAM_ALPHA);
|
||||
$typeid = null;
|
||||
if (($vendorcode === 'tool') && is_numeric($productcode)) {
|
||||
$typeid = $productcode;
|
||||
}
|
||||
$ok = !empty($contexttype) && !empty($contextid) &&
|
||||
!empty($vendorcode) && !empty($productcode) &&
|
||||
$this->check_tool_proxy($productcode, $response->get_request_data());
|
||||
$this->check_tool($typeid, $response->get_request_data(),
|
||||
array(toolsettings::SCOPE_TOOL_SETTINGS));
|
||||
if (!$ok) {
|
||||
$response->set_code(401);
|
||||
}
|
||||
$contenttype = $response->get_accept();
|
||||
$simpleformat = !empty($contenttype) && ($contenttype == $this->formats[1]);
|
||||
if ($ok) {
|
||||
$ok = (empty($bubble) || ((($bubble == 'distinct') || ($bubble == 'all')))) &&
|
||||
(!$simpleformat || empty($bubble) || ($bubble != 'all')) &&
|
||||
(empty($bubble) || ($response->get_request_method() == 'GET'));
|
||||
}
|
||||
|
||||
if (!$ok) {
|
||||
$response->set_code(404);
|
||||
} else {
|
||||
$systemsetting = null;
|
||||
$contextsettings = lti_get_tool_settings($this->get_service()->get_tool_proxy()->id, $contextid);
|
||||
if (!empty($bubble)) {
|
||||
$systemsetting = new systemsettings($this->get_service());
|
||||
$systemsetting->params['tool_proxy_id'] = $productcode;
|
||||
$systemsettings = lti_get_tool_settings($this->get_service()->get_tool_proxy()->id);
|
||||
if ($bubble == 'distinct') {
|
||||
toolsettings::distinct_settings($systemsettings, $contextsettings, null);
|
||||
}
|
||||
$toolproxy = $this->get_service()->get_tool_proxy();
|
||||
if (!empty($toolproxy)) {
|
||||
$ok = $toolproxy->guid === $productcode;
|
||||
$typeid = null;
|
||||
$id = $toolproxy->id;
|
||||
} else {
|
||||
$systemsettings = null;
|
||||
$ok = $vendorcode === 'tool';
|
||||
$typeid = intval($productcode);
|
||||
$id = -$typeid;
|
||||
}
|
||||
if ($response->get_request_method() == 'GET') {
|
||||
$json = '';
|
||||
if ($simpleformat) {
|
||||
$response->set_content_type($this->formats[1]);
|
||||
$json .= "{";
|
||||
$contenttype = $response->get_accept();
|
||||
$simpleformat = !empty($contenttype) && ($contenttype == $this->formats[1]);
|
||||
if ($ok) {
|
||||
$ok = (empty($bubble) || ((($bubble == 'distinct') || ($bubble == 'all')))) &&
|
||||
(!$simpleformat || empty($bubble) || ($bubble != 'all')) &&
|
||||
(empty($bubble) || ($response->get_request_method() == 'GET'));
|
||||
}
|
||||
|
||||
if (!$ok) {
|
||||
$response->set_code(404);
|
||||
} else {
|
||||
$systemsetting = null;
|
||||
$contextsettings = lti_get_tool_settings($id, $contextid);
|
||||
if (!empty($bubble)) {
|
||||
$systemsetting = new systemsettings($this->get_service());
|
||||
$systemsetting->params['tool_proxy_id'] = $productcode;
|
||||
if ($id >= 0) {
|
||||
$systemsetting->params['config_type'] = 'toolproxy';
|
||||
} else {
|
||||
$systemsetting->params['config_type'] = 'tool';
|
||||
}
|
||||
$systemsettings = lti_get_tool_settings($id);
|
||||
if ($bubble == 'distinct') {
|
||||
toolsettings::distinct_settings($systemsettings, $contextsettings, null);
|
||||
}
|
||||
} else {
|
||||
$response->set_content_type($this->formats[0]);
|
||||
$json .= "{\n \"@context\":\"http://purl.imsglobal.org/ctx/lti/v2/ToolSettings\",\n \"@graph\":[\n";
|
||||
$systemsettings = null;
|
||||
}
|
||||
$settings = toolsettings::settings_to_json($systemsettings, $simpleformat, 'ToolProxy', $systemsetting);
|
||||
$json .= $settings;
|
||||
$isfirst = strlen($settings) <= 0;
|
||||
$settings = toolsettings::settings_to_json($contextsettings, $simpleformat, 'ToolProxyBinding', $this);
|
||||
if ((strlen($settings) > 0) && !$isfirst) {
|
||||
$json .= ",";
|
||||
}
|
||||
$json .= $settings;
|
||||
if ($simpleformat) {
|
||||
$json .= "\n}";
|
||||
} else {
|
||||
$json .= "\n ]\n}";
|
||||
}
|
||||
$response->set_body($json);
|
||||
} else { // PUT.
|
||||
$settings = null;
|
||||
if ($response->get_content_type() == $this->formats[0]) {
|
||||
$json = json_decode($response->get_request_data());
|
||||
$ok = !empty($json);
|
||||
if ($ok) {
|
||||
$ok = isset($json->{"@graph"}) && is_array($json->{"@graph"}) && (count($json->{"@graph"}) == 1) &&
|
||||
($json->{"@graph"}[0]->{"@type"} == 'ToolProxyBinding');
|
||||
if ($response->get_request_method() == 'GET') {
|
||||
$json = '';
|
||||
if ($simpleformat) {
|
||||
$response->set_content_type($this->formats[1]);
|
||||
$json .= "{";
|
||||
} else {
|
||||
$response->set_content_type($this->formats[0]);
|
||||
$json .= "{\n \"@context\":\"http://purl.imsglobal.org/ctx/lti/v2/ToolSettings\",\n \"@graph\":[\n";
|
||||
}
|
||||
$settings = toolsettings::settings_to_json($systemsettings, $simpleformat, 'ToolProxy', $systemsetting);
|
||||
$json .= $settings;
|
||||
$isfirst = strlen($settings) <= 0;
|
||||
$settings = toolsettings::settings_to_json($contextsettings, $simpleformat, 'ToolProxyBinding', $this);
|
||||
if ((strlen($settings) > 0) && !$isfirst) {
|
||||
$json .= ",";
|
||||
}
|
||||
$json .= $settings;
|
||||
if ($simpleformat) {
|
||||
$json .= "\n}";
|
||||
} else {
|
||||
$json .= "\n ]\n}";
|
||||
}
|
||||
$response->set_body($json);
|
||||
} else { // PUT.
|
||||
$settings = null;
|
||||
if ($response->get_content_type() == $this->formats[0]) {
|
||||
$json = json_decode($response->get_request_data());
|
||||
$ok = !empty($json);
|
||||
if ($ok) {
|
||||
$ok = isset($json->{"@graph"}) && is_array($json->{"@graph"}) && (count($json->{"@graph"}) == 1) &&
|
||||
($json->{"@graph"}[0]->{"@type"} == 'ToolProxyBinding');
|
||||
}
|
||||
if ($ok) {
|
||||
$settings = $json->{"@graph"}[0]->custom;
|
||||
unset($settings->{'@id'});
|
||||
}
|
||||
} else { // Simple JSON.
|
||||
$json = json_decode($response->get_request_data(), true);
|
||||
$ok = !empty($json);
|
||||
if ($ok) {
|
||||
$ok = is_array($json);
|
||||
}
|
||||
if ($ok) {
|
||||
$settings = $json;
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
$settings = $json->{"@graph"}[0]->custom;
|
||||
unset($settings->{'@id'});
|
||||
lti_set_tool_settings($settings, $id, $contextid);
|
||||
} else {
|
||||
$response->set_code(406);
|
||||
}
|
||||
} else { // Simple JSON.
|
||||
$json = json_decode($response->get_request_data(), true);
|
||||
$ok = !empty($json);
|
||||
if ($ok) {
|
||||
$ok = is_array($json);
|
||||
}
|
||||
if ($ok) {
|
||||
$settings = $json;
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
lti_set_tool_settings($settings, $this->get_service()->get_tool_proxy()->id, $contextid);
|
||||
} else {
|
||||
$response->set_code(406);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,8 +193,13 @@ class contextsettings extends \mod_lti\local\ltiservice\resource_base {
|
||||
$this->params['context_type'] = 'CourseSection';
|
||||
}
|
||||
$this->params['context_id'] = $COURSE->id;
|
||||
$this->params['vendor_code'] = $this->get_service()->get_tool_proxy()->vendorcode;
|
||||
$this->params['product_code'] = $this->get_service()->get_tool_proxy()->guid;
|
||||
if (!empty($this->get_service()->get_tool_proxy())) {
|
||||
$this->params['vendor_code'] = $this->get_service()->get_tool_proxy()->vendorcode;
|
||||
$this->params['product_code'] = $this->get_service()->get_tool_proxy()->guid;
|
||||
} else {
|
||||
$this->params['vendor_code'] = 'tool';
|
||||
$this->params['product_code'] = $this->get_service()->get_type()->id;
|
||||
}
|
||||
$value = str_replace('$ToolProxyBinding.custom.url', parent::get_endpoint(), $value);
|
||||
}
|
||||
return $value;
|
||||
|
||||
@@ -48,7 +48,7 @@ class linksettings extends \mod_lti\local\ltiservice\resource_base {
|
||||
|
||||
parent::__construct($service);
|
||||
$this->id = 'LtiLinkSettings';
|
||||
$this->template = '/links/{link_id}';
|
||||
$this->template = '/links/{link_id}(/custom)';
|
||||
$this->variables[] = 'LtiLink.custom.url';
|
||||
$this->formats[] = 'application/vnd.ims.lti.v2.toolsettings+json';
|
||||
$this->formats[] = 'application/vnd.ims.lti.v2.toolsettings.simple+json';
|
||||
@@ -72,7 +72,7 @@ class linksettings extends \mod_lti\local\ltiservice\resource_base {
|
||||
$simpleformat = !empty($contenttype) && ($contenttype == $this->formats[1]);
|
||||
$ok = (empty($bubble) || ((($bubble == 'distinct') || ($bubble == 'all')))) &&
|
||||
(!$simpleformat || empty($bubble) || ($bubble != 'all')) &&
|
||||
(empty($bubble) || ($response->get_request_method() == 'GET'));
|
||||
(empty($bubble) || ($response->get_request_method() == self::HTTP_GET));
|
||||
if (!$ok) {
|
||||
$response->set_code(406);
|
||||
}
|
||||
@@ -84,38 +84,51 @@ class linksettings extends \mod_lti\local\ltiservice\resource_base {
|
||||
$ok = !empty($linkid);
|
||||
if ($ok) {
|
||||
$lti = $DB->get_record('lti', array('id' => $linkid), 'course,typeid', MUST_EXIST);
|
||||
$ltitype = $DB->get_record('lti_types', array('id' => $lti->typeid));
|
||||
$toolproxy = $DB->get_record('lti_tool_proxies', array('id' => $ltitype->toolproxyid));
|
||||
$ok = $this->check_tool_proxy($toolproxy->guid, $response->get_request_data());
|
||||
$ok = $this->check_tool($lti->typeid, $response->get_request_data(),
|
||||
array(toolsettings::SCOPE_TOOL_SETTINGS));
|
||||
}
|
||||
if (!$ok) {
|
||||
$response->set_code(401);
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
$linksettings = lti_get_tool_settings($this->get_service()->get_tool_proxy()->id, $lti->course, $linkid);
|
||||
if (!empty($bubble)) {
|
||||
$contextsetting = new contextsettings($this->get_service());
|
||||
if ($COURSE == 'site') {
|
||||
$contextsetting->params['context_type'] = 'Group';
|
||||
} else {
|
||||
$contextsetting->params['context_type'] = 'CourseSection';
|
||||
}
|
||||
$contextsetting->params['context_id'] = $lti->course;
|
||||
$contextsetting->params['vendor_code'] = $this->get_service()->get_tool_proxy()->vendorcode;
|
||||
$contextsetting->params['product_code'] = $this->get_service()->get_tool_proxy()->id;
|
||||
$contextsettings = lti_get_tool_settings($this->get_service()->get_tool_proxy()->id, $lti->course);
|
||||
$systemsetting = new systemsettings($this->get_service());
|
||||
$systemsetting->params['tool_proxy_id'] = $this->get_service()->get_tool_proxy()->id;
|
||||
$systemsettings = lti_get_tool_settings($this->get_service()->get_tool_proxy()->id);
|
||||
if ($bubble == 'distinct') {
|
||||
toolsettings::distinct_settings($systemsettings, $contextsettings, $linksettings);
|
||||
}
|
||||
if (!empty($this->get_service()->get_tool_proxy())) {
|
||||
$id = $this->get_service()->get_tool_proxy()->id;
|
||||
} else {
|
||||
$contextsettings = null;
|
||||
$systemsettings = null;
|
||||
$id = -$this->get_service()->get_type()->id;
|
||||
}
|
||||
if ($response->get_request_method() == 'GET') {
|
||||
$linksettings = lti_get_tool_settings($id, $lti->course, $linkid);
|
||||
if (!empty($bubble)) {
|
||||
$contextsetting = new contextsettings($this->get_service());
|
||||
if ($COURSE == 'site') {
|
||||
$contextsetting->params['context_type'] = 'Group';
|
||||
} else {
|
||||
$contextsetting->params['context_type'] = 'CourseSection';
|
||||
}
|
||||
$contextsetting->params['context_id'] = $lti->course;
|
||||
if ($id >= 0) {
|
||||
$contextsetting->params['vendor_code'] = $this->get_service()->get_tool_proxy()->vendorcode;
|
||||
} else {
|
||||
$contextsetting->params['vendor_code'] = 'tool';
|
||||
}
|
||||
$contextsetting->params['product_code'] = abs($id);
|
||||
$contextsettings = lti_get_tool_settings($id, $lti->course);
|
||||
$systemsetting = new systemsettings($this->get_service());
|
||||
if ($id >= 0) {
|
||||
$systemsetting->params['config_type'] = 'toolproxy';
|
||||
} else {
|
||||
$systemsetting->params['config_type'] = 'tool';
|
||||
}
|
||||
$systemsetting->params['tool_proxy_id'] = abs($id);
|
||||
$systemsettings = lti_get_tool_settings($id);
|
||||
if ($bubble == 'distinct') {
|
||||
toolsettings::distinct_settings($systemsettings, $contextsettings, $linksettings);
|
||||
}
|
||||
} else {
|
||||
$contextsettings = null;
|
||||
$systemsettings = null;
|
||||
}
|
||||
$json = '';
|
||||
if ($simpleformat) {
|
||||
$response->set_content_type($this->formats[1]);
|
||||
@@ -176,7 +189,7 @@ class linksettings extends \mod_lti\local\ltiservice\resource_base {
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
lti_set_tool_settings($settings, $this->get_service()->get_tool_proxy()->id, $lti->course, $linkid);
|
||||
lti_set_tool_settings($settings, $id, $lti->course, $linkid);
|
||||
} else {
|
||||
$response->set_code(406);
|
||||
}
|
||||
|
||||
@@ -50,12 +50,12 @@ class systemsettings extends resource_base {
|
||||
|
||||
parent::__construct($service);
|
||||
$this->id = 'ToolProxySettings';
|
||||
$this->template = '/toolproxy/{tool_proxy_id}';
|
||||
$this->template = '/{config_type}/{tool_proxy_id}(/custom)';
|
||||
$this->variables[] = 'ToolProxy.custom.url';
|
||||
$this->formats[] = 'application/vnd.ims.lti.v2.toolsettings+json';
|
||||
$this->formats[] = 'application/vnd.ims.lti.v2.toolsettings.simple+json';
|
||||
$this->methods[] = 'GET';
|
||||
$this->methods[] = 'PUT';
|
||||
$this->methods[] = self::HTTP_GET;
|
||||
$this->methods[] = self::HTTP_PUT;
|
||||
|
||||
}
|
||||
|
||||
@@ -68,10 +68,27 @@ class systemsettings extends resource_base {
|
||||
|
||||
$params = $this->parse_template();
|
||||
$tpid = $params['tool_proxy_id'];
|
||||
$bubble = optional_param('bubble', '', PARAM_ALPHA);
|
||||
$ok = !empty($tpid) && $this->check_tool_proxy($tpid, $response->get_request_data());
|
||||
$configtype = $params['config_type'];
|
||||
$ok = (in_array($configtype, array('toolproxy', 'tool')));
|
||||
if ($ok) {
|
||||
$typeid = null;
|
||||
if (($configtype === 'tool') && is_numeric($tpid)) {
|
||||
$typeid = $tpid;
|
||||
}
|
||||
$bubble = optional_param('bubble', '', PARAM_ALPHA);
|
||||
$ok = !empty($tpid) && $this->check_tool($typeid, $response->get_request_data(),
|
||||
array(toolsettings::SCOPE_TOOL_SETTINGS));
|
||||
}
|
||||
if (!$ok) {
|
||||
$response->set_code(401);
|
||||
} else if (!empty($this->get_service()->get_tool_proxy())) {
|
||||
$ok = ($this->get_service()->get_tool_proxy()->guid === $tpid);
|
||||
$id = $this->get_service()->get_tool_proxy()->id;
|
||||
} else if (!empty($typeid)) {
|
||||
$id = -$typeid;
|
||||
} else {
|
||||
$ok = false;
|
||||
$response->set_code(404);
|
||||
}
|
||||
$contenttype = $response->get_accept();
|
||||
$simpleformat = !empty($contenttype) && ($contenttype == $this->formats[1]);
|
||||
@@ -85,7 +102,7 @@ class systemsettings extends resource_base {
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
$systemsettings = lti_get_tool_settings($this->get_service()->get_tool_proxy()->id);
|
||||
$systemsettings = lti_get_tool_settings($id);
|
||||
if ($response->get_request_method() == 'GET') {
|
||||
$json = '';
|
||||
if ($simpleformat) {
|
||||
@@ -127,7 +144,7 @@ class systemsettings extends resource_base {
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
lti_set_tool_settings($settings, $this->get_service()->get_tool_proxy()->id);
|
||||
lti_set_tool_settings($settings, $id);
|
||||
} else {
|
||||
$response->set_code(406);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class toolsettings extends \mod_lti\local\ltiservice\service_base {
|
||||
|
||||
/** Scope for managing tool settings */
|
||||
const SCOPE_TOOL_SETTINGS = 'https://purl.imsglobal.org/spec/lti-ts/scope/toolsetting';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
@@ -67,6 +70,24 @@ class toolsettings extends \mod_lti\local\ltiservice\service_base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scope(s) permitted for the tool relevant to this service.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_permitted_scopes() {
|
||||
|
||||
$scopes = array();
|
||||
$ok = !empty($this->get_type());
|
||||
if ($ok && isset($this->get_typeconfig()[$this->get_component_id()]) &&
|
||||
($this->get_typeconfig()[$this->get_component_id()] == parent::SERVICE_ENABLED)) {
|
||||
$scopes[] = self::SCOPE_TOOL_SETTINGS;
|
||||
}
|
||||
|
||||
return $scopes;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distinct settings from each level by removing any duplicates from higher levels.
|
||||
*
|
||||
@@ -111,11 +132,10 @@ class toolsettings extends \mod_lti\local\ltiservice\service_base {
|
||||
if (!$simpleformat) {
|
||||
$json .= " {\n \"@type\":\"{$type}\",\n";
|
||||
$json .= " \"@id\":\"{$resource->get_endpoint()}\",\n";
|
||||
$json .= " \"custom\":{\n";
|
||||
$json .= " \"@id\":\"{$resource->get_endpoint()}/custom\"";
|
||||
$json .= " \"custom\":{";
|
||||
$indent = ' ';
|
||||
}
|
||||
$isfirst = $simpleformat;
|
||||
$isfirst = true;
|
||||
if (!empty($settings)) {
|
||||
foreach ($settings as $key => $value) {
|
||||
if (!$isfirst) {
|
||||
@@ -135,4 +155,52 @@ class toolsettings extends \mod_lti\local\ltiservice\service_base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create form element for membership add/edit page.
|
||||
*
|
||||
* @return array of \HTML_QuickForm_element Form elements
|
||||
*/
|
||||
public function get_configuration_elements() {
|
||||
$elements = array();
|
||||
$options = [
|
||||
get_string('notallow', $this->get_component_id()),
|
||||
get_string('allow', $this->get_component_id())
|
||||
];
|
||||
$elements[''] = new \MoodleQuickForm_select($this->get_component_id(), get_string($this->get_component_id(),
|
||||
$this->get_component_id()), $options);
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of key/values to add to the launch parameters.
|
||||
*
|
||||
* @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
|
||||
* @param string $courseid The course id.
|
||||
* @param string $user The user id.
|
||||
* @param string $typeid The tool lti type id.
|
||||
* @param string $modlti The id of the lti activity.
|
||||
*
|
||||
* The type is passed to check the configuration
|
||||
* and not return parameters for services not used.
|
||||
*
|
||||
* @return array of key/value pairs to add as launch parameters.
|
||||
*/
|
||||
public function get_launch_parameters($messagetype, $courseid, $user, $typeid, $modlti = null) {
|
||||
global $COURSE;
|
||||
|
||||
$launchparameters = array();
|
||||
$tool = lti_get_type_type_config($typeid);
|
||||
if (isset($tool->{$this->get_component_id()})) {
|
||||
if ($tool->{$this->get_component_id()} == self::SERVICE_ENABLED && $this->is_used_in_context($typeid, $courseid)) {
|
||||
$launchparameters['system_setting_url'] = '$ToolProxy.custom.url';
|
||||
$launchparameters['context_setting_url'] = '$ToolProxyBinding.custom.url';
|
||||
if ($messagetype === 'basic-lti-launch-request') {
|
||||
$launchparameters['link_setting_url'] = '$LtiLink.custom.url';
|
||||
}
|
||||
}
|
||||
}
|
||||
return $launchparameters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,5 +23,9 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['allow'] = 'Use this service';
|
||||
$string['ltiservice_toolsettings'] = 'Tool Settings';
|
||||
$string['ltiservice_toolsettings_help'] = 'Allow the tool to save and retrieve setting values.';
|
||||
$string['notallow'] = 'Do not use this service';
|
||||
$string['pluginname'] = 'Tool Settings Service';
|
||||
$string['privacy:metadata'] = 'The Tool Settings Service plugin does not store any personal data.';
|
||||
|
||||
+10
-2
@@ -71,7 +71,9 @@ function lti_parse_message_id($xml) {
|
||||
function lti_parse_grade_replace_message($xml) {
|
||||
$node = $xml->imsx_POXBody->replaceResultRequest->resultRecord->sourcedGUID->sourcedId;
|
||||
$resultjson = json_decode((string)$node);
|
||||
|
||||
if ( is_null($resultjson) ) {
|
||||
throw new Exception('Invalid sourcedId in result message');
|
||||
}
|
||||
$node = $xml->imsx_POXBody->replaceResultRequest->resultRecord->result->resultScore->textString;
|
||||
|
||||
$score = (string) $node;
|
||||
@@ -100,6 +102,9 @@ function lti_parse_grade_replace_message($xml) {
|
||||
function lti_parse_grade_read_message($xml) {
|
||||
$node = $xml->imsx_POXBody->readResultRequest->resultRecord->sourcedGUID->sourcedId;
|
||||
$resultjson = json_decode((string)$node);
|
||||
if ( is_null($resultjson) ) {
|
||||
throw new Exception('Invalid sourcedId in result message');
|
||||
}
|
||||
|
||||
$parsed = new stdClass();
|
||||
$parsed->instanceid = $resultjson->data->instanceid;
|
||||
@@ -116,6 +121,9 @@ function lti_parse_grade_read_message($xml) {
|
||||
function lti_parse_grade_delete_message($xml) {
|
||||
$node = $xml->imsx_POXBody->deleteResultRequest->resultRecord->sourcedGUID->sourcedId;
|
||||
$resultjson = json_decode((string)$node);
|
||||
if ( is_null($resultjson) ) {
|
||||
throw new Exception('Invalid sourcedId in result message');
|
||||
}
|
||||
|
||||
$parsed = new stdClass();
|
||||
$parsed->instanceid = $resultjson->data->instanceid;
|
||||
@@ -146,7 +154,7 @@ function lti_accepts_grades($ltiinstance) {
|
||||
}
|
||||
} else {
|
||||
$enabledcapabilities = explode("\n", $ltitype->enabledcapability);
|
||||
$acceptsgrades = in_array('Result.autocreate', $enabledcapabilities);
|
||||
$acceptsgrades = in_array('Result.autocreate', $enabledcapabilities) || in_array('BasicOutcome.url', $enabledcapabilities);
|
||||
}
|
||||
|
||||
return $acceptsgrades;
|
||||
|
||||
@@ -32,19 +32,20 @@ require_once($CFG->dirroot . '/mod/lti/locallib.php');
|
||||
|
||||
$response = new \mod_lti\local\ltiservice\response();
|
||||
|
||||
$isget = $response->get_request_method() == 'GET';
|
||||
$isdelete = $response->get_request_method() == 'DELETE';
|
||||
$isget = $response->get_request_method() === mod_lti\local\ltiservice\resource_base::HTTP_GET;
|
||||
$isdelete = $response->get_request_method() === mod_lti\local\ltiservice\resource_base::HTTP_DELETE;
|
||||
|
||||
if ($isget) {
|
||||
$response->set_accept(isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : '');
|
||||
} else {
|
||||
$response->set_content_type(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '');
|
||||
$response->set_content_type(isset($_SERVER['CONTENT_TYPE']) ? explode(';', $_SERVER['CONTENT_TYPE'], 2)[0] : '');
|
||||
}
|
||||
|
||||
$ok = false;
|
||||
$path = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
|
||||
|
||||
$accept = $response->get_accept();
|
||||
$contenttype = $response->get_content_type();
|
||||
|
||||
$services = lti_get_services();
|
||||
foreach ($services as $service) {
|
||||
@@ -52,14 +53,15 @@ foreach ($services as $service) {
|
||||
foreach ($resources as $resource) {
|
||||
if (($isget && !empty($accept) && (strpos($accept, '*/*') === false) &&
|
||||
!in_array($accept, $resource->get_formats())) ||
|
||||
((!$isget && !$isdelete) && !in_array($response->get_content_type(), $resource->get_formats()))) {
|
||||
((!$isget && !$isdelete) && !in_array($contenttype, $resource->get_formats()))) {
|
||||
continue;
|
||||
}
|
||||
$template = $resource->get_template();
|
||||
$template = preg_replace('/{config_type}/', '(toolproxy|tool)', $template);
|
||||
$template = preg_replace('/\{[a-zA-Z_]+\}/', '[^/]+', $template);
|
||||
$template = preg_replace('/\{\?[0-9a-zA-Z_\-,]+\}$/', '', $template);
|
||||
$template = preg_replace('/\(([0-9a-zA-Z_\-,\/]+)\)/', '(\\1|)', $template);
|
||||
$template = str_replace('/', '\/', $template);
|
||||
if (preg_match("/{$template}/", $path) === 1) {
|
||||
if (preg_match("/^{$template}$/", $path) === 1) {
|
||||
$ok = true;
|
||||
break 2;
|
||||
}
|
||||
@@ -67,6 +69,7 @@ foreach ($services as $service) {
|
||||
}
|
||||
if (!$ok) {
|
||||
$response->set_code(400);
|
||||
$response->set_reason("No handler found for {$path} {$accept} {$contenttype}");
|
||||
} else {
|
||||
$body = file_get_contents('php://input');
|
||||
$response->set_request_data($body);
|
||||
|
||||
@@ -35,12 +35,28 @@
|
||||
* urls
|
||||
** course
|
||||
** icon
|
||||
** publickeyset
|
||||
** accesstoken
|
||||
** authrequest
|
||||
* name
|
||||
* description
|
||||
* platformid
|
||||
* clientid
|
||||
* deploymentid
|
||||
* instancecount
|
||||
|
||||
}}
|
||||
<div class="tool-card" data-type-id="{{id}}">
|
||||
<div class="tool-card" data-type-id="{{id}}" data-uniqid="{{uniqid}}"
|
||||
data-modaltitle="{{#str}} tooldetailsmodaltitle, mod_lti {{/str}}"
|
||||
data-mailtosubject="{{#str}} tooldetailsmailtosubject, mod_lti {{/str}}"
|
||||
data-platformidstr="{{#str}} tooldetailsplatformid, mod_lti {{/str}}"
|
||||
data-clientidstr="{{#str}} tooldetailsclientid, mod_lti {{/str}}"
|
||||
data-deploymentidstr="{{#str}} tooldetailsdeploymentid, mod_lti {{/str}}"
|
||||
data-publickeyseturlstr="{{#str}} tooldetailspublickeyseturl, mod_lti {{/str}}"
|
||||
data-accesstokenurlstr="{{#str}} tooldetailsaccesstokenurl, mod_lti {{/str}}"
|
||||
data-authrequesturlstr="{{#str}} tooldetailsauthrequesturl, mod_lti {{/str}}"
|
||||
data-platformid="{{platformid}}" data-clientid="{{clientid}}" data-deploymentid="{{deploymentid}}"
|
||||
data-publickeyseturl="{{urls.publickeyset}}" data-accesstokenurl="{{urls.accesstoken}}" data-authrequesturl="{{urls.authrequest}}">
|
||||
<div class="overlay-container">
|
||||
<div class="img-container">
|
||||
{{> mod_lti/loader }}
|
||||
@@ -65,6 +81,9 @@
|
||||
{{#courseid}}
|
||||
<a href="{{{urls.course}}}" title="{{#str}} courselink, mod_lti {{/str}}">{{#pix}} i/course, core, {{#str}} courselink, mod_lti {{/str}}{{/pix}}</a>
|
||||
{{/courseid}}
|
||||
{{#clientid}}
|
||||
<a class="viewdetails" id="{{uniqid}}-{{deploymentid}}" href="#" title="{{#str}} tooldetailsmodallink, mod_lti {{/str}}">{{#pix}} t/viewdetails, core, {{#str}} tooldetailsmodallink, mod_lti {{/str}}{{/pix}}</a>
|
||||
{{/clientid}}
|
||||
<a class="edit" href="{{{urls.edit}}}" title="{{#str}} edit {{/str}}">{{#pix}} t/edit, core, {{#str}} edit {{/str}}{{/pix}}</a>
|
||||
<a class="delete" href="#" title="{{#str}} delete {{/str}}">{{#pix}} t/delete, core, {{#str}} delete {{/str}}{{/pix}}</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template mod_lti/tool_config_modal_body
|
||||
|
||||
This template provides the layout for the body of the configuration summary modal for
|
||||
a single tool opened from the tool configuration page.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* platformid
|
||||
* clientid
|
||||
* deploymentid
|
||||
* urls
|
||||
** publickeyset
|
||||
** accesstoken
|
||||
** authrequest
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"platformid": "htps://...",
|
||||
"clientid": "123",
|
||||
"deploymentid": "1",
|
||||
"urls": {
|
||||
"publickeyset": "https://...",
|
||||
"accesstoken": "https://...",
|
||||
"authrequest": "https://..."
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
<ul>
|
||||
<li><strong>{{#str}} tooldetailsplatformid, mod_lti {{/str}}:</strong> {{platformid}}</li>
|
||||
<li><strong>{{#str}} tooldetailsclientid, mod_lti {{/str}}:</strong> {{clientid}}</li>
|
||||
<li><strong>{{#str}} tooldetailsdeploymentid, mod_lti {{/str}}:</strong> {{deploymentid}}</li>
|
||||
<li><strong>{{#str}} tooldetailspublickeyseturl, mod_lti {{/str}}:</strong> {{urls.publickeyset}}</li>
|
||||
<li><strong>{{#str}} tooldetailsaccesstokenurl, mod_lti {{/str}}:</strong> {{urls.accesstoken}}</li>
|
||||
<li><strong>{{#str}} tooldetailsauthrequesturl, mod_lti {{/str}}:</strong> {{urls.authrequest}}</li>
|
||||
</ul>
|
||||
@@ -0,0 +1,41 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template mod_lti/tool_config_modal_footer
|
||||
|
||||
This template provides the layout for the footer of the configuration summary modal for
|
||||
a single tool opened from the tool configuration page.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* mailto
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"mailto": "mailto:?subject=...&body=..."
|
||||
}
|
||||
|
||||
}}
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" onclick="location.href='{{mailto}}';">{{#str}} tooldetailsmodalemail, mod_lti {{/str}}</button>
|
||||
<button type="button" class="btn btn-secondary" data-action="hide">{{#str}} cancel {{/str}}</button>
|
||||
</div>
|
||||
@@ -68,23 +68,26 @@ class mod_lti_locallib_testcase extends advanced_testcase {
|
||||
$tool = new stdClass();
|
||||
$tool->enabledcapability = '';
|
||||
$tool->parameter = '';
|
||||
$tool->ltiversion = 'LTI-1p0';
|
||||
$this->assertEquals(lti_split_custom_parameters(null, $tool, array(), "x=1\ny=2", false),
|
||||
array('custom_x' => '1', 'custom_y' => '2'));
|
||||
|
||||
// Check params with caps.
|
||||
$this->assertEquals(lti_split_custom_parameters(null, $tool, array(), "X=1", false),
|
||||
$this->assertEquals(lti_split_custom_parameters(null, $tool, array(), "X=1", true),
|
||||
array('custom_x' => '1', 'custom_X' => '1'));
|
||||
|
||||
// Removed repeat of previous test with a semicolon separator.
|
||||
|
||||
$this->assertEquals(lti_split_custom_parameters(null, $tool, array(), 'Review:Chapter=1.2.56', false),
|
||||
array('custom_review_chapter' => '1.2.56', 'custom_Review_Chapter' => '1.2.56'));
|
||||
$this->assertEquals(lti_split_custom_parameters(null, $tool, array(), 'Review:Chapter=1.2.56', true),
|
||||
array(
|
||||
'custom_review_chapter' => '1.2.56',
|
||||
'custom_Review:Chapter' => '1.2.56'));
|
||||
|
||||
$this->assertEquals(lti_split_custom_parameters(null, $tool, array(),
|
||||
'Complex!@#$^*(){}[]KEY=Complex!@#$^*;(){}[]½Value', false),
|
||||
'Complex!@#$^*(){}[]KEY=Complex!@#$^*;(){}[]½Value', true),
|
||||
array(
|
||||
'custom_complex____________key' => 'Complex!@#$^*;(){}[]½Value',
|
||||
'custom_Complex____________KEY' => 'Complex!@#$^*;(){}[]½Value'));
|
||||
'custom_Complex!@#$^*(){}[]KEY' => 'Complex!@#$^*;(){}[]½Value'));
|
||||
|
||||
// Test custom parameter that returns $USER property.
|
||||
$user = $this->getDataGenerator()->create_user(array('middlename' => 'SOMETHING'));
|
||||
@@ -369,7 +372,7 @@ class mod_lti_locallib_testcase extends advanced_testcase {
|
||||
$url = $result->url;
|
||||
$this->assertEquals($typeconfig['toolurl'], $url);
|
||||
$this->assertEquals('ContentItemSelectionRequest', $params['lti_message_type']);
|
||||
$this->assertEquals(LTI_VERSION_2, $params['lti_version']);
|
||||
$this->assertEquals(LTI_VERSION_1, $params['lti_version']);
|
||||
$this->assertEquals('application/vnd.ims.lti.v1.ltilink', $params['accept_media_types']);
|
||||
$this->assertEquals('frame,iframe,window', $params['accept_presentation_document_targets']);
|
||||
$this->assertEquals($returnurl->out(false), $params['content_item_return_url']);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Tests cleaning up the access tokens task.
|
||||
*
|
||||
* @package mod_lti
|
||||
* @category test
|
||||
* @copyright 2019 Mark Nelson <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Tests cleaning up the access tokens task.
|
||||
*
|
||||
* @package mod_lti
|
||||
* @category test
|
||||
* @copyright 2019 Mark Nelson <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mod_lti_clean_access_tokens_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test set up.
|
||||
*
|
||||
* This is executed before running any test in this file.
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->resetAfterTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the cleanup task.
|
||||
*/
|
||||
public function test_cleanup_task() {
|
||||
global $DB;
|
||||
|
||||
$time = time();
|
||||
|
||||
// Create an expired access token.
|
||||
$token = new stdClass();
|
||||
$token->typeid = 1;
|
||||
$token->scope = 'scope';
|
||||
$token->token = 'token';
|
||||
$token->validuntil = $time - DAYSECS;
|
||||
$token->timecreated = $time - DAYSECS;
|
||||
|
||||
$t1id = $DB->insert_record('lti_access_tokens', $token);
|
||||
|
||||
// New token, in the future.
|
||||
$token->validuntil = $time + DAYSECS;
|
||||
|
||||
$token->token = 'token2';
|
||||
$t2id = $DB->insert_record('lti_access_tokens', $token);
|
||||
|
||||
// Run the task.
|
||||
$task = new \mod_lti\task\clean_access_tokens();
|
||||
$task->execute();
|
||||
|
||||
// Check there is only one token now.
|
||||
$tokens = $DB->get_records('lti_access_tokens');
|
||||
|
||||
$this->assertCount(1, $tokens);
|
||||
|
||||
$token = reset($tokens);
|
||||
|
||||
$this->assertEquals($t2id, $token->id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file contains a service for issuing access tokens
|
||||
*
|
||||
* @package mod_lti
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('NO_DEBUG_DISPLAY', true);
|
||||
define('NO_MOODLE_COOKIES', true);
|
||||
|
||||
use Firebase\JWT\JWT as JWT;
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->dirroot . '/mod/lti/locallib.php');
|
||||
|
||||
|
||||
$response = new \mod_lti\local\ltiservice\response();
|
||||
|
||||
$contenttype = isset($_SERVER['CONTENT_TYPE']) ? explode(';', $_SERVER['CONTENT_TYPE'], 2)[0] : '';
|
||||
|
||||
$ok = ($_SERVER['REQUEST_METHOD'] === 'POST') && ($contenttype === 'application/x-www-form-urlencoded');
|
||||
$error = 'invalid_request';
|
||||
|
||||
$clientassertion = optional_param('client_assertion', '', PARAM_TEXT);
|
||||
$clientassertiontype = optional_param('client_assertion_type', '', PARAM_TEXT);
|
||||
$granttype = optional_param('grant_type', '', PARAM_TEXT);
|
||||
$scope = optional_param('scope', '', PARAM_TEXT);
|
||||
|
||||
if ($ok) {
|
||||
$ok = !empty($clientassertion) && !empty($clientassertiontype) &&
|
||||
!empty($granttype) && !empty($scope);
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
$ok = ($clientassertiontype === 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer') &&
|
||||
($granttype === 'client_credentials');
|
||||
$error = 'unsupported_grant_type';
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
$parts = explode('.', $clientassertion);
|
||||
$ok = (count($parts) === 3);
|
||||
if ($ok) {
|
||||
$payload = JWT::urlsafeB64Decode($parts[1]);
|
||||
$claims = json_decode($payload, true);
|
||||
$ok = !is_null($claims) && !empty($claims['sub']);
|
||||
}
|
||||
$error = 'invalid_request';
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
$error = 'invalid_client';
|
||||
$tool = $DB->get_record('lti_types', array('clientid' => $claims['sub']));
|
||||
if ($tool) {
|
||||
$typeconfig = lti_get_type_config($tool->id);
|
||||
if (!empty($typeconfig['publickey'])) {
|
||||
try {
|
||||
$jwt = JWT::decode($clientassertion, $typeconfig['publickey'], array('RS256'));
|
||||
$ok = true;
|
||||
} catch (Exception $e) {
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
$scopes = array();
|
||||
$requestedscopes = explode(' ', $scope);
|
||||
$permittedscopes = lti_get_permitted_service_scopes($tool, $typeconfig);
|
||||
$scopes = array_intersect($requestedscopes, $permittedscopes);
|
||||
$ok = !empty($scopes);
|
||||
$error = 'invalid_scope';
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
$token = lti_new_access_token($tool->id, $scopes);
|
||||
$expiry = LTI_ACCESS_TOKEN_LIFE;
|
||||
$permittedscopes = implode(' ', $scopes);
|
||||
$body = <<< EOD
|
||||
{
|
||||
"access_token" : "{$token->token}",
|
||||
"token_type" : "Bearer",
|
||||
"expires_in" : {$expiry},
|
||||
"scope" : "{$permittedscopes}"
|
||||
}
|
||||
EOD;
|
||||
} else {
|
||||
$response->set_code(400);
|
||||
$body = <<< EOD
|
||||
{
|
||||
"error" : "{$error}"
|
||||
}
|
||||
EOD;
|
||||
}
|
||||
|
||||
$response->set_body($body);
|
||||
|
||||
$response->send();
|
||||
@@ -84,6 +84,8 @@ if (!empty($id)) {
|
||||
// Assign a default empty value for the lti_icon.
|
||||
$type->lti_icon = '';
|
||||
$type->lti_secureicon = '';
|
||||
|
||||
$type->lti_clientid = null;
|
||||
}
|
||||
|
||||
$pageurl = new moodle_url('/mod/lti/typessettings.php');
|
||||
@@ -119,7 +121,8 @@ if (lti_request_is_using_ssl() && !empty($type->lti_secureicon)) {
|
||||
$type->oldicon = $type->lti_icon;
|
||||
}
|
||||
|
||||
$form = new mod_lti_edit_types_form($pageurl, (object)array('isadmin' => true, 'istool' => false));
|
||||
$form = new mod_lti_edit_types_form($pageurl,
|
||||
(object)array('isadmin' => true, 'istool' => false, 'id' => $id, 'clientid' => $type->lti_clientid));
|
||||
|
||||
if ($data = $form->get_data()) {
|
||||
$type = new stdClass();
|
||||
|
||||
+34
-2
@@ -1,5 +1,37 @@
|
||||
This files describes API changes in the lti code.
|
||||
|
||||
=== 3.7 ===
|
||||
|
||||
* Deprecated functions to add support for LTI 1 tools to access services:
|
||||
- mod_lti\local\ltiservice\service_base->check_tool_proxy()
|
||||
- mod_lti\local\ltiservice\service_base->check_type()
|
||||
- mod_lti\local\ltiservice\resource_base->check_tool_proxy()
|
||||
- mod_lti\local\ltiservice\resource_base->check_type()
|
||||
- mod_lti_edit_types_form->get_lti_advantage_services()
|
||||
replaced by:
|
||||
- mod_lti\local\ltiservice\service_base->check_tool()
|
||||
- mod_lti\local\ltiservice\resource_base->check_tool()
|
||||
- mod_lti_edit_types_form->get_lti_services()
|
||||
* Deprecated functions to prevent name clashes with service-related parameters:
|
||||
- mod_lti\local\ltiservice\service_base->get_configuration_parameter_names()
|
||||
- mod_lti\local\ltiservice\service_base->get_configuration_options()
|
||||
- ltiservice_gradebookservices\local\service\gradebookservices\get_configuration_options()
|
||||
* Deprecated functions to simplify implementation of LTI scopes for accessing services:
|
||||
- mod_lti\local\ltiservice\resource_base->get_permissions()
|
||||
- ltiservice_gradebookservices\local\resources\lineitem\get_permissions()
|
||||
- ltiservice_gradebookservices\local\resources\lineitems\get_permissions()
|
||||
- ltiservice_gradebookservices\local\resources\results\get_permissions()
|
||||
- ltiservice_gradebookservices\local\resources\scores\get_permissions()
|
||||
- ltiservice_memberships\local\resources\contextmemberships\get_permissions()
|
||||
replaced by:
|
||||
- mod_lti\local\ltiservice\service_base->get_permitted_scopes()
|
||||
- ltiservice_gradebookservices\local\service\gradebookservices->get_permitted_scopes()
|
||||
- ltiservice_memberships\local\service\memberships\->get_permitted_scopes()
|
||||
* Deprecated function to remove definition as static:
|
||||
- ltiservice_memberships\local\service\memberships\get_users_json()
|
||||
replaced by:
|
||||
- ltiservice_memberships\local\service\memberships\get_members_json()
|
||||
|
||||
=== 3.4 ===
|
||||
|
||||
* The following functions, previously used (exclusively) by upgrade steps are not available
|
||||
@@ -21,7 +53,8 @@ This files describes API changes in the lti code.
|
||||
=== 3.0 ===
|
||||
|
||||
* Function lti_view (locallib.php) was renamed to lti_launch_tool.
|
||||
A new and different lti_view function was created in the module lib.php containing the code for launching events and update the completion status for the module.
|
||||
A new and different lti_view function was created in the module lib.php containing the code
|
||||
for launching events and update the completion status for the module.
|
||||
|
||||
|
||||
=== 2.8 ===
|
||||
@@ -35,4 +68,3 @@ This files describes API changes in the lti code.
|
||||
|
||||
* mod_lti\event\unknown_service_api_called now has less data stored in 'other'
|
||||
but everything is still available for event observers via method get_message_data()
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$plugin->version = 2018120300; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2019031303; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018112800; // Requires this Moodle version.
|
||||
$plugin->component = 'mod_lti'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->cron = 0;
|
||||
|
||||
+18
-2
@@ -116,7 +116,17 @@ if ($lti->showdescriptionlaunch && $lti->intro) {
|
||||
echo $OUTPUT->box(format_module_intro('lti', $lti, $cm->id), 'generalbox description', 'intro');
|
||||
}
|
||||
|
||||
if ( $launchcontainer == LTI_LAUNCH_CONTAINER_WINDOW ) {
|
||||
$typeid = $lti->typeid;
|
||||
if ($typeid) {
|
||||
$config = lti_get_type_type_config($typeid);
|
||||
} else {
|
||||
$config = new stdClass();
|
||||
$config->lti_ltiversion = LTI_VERSION_1;
|
||||
}
|
||||
|
||||
if (($launchcontainer == LTI_LAUNCH_CONTAINER_WINDOW) &&
|
||||
(($config->lti_ltiversion !== LTI_VERSION_1P3) || isset($SESSION->lti_initiatelogin_status))) {
|
||||
unset($SESSION->lti_initiatelogin_status);
|
||||
if (!$forceview) {
|
||||
echo "<script language=\"javascript\">//<![CDATA[\n";
|
||||
echo "window.open('launch.php?id=" . $cm->id . "&triggerview=0','lti-" . $cm->id . "');";
|
||||
@@ -129,8 +139,14 @@ if ( $launchcontainer == LTI_LAUNCH_CONTAINER_WINDOW ) {
|
||||
echo html_writer::link($url, get_string("basiclti_in_new_window_open", "lti"), array('target' => '_blank'));
|
||||
echo html_writer::end_tag('p');
|
||||
} else {
|
||||
$content = '';
|
||||
if ($config->lti_ltiversion === LTI_VERSION_1P3) {
|
||||
$content = lti_initiatelogin($cm->course, $id, $lti, $config);
|
||||
}
|
||||
|
||||
// Request the launch content with an iframe tag.
|
||||
echo '<iframe id="contentframe" height="600px" width="100%" src="launch.php?id='.$cm->id.'&triggerview=0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
|
||||
echo '<iframe id="contentframe" height="600px" width="100%" src="launch.php?id=' . $cm->id .
|
||||
"&triggerview=0\" webkitallowfullscreen mozallowfullscreen allowfullscreen>{$content}</iframe>";
|
||||
|
||||
// Output script to make the iframe tag be as large as possible.
|
||||
$resize = '
|
||||
|
||||
Reference in New Issue
Block a user