Code Monkey home page Code Monkey logo

lti-php's People

Contributors

abertranb avatar izumi-kun avatar spvickers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lti-php's Issues

Grade Passback

Apologies this is not an issue. Just wanted to ask where is a good place to start with grade passback?

Thanks

Feature Request: ability to ignore host validation while testing

In my test environment I've spun up dev instances of my servers and given them self-signed certificates for https. When I initiate LTI 1.3 messages between my platform and tool I get the following response:

["error"]=>
  string(48) "SSL certificate problem: self signed certificate"

I can bypass this error message if I add the following lines to the ceLTIc\LTI\Http\CurlClient class:

        if( $test_environment ){
            curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false );
            curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
        }

I would need a way to set the $test_environment boolean, perhaps by passing it into the send() method as an optional parameter.

Have you run into this issue before, and do you have a different means of bypassing the self-signed certificate check? If not, do you think it would be worthwhile to add a testing flag to the CurlClient's send() method? I am most interested in enabling PHP unit tests and think this could be helpful.

question about logic: can variable both not exist and have a positive string length?

LTI-PHP/src/Tool.php

Lines 1378 to 1383 in cf2212b

if (!isset($this->messageParameters['custom_lineitem_url']) && (strlen(trim($this->messageParameters['custom_lineitem_url'])) > 0)) {
$this->setError('Missing LineItem service URL.', true, $generateWarnings);
}
if (!isset($this->messageParameters['for_user_id']) && (strlen(trim($this->messageParameters['for_user_id'])) > 0)) {
$this->setError('Missing ID of \'for user\'', true, $generateWarnings);
}

I'm a bit confused by the two if statements referenced above.
The logic seems as if it will never be true:

if (!isset($var) && strlen(trim($var)) > 0) {
  // do logic
}

Should the evaluation instead be an or statement with the second comparison being equal to zero?

[Question] Correct way to overwrite Tool class and call LTI Advantage services

Hello Stephen Vickers,
I am starting the development with your incredible library, however I have some doubts, it would be very useful if you can help me with an answer.

1) In the OnLaunch() method, when I override the LTI\Tool class I print $this to see what things I can access and I realize that $this->consumer and $this->platform are available. , which refer to the same thing.

Which of the two instances should I use?

2) For use Memberships service you specify in the documentation that it can be used from the context, and in the onLaunch() method you are providing the context, but when trying to call the service I get a null even when passing true var inside function.
In the LMS all lti services are enabled

$users = $this->context->getMemberships();

On the other hand the constructor of my class looks something like this (Maybe I am declaring the use of the service wrong)

 $requiredMessages = array(new Profile\Message('basic-lti-launch-request', 'connect.php', array('User.id', 'Membership.role')));
        $optionalMessages = array(new Profile\Message('ContentItemSelectionRequest', 'connect.php', 
                array('User.id', 'Membership.role')),
            new Profile\Message('DashboardRequest', 'connect.php', array('User.id'))
        );
        $this->resourceHandlers[] = new Profile\ResourceHandler(
            new Profile\Item('clean_app', 'clean_app', 'Some clean lti app'),
            'images/icon50.png', $requiredMessages, $optionalMessages);

        $this->requiredServices[] = new Profile\ServiceDefinition(array('application/vnd.ims.lti.v2.toolproxy+json'), array('POST'));
        
        $this->allowSharing = true;
        $this->baseUrl = getAppUrl();
        $this->signatureMethod = SIGNATURE_METHOD;
        $this->kid = KID;
        $this->rsaKey = PRIVATE_KEY;
        $this->requiredScopes = array(
            LTI\Service\LineItem::$SCOPE,
            LTI\Service\Score::$SCOPE,
            LTI\Service\Membership::$SCOPE
        );

Can we mask the request URL when validating the OAuth signature? (lti 1.2)

Under the OAuth 1.0 specification we take the request and hash it against the shared secret to get an OAuth signature. The OAuthRequest class uses its from_request static method to reconstruct the OAuth request from server variables, but there are times when we may wish to override the calculated request URL with a custom URL. For example, a server redirect may reroute a request and cause the calculated request URL to differ from the request URL that was used to encode the original OAuth signature. The OAuthRequest::from_request static method provides parameter options that we may use to override the calculated request values. However, the current System class does not leverage these parameters in its verifySignature method.

Could we add the $http_method, $http_url, and $parameters options to the System verifySignature method so they can be passed into the OAuthRequest::from_request static method? This will allow us to extend the Tool class and provide custom URL values to the verifySignature method call. This should help with test environments that implement custom ports as well as instances where redirects have changed the destination of the LTI request.

PHP error in parseClaims() with new version

Hi,

I have just upgraded to the new version (4.7.0) and there seems to be a bug in System.php line 1015:
unset($payload->{$claim}[$mapping['claim']]);
whereas line 1018:
unset($payload->{$claim}->{$mapping['claim']});

PHP error message: Cannot use object of type stdClass as array

Changing line 1015 to the same resolves the issue.

Thanks,
Aron

Canvas LMS returning error 422 Unprocessable Entity

It was fairly easy to get my LTI 1.3 app working with Moodle using your Rating app example as a guide (thanks!). I can't seem to get the Rating app or my app to work with Canvas LMS. It has something to do with the authorization piece. I get an HTTP 422 Unprocessable Entity error during the authorization process. I can't get the Rating app to work with Canvas either. It encounters the same 422 error. Any thoughts?

Canvas loads my connect.php into an iframe with this request payload:

{
"iss":"http://www.canvasvm.local",
"login_hint":"314f5492a2603d07aec9264e40c827b9f302667d",
"client_id":"10000000000006",
"target_link_uri":"http://myapp.local/lti/myapp/connect.php",
"lti_message_hint":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ2ZXJpZmllciI6ImY3NDUzOTgyYzkyYzkyNmI0MmU3YzAzMDdmMGI5MzhiMGM3NzljMGIwMzAyNTU5OTFkMWZmYWE3OWEwMzZlN2ZjZGYyNjE0OGI0NTFiMjU1NmYyMjM3Mjg0ZmJlODljMThhNWRmMjU1YTM1ZTgxZmE3ZjZlMTFlYjhiYjBiOTllIiwiY2FudmFzX2RvbWFpbiI6ImNhbnZhc3ZtLmxvY2FsIiwiY29udGV4dF90eXBlIjoiQ291cnNlIiwiY29udGV4dF9pZCI6MTAwMDAwMDAwMDAwMDEsImV4cCI6MTU5NDcyNjE2Mn0.AUUZxi66t3oxwMwFlrWJ951vruaxefn6ajf7T9lAvlI",
"canvas_region":"not_configured"
}

Then I see my connect.php make a call to http://www.canvasvm.local/api/lti/authorize which fails with a 422 error. This is the payload the connect.php sent:

{
"client_id":"10000000000006",
"login_hint":"314f5492a2603d07aec9264e40c827b9f302667d",
"nonce":"Zo0i1KY3rJ7N6fbBqOgHkrEPKDmSaDLI",
"prompt":"none",
"redirect_uri":"http://myapp.local/lti/myapp/connect.php",
"response_mode":"form_post",
"response_type":"id_token",
"scope":"openid",
"state":"F9MuZleZ",
"lti_message_hint":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ2ZXJpZmllciI6ImY3NDUzOTgyYzkyYzkyNmI0MmU3YzAzMDdmMGI5MzhiMGM3NzljMGIwMzAyNTU5OTFkMWZmYWE3OWEwMzZlN2ZjZGYyNjE0OGI0NTFiMjU1NmYyMjM3Mjg0ZmJlODljMThhNWRmMjU1YTM1ZTgxZmE3ZjZlMTFlYjhiYjBiOTllIiwiY2FudmFzX2RvbWFpbiI6ImNhbnZhc3ZtLmxvY2FsIiwiY29udGV4dF90eXBlIjoiQ291cnNlIiwiY29udGV4dF9pZCI6MTAwMDAwMDAwMDAwMDEsImV4cCI6MTU5NDcyNjE2Mn0.AUUZxi66t3oxwMwFlrWJ951vruaxefn6ajf7T9lAvlI"
}

It looks like the Tool->result function outputs this in the iframe which generates the 422 error

<html>
<head>
<title>IMS LTI message</title>
<script type="text/javascript">
//<![CDATA[
function doOnLoad() {
    document.forms[0].submit();
}

window.onload=doOnLoad;
//]]>
</script>
</head>
<body>
<form action="http://www.canvasvm.local/api/lti/authorize" method="post" target="" encType="application/x-www-form-urlencoded">
    <input type="hidden" name="client_id" value="10000000000006" />
    <input type="hidden" name="login_hint" value="314f5492a2603d07aec9264e40c827b9f302667d" />
    <input type="hidden" name="nonce" value="bUrPzbOLFeTXvMv25VP9zjevCteSEAO2" />
    <input type="hidden" name="prompt" value="none" />
    <input type="hidden" name="redirect_uri" value="http://myapp.local/lti/myapp/connect.php" />
    <input type="hidden" name="response_mode" value="form_post" />
    <input type="hidden" name="response_type" value="id_token" />
    <input type="hidden" name="scope" value="openid" />
    <input type="hidden" name="state" value="5Yi1u3sN" />
    <input type="hidden" name="lti_message_hint" value="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ2ZXJpZmllciI6IjNiZTY5YzUxZWE0ODg3OGFlNjEzMGZmYjkzNDIyNWIxMzM4MmQ4YzJkOTRkZjkyMmY1YThhYjEyMTQzZWMwNDI2NDhkZmNjZGU0N2E5MjM0ZDY3MmI4ODM3MDVhNjljYWYxMWY3ZmVhN2JhOWE1NzVlNjA1YTFjYTQwZjgzOWJjIiwiY2FudmFzX2RvbWFpbiI6ImNhbnZhc3ZtLmxvY2FsIiwiY29udGV4dF90eXBlIjoiQ291cnNlIiwiY29udGV4dF9pZCI6MTAwMDAwMDAwMDAwMDEsImV4cCI6MTU5NDc0MTQwNn0.EZYcc7gNnAPaSCcQkXZ9r7So5ZeGKkrLA6324dWUr5M" />
</form>
</body>
</html>

I am using LTI-PHP 4.0.1
I am using a Bitnami install of Canvas using the June code.
I have my app setup as an LTI 1.3 app in Canvas (developer LTI key and App)
My canvas host is www.canvasvm.local

no responseJson on getAll lineItem request from context

Iam trying to retrieve lineitems - using the context.
My code is -

use ceLTIc\LTI;

require_once('lib.php');

$db = null;
init($db, true);

$dataConnector = LTI\DataConnector\DataConnector_pdo::getDataConnector($db, DB_TABLENAME_PREFIX);
$platform = LTI\Platform::fromPlatformId($_SESSION['all']['platform_id'], $_SESSION['all']['oauth_consumer_key'], $_SESSION['all']['deployment_id'], $dataConnector);

$context = LTI\Context::fromPlatform($platform, $_SESSION['all']['context_id']);
$lineItems = $context->getLineItems($_SESSION['all']['resource_link_id'], 'Essay');

Debugging the httpMessage i can see 5 requests being made 3 GET 2 POST

Any advice on where im possibly getting stuck with retrieving the response to be use able.

[Question] Incorrectly Invalid Signature

I'm running into a strange error. I'm running my Tool Provider on Heroku (although I think that may be irrelevant — I mention it only because of the way they handle SSL connections on free apps), and when I make an LTI launch request from my Tool Consumer (Blackbaud's learning management system, in this case), I find that the OAuth Signature is failing its checks and being flagged as invalid.

I went through a bunch of gyrations making sure that I wasn't having some sort of timezone/timestamp disconnect, and have both the tool consumer and tool provider in the same timezone, with timestamps ~1 second apart (depending on when checked, I suppose). Double-checked that everyone was using the same key/secret combination.

Having dug around for a while in PHP-LTI code, I slapped together the below. Which makes me think that I'm experiencing Schrödinger's OAuth signature: it is both valid (using my hacked together check) and invalid (when processed by $tool->handleRequest()).

Any perspective on what might be going wrong? Any insights/redirects would be much appreciated! Thanks!

use ceLTIc\LTI\OAuth\OAuthRequest;
use ceLTIc\LTI\OAuthDataStore;
use DI\Container;
use GrotonSchool\AssignmentsViewer\AssignmentsViewerTool;
use ceLTIc\LTI\OAuth\OAuthConsumer;
use ceLTIc\LTI\OAuth\OAuthServer;
use ceLTIc\LTI\OAuth\OAuthSignatureMethod_HMAC_SHA1;

require_once __DIR__ . '/../bootstrap.php';
/** @var Container $container */

$_SESSION = [];
session_destroy();

/** @var LTITestTool */
$tool = $container->get(LTITestTool::class);
$tool->debugMode = true;

$hmac = new OAuthSignatureMethod_HMAC_SHA1();
$consumer = new OAuthConsumer('<key provided to consumer>', '<secret provided to consumer>');
$store = new OAuthDataStore($tool);
$server = new OAuthServer($store);
$request = OAuthRequest::from_request();
$token_field = $request instanceof OAuthRequest ? $request->get_parameter('oauth_token') : null;
error_log('token_field: ' . $token_field);
$token = $store->lookup_token($consumer, "access", $token_field);
error_log('token: ' . print_r($token, true));
error_log('Built signature: ' . $hmac->build_signature($request, $consumer, $token));
error_log('Check: ' . $hmac->check_signature($request, $consumer, $token, $request->get_parameter('oauth_signature')));

$tool->handleRequest();

And I'm seeing results like this:

2021-11-29T19:33:03.258151+00:00 app[web.1]: [29-Nov-2021 14:33:03 America/New_York] token_field: 
2021-11-29T19:33:03.258387+00:00 app[web.1]: [29-Nov-2021 14:33:03 America/New_York] token: ceLTIc\LTI\OAuth\OAuthToken Object
2021-11-29T19:33:03.258411+00:00 app[web.1]: (
2021-11-29T19:33:03.258533+00:00 app[web.1]:     [key] => ceLTIc\LTI\OAuth\OAuthConsumer Object
2021-11-29T19:33:03.258577+00:00 app[web.1]:         (
2021-11-29T19:33:03.258711+00:00 app[web.1]:             [key] => <key provided to consumer>
2021-11-29T19:33:03.258859+00:00 app[web.1]:             [secret] => <secret provided to consumer>
2021-11-29T19:33:03.258941+00:00 app[web.1]:             [callback_url] => 
2021-11-29T19:33:03.258984+00:00 app[web.1]:         )
2021-11-29T19:33:03.258996+00:00 app[web.1]: 
2021-11-29T19:33:03.259171+00:00 app[web.1]:     [secret] => 
2021-11-29T19:33:03.259192+00:00 app[web.1]: )
2021-11-29T19:33:03.259204+00:00 app[web.1]: 
2021-11-29T19:33:03.259411+00:00 app[web.1]: [29-Nov-2021 14:33:03 America/New_York] Built signature: 3SUKntyPu0Md+fXXf42fV1IpXq8=
2021-11-29T19:33:03.259536+00:00 app[web.1]: [29-Nov-2021 14:33:03 America/New_York] Check: 1
2021-11-29T19:33:03.259775+00:00 app[web.1]: [29-Nov-2021 14:33:03 America/New_York] [INFO] POST request received for '/launch' with body parameters of:
2021-11-29T19:33:03.259807+00:00 app[web.1]: array (
2021-11-29T19:33:03.259903+00:00 app[web.1]:   'oauth_callback' => 'about:blank',
2021-11-29T19:33:03.260048+00:00 app[web.1]:   'oauth_nonce' => '6698fe12a2844078848815af6142c8aa',
2021-11-29T19:33:03.260159+00:00 app[web.1]:   'oauth_signature_method' => 'HMAC-SHA1',
2021-11-29T19:33:03.260271+00:00 app[web.1]:   'oauth_timestamp' => '1638214383',
2021-11-29T19:33:03.260321+00:00 app[web.1]:   'oauth_version' => '1.0',
2021-11-29T19:33:03.260421+00:00 app[web.1]:   'launch_presentation_locale' => 'en-US',
2021-11-29T19:33:03.260536+00:00 app[web.1]:   'lti_message_type' => 'basic-lti-launch-request',
2021-11-29T19:33:03.260596+00:00 app[web.1]:   'lti_version' => 'LTI-1p0',
2021-11-29T19:33:03.260713+00:00 app[web.1]:   'resource_link_id' => '542|27|2352627|84489|1202415',
2021-11-29T19:33:03.260814+00:00 app[web.1]:   'resource_link_title' => 'LTI Test',
2021-11-29T19:33:03.260923+00:00 app[web.1]:   'resource_link_description' => 'Testing LTI tool provider',
2021-11-29T19:33:03.261056+00:00 app[web.1]:   'tool_consumer_info_product_family_code' => 'BlackbaudK12',
2021-11-29T19:33:03.261139+00:00 app[web.1]:   'tool_consumer_info_version' => '1.52',
2021-11-29T19:33:03.261193+00:00 app[web.1]:   'user_id' => '6511555',
2021-11-29T19:33:03.261252+00:00 app[web.1]:   'roles' => 'Instructor',
2021-11-29T19:33:03.261328+00:00 app[web.1]:   'lis_person_name_given' => 'Seth',
2021-11-29T19:33:03.261414+00:00 app[web.1]:   'lis_person_name_family' => 'Battis',
2021-11-29T19:33:03.261501+00:00 app[web.1]:   'lis_person_name_full' => 'Seth Battis',
2021-11-29T19:33:03.261628+00:00 app[web.1]:   'lis_person_contact_email_primary' => '[email protected]',
2021-11-29T19:33:03.261705+00:00 app[web.1]:   'context_id' => '542|2|94137853',
2021-11-29T19:33:03.261797+00:00 app[web.1]:   'context_label' => 'Advanced History (Y)',
2021-11-29T19:33:03.261890+00:00 app[web.1]:   'context_title' => 'Advanced History (Y)',
2021-11-29T19:33:03.262002+00:00 app[web.1]:   'tool_consumer_instance_guid' => '<tool consumer GUID>',
2021-11-29T19:33:03.262109+00:00 app[web.1]:   'tool_consumer_instance_name' => 'Groton School',
2021-11-29T19:33:03.262217+00:00 app[web.1]:   'launch_presentation_document_target' => 'window',
2021-11-29T19:33:03.262461+00:00 app[web.1]:   'launch_presentation_return_url' => 'https://groton.myschoolapp.com/app/Lti/Land?lid=eyFC5CGyAwwYN7YMH4ihNg%253d%253d',
2021-11-29T19:33:03.262586+00:00 app[web.1]:   'oauth_consumer_key' => '<key provided to consumer>',
2021-11-29T19:33:03.262699+00:00 app[web.1]:   'oauth_signature' => '3SUKntyPu0Md+fXXf42fV1IpXq8=',
2021-11-29T19:33:03.262709+00:00 app[web.1]: )
2021-11-29T19:33:03.262900+00:00 app[web.1]: [29-Nov-2021 14:33:03 America/New_York] [ERROR] Request failed with reason: 'Invalid signature'
2021-11-29T19:33:03.262917+00:00 app[web.1]: See: 
2021-11-29T19:33:03.263014+00:00 app[web.1]:   /app/vendor/celtic/lti/src/Util.php line 222
2021-11-29T19:33:03.263110+00:00 app[web.1]:   /app/vendor/celtic/lti/src/Tool.php line 473
2021-11-29T19:33:03.263192+00:00 app[web.1]:   /app/public/launch.php line 34

Add ability to disable draft/pre-beta specifications

We use this library in several production apps against the Canvas LMS, all of which went down simultaneously for us last week. The culprit was some kind of bug or other downtime in Canvas's support for the draft "OIDC Login with LTI Client Side postMessages" specification, which this library added support for in version 4.10.0. Canvas started failing to negotiate its side of the postMessages correctly, and the apps failed to launch as a result. (Our solution was to roll all of our apps back to v4.9 temporarily; this may have been fixed in Canvas since then.)

Technically, that's a Canvas bug and not a Celtic LTI-PHP bug, but it was caused by us being unable to control our exposure to a draft spec and the instability around it. It'd be great to have some kind of feature flag to enable this spec (and future draft or pre-beta specs), which is off by default; clients should be able to control whether they can afford to be on the bleeding edge or not.

Trouble with testing as a tool against test platforms

I am attempting to use this library for our tool. We used the older version of this library for lti 1.1 and am hoping to use this for LTI Advantage. It works when testing against https://saltire.lti.app/platform
When testing against https://lti-ri.imsglobal.org/ it fails the OIDC check.
When testing using moodle as a platform I get "An error occurred when launching the external tool:Debug error: JWT signature check failed - perhaps an invalid public key or timestamp"

I have tried the example tool and can get it to launch in https://saltire.lti.app/platform but will not launch in https://lti-ri.imsglobal.org/

Has anybody used this library to test against a platform here with any success? https://lti-ri.imsglobal.org/

I am hoping to find a little support for this library to get me through this roadblock.

Fatal error in ceLTIc\LTI\Tool::ID_SCOPE_RESOURCE

LTI plugin was updated from the previous version to 5.0.0. After updating, visiting the site yields the below stack trace.

Some info about the environment:

PHP 8.0.29
WordPress 6.0.5
LTI Tool v3.2.1

Fatal error: Uncaught Error: Undefined constant ceLTIc\LTI\Tool::ID_SCOPE_RESOURCE in /home/someuser/public_html/wp-content/plugins/lti-tool/includes/lib.php:708 Stack trace:
#0 /home/someuser/public_html/wp-content/plugins/lti-tool/lti-tool.php(119): lti_tool_get_options()
#1 /home/someuser/public_html/wp-includes/class-wp-hook.php(307): lti_tool_once_wp_loaded('')
#2 /home/someuser/public_html/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters(NULL, Array)
#3 /home/someuser/public_html/wp-includes/plugin.php(476): WP_Hook->do_action(Array)
#4 /home/someuser/public_html/wp-settings.php(620): do_action('wp_loaded')
#5 /home/someuser/public_html/wp-config.php(117): require_once('/home/someuser/...')
#6 /home/someuser/public_html/wp-load.php(50): require_once('/home/someuser/...')
#7 /home/someuser/public_html/wp-admin/admin.php(34): require_once('/home/someuser/...')
#8 /home/someuser/public_html/wp-admin/index.php(10): require_once('/home/someuser/...')
#9 {main} thrown in /home/someuser/public_html/wp-content/plugins/lti-tool/includes/lib.php on line 708

I wasn't sure whether to post the issue in this repo, or the wordpress-lti one as the issue popped up when LTI-PHP was updated to version 5.

Databaseless?

Sorry for the "rambling," I tried to give context to the question.

We've been using this library for years and it's been great for our usage requirements, but we recently encountered a use case (a secure redirect utilizing information passed through the LTI data to authenticate access) where the database configuration was entirely excessive. We don't really have any need to log anything for this use case and the LTI is essentially a glorified redirect script (obviously more or we wouldn't have made it an LTI).

We've looked and looked, but it doesn't appear that the class currently supports a setup that does not utilize a database. We understand that the level of resources needed is negligible, but we subscribe to the belief that the fewer things involved in a process makes for a process less likely to break.

So, the question is: can we use this class hardcoding a secret and key for the handshake, instead of looking it up in a database? If so, what did we miss in how-to do it?

How do I get student results from the third party after completing the course ?

Hello

Hope you are doing well,

I have one question regarding LTI. I am using the 1.1 LTI version. How do I get student results from the third party after completing the course? so can you suggest to me how to handle the result response from the tool consumer? if possible please send me a sample code

Thank you in advance

Thanks.

array_merge_recursive throws off OAuth1 Signature when GET and POST keys are duplicated

SCENARIO: Signing LTI 1.2 basic launch request using OAuth1 Protocol
PLATFORM appends custom values to the URL Query String as well as to the POST
e.g.

  • URL: example.com/lti/v1p2?custom_type=article&custom_id=1234
  • POST: custom_type=article&custom_id=1234

In the OAuth/OAuthRequest::from_request method we use the following logic:

$parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
$post_data = OAuthUtil::parse_parameters(file_get_contents(self::$POST_INPUT));
$parameters = array_merge_recursive($parameters, $post_data);

The array_merge_recursive function causes the duplicated parameters to stack into an array:
{"custom_type":["article","article"],"custom_id":["1234","1234"]}

This causes the OAuth1 signature that is calculated to yield a different signature than that provided by the platform.
Could we change the OAuth/OAuthRequest::from_request method so it uses array_replace_recursive instead of array_merge_recursive? This should prevent the duplicated keys from transforming into redundant array values.

how can I extract $http->response after calling the send method ?

Hi @spvickers

I hope you are doing well.

I am using your package to implement a Tool provider. There is an issue with the result section. Using the celtic package, I am throwing the result from the tool provider to the tool consumer. I am throwing the result to the url passed by the tool consumer, but I am getting a below response After call Curl.

image

image

According to your code, you will extract the response after calling the $http->send() method.

I have print $http before the called the $http->send(). You can see I got null response in $http.

image

image

In order for me to determine whether or not the result was successfully sent to tool consumers, how can I extract $http->response after calling the send method?

Thanks

Blackboard send the launch_presentation_document_target in all-caps and causes an error

When Blackboard launches the LTI tool in a new window for instance, the value it passes to the Tool is WINDOW and since this is not the same as window (which is on the allowed list and WINDOW isn't) the LTI Library thinks an invalid value was supplied and reports an error.
Casting the launch_presentation_document_target to a lowercase and optionally casting the accept_presentation_document_targets to lowercase could prevent further problems.
I have made PR for this. I am not sure if this needs some deeper adjustments on a further level down in the Tool.

Update to PHP 8.1

I am working on an update to this library to use the functionality available in PHP 8.1, such as:

  • type hinting
  • strict typing
  • match expression
  • enumerations

I propose that this will be released as version 5, with version 4 being placed in a security-update only mode.

If anyone has any suggestions for how this library can be improved then this would be a good time to report them.

Thanks.

LTI launches can be blocked by popup blockers due to lack of human interaction

During LTI launch in an iframed tool, we're seeing new users in Chrome being blocked by that browser's popup blocker on their first usage of the tool. I think the scenario that's happening here is:

  1. Chrome isn't allowing a cookie to be set, due to the new-ish restrictions on setting cookies in iframes for untrusted sources (i.e., ones the user hasn't interacted with yet.)
  2. Celtic's LTI-PHP library is correctly detecting that this has happened, and is creating a form with a "_blank" target to open the tool in a new window instead. This form is automatically submitted by Javascript.
  3. Chrome's popup blocker detects a request to open a popup window which the user hasn't requested via a UI interaction, and blocks the popup.
  4. The user sees a blank page, and a 'popup blocked' notification which is easy to miss. There's no visible way forward from this point.

The key part here is in step 2; submitting the form happens without user interaction. And there's no visible controls on the resulting page (

LTI-PHP/src/Util.php

Lines 381 to 430 in 62ac401

if (empty($javascript)) {
$javascript = <<< EOD
function doOnLoad() {
if ((document.forms[0].target === '_blank') && (window.top === window.self)) {
document.forms[0].target = '';
}
document.forms[0].submit();
}
window.onload=doOnLoad;
EOD;
}
self::logForm($url, $params, 'POST');
$page = <<< EOD
<!DOCTYPE html>
<head>
<title>1EdTech LTI message</title>
<script type="text/javascript">
{$javascript}
</script>
</head>
<body>
<form action="{$url}" method="post" target="{$target}" encType="application/x-www-form-urlencoded">
EOD;
if (!empty($params)) {
foreach ($params as $key => $value) {
$key = htmlentities($key, ENT_COMPAT | ENT_HTML401, 'UTF-8');
if (!is_array($value)) {
$value = htmlentities($value, ENT_COMPAT | ENT_HTML401, 'UTF-8');
$page .= <<< EOD
<input type="hidden" name="{$key}" id="id_{$key}" value="{$value}" />
EOD;
} else {
foreach ($value as $element) {
$element = htmlentities($element, ENT_COMPAT | ENT_HTML401, 'UTF-8');
$page .= <<< EOD
<input type="hidden" name="{$key}" value="{$element}" />
EOD;
}
}
}
}
$page .= <<< EOD
</form>
</body>
</html>

) , giving the user a fallback option if this situation happens.

There are a couple of approaches to dealing with this, but one fairly straightforward-seeming one is to alter Util->sendForm() to include a 'Please wait...' message, along with an <input type='submit'> which becomes visible after a few seconds. If the user clicks that, that counts as user interaction, which is enough to cause the popup to be allowed.

Would be happy to discuss and/or collaborate on a pull request for this.

Troubleshooting AGS ResourceLink->doOutcomesService()

Hi Stephen,

I have been trying to send grades over to the Schoology LMS using the Assignment and Grades Services (AGS) protocol, and am having trouble with the ResourceLink class doOutcomesService() method. Though I enter the method, the logic cannot determine which outcomes service to use from the collection of variables. I added this logic so I could review the collected variables:

var_dump([
    'urlAGS' => $urlAGS,
    'urlLTI11' => $urlLTI11,
    'urlExt' => $urlExt,
    'has_configured_outcomes_service_hook' => $this->hasConfiguredApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this),
]);
die(__FILE__ . __LINE__);

And this is the outcome I receive:

array(4) {
    ["urlAGS"]=> string(0) ""
    ["urlLTI11"]=> string(0) ""
    ["urlExt"]=> string(0) ""
    ["has_configured_outcomes_service_hook"]=> bool(false)
}
/online-api/vendor/celtic/lti/src/ResourceLink.php729

These values maye it so I don't hit on any of the if statements that trigger the various outcomes services options. I had expected to trigger the first option, AGS, but the urlAGS variable is populated with this statement:

$urlAGS = $sourceResourceLink->getSetting('custom_lineitem_url');

However, the custom_lineitem_url variable seems to be missing from the data that I received from Schoology:

{
    "custom_lineitems_url":"https://lti-service.svc.schoology.com/lti-service/tool/6372124883/services/assignment-grade/v2p0/sections/6882476228/lineitems",
    "custom_ags_scopes":"
        https://purl.imsglobal.org/spec/lti-ags/scope/lineitem,
        https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly,
        https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly,
        https://purl.imsglobal.org/spec/lti-ags/scope/score",
    "custom_assignment_id":"1048",
    "custom_publication_id":"61",
    "custom_deeplink_type":"assignment",
    "custom_week_id":"2071",
    "custom_unit_id":"96",
    "custom_username":"dan.hammari"
}

It appears that I am receiving custom_lineitems_url while the code is looking for custom_lineitem_url. From the LTI documentation, it appears that both may be considered valid options:
https://www.imsglobal.org/spec/lti-ags/v2p0/#assignment-and-grade-service-claim

The claim defines the following properties:

lineitems: the endpoint URL for accessing the line item container for the current context. May be omitted if the tool has no permissions to access this endpoint.
lineitem: when an LTI message is launching a resource associated to one and only one lineitem, the claim must include the endpoint URL for accessing the associated line item; in all other cases, this property must be either blank or not included in the claim.

It seems that the custom_lineitem_url variable is optional. Should we look for custom_lineitems_url instead?

Roles mangled by Membership service due to hardcoded LTI version

In two places in Service/Membership/getMembers(), Tool::parseRoles() is called, with a hardcoded LTI version of Util::LTI_VERSION2. This leads to a bug in LTI 1.3 tools, which can receive membership roles for a user such as this pair (for a user who is both a teacher and a TA in the same course):

  [ "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant" , 
    "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor" ]

parseRoles() using LTI_VERSION2 incorrectly collapses these down into one role:

  http://purl.imsglobal.org/vocab/lis/v2/membership#TeachingAssistant

...which isn't even one of the roles that the LTI 1.3 spec allows, as far as I can tell.

Changing the hardcoded version in these calls to LTI_VERSION1P3 causes this pair of roles to be preserved correctly.

(Offhand, it looks like the Membership class has access to the platform's LTI version (through $this->source->getPlatform()->ltiVersion)...maybe it could use that instead of hardcoding LTI_VERSION2?)

Unable to verify JWT signature

I have a problem calling the Access token service. The service will return "Unable to verify JWT signature".

This is my JWT:
eyJhbGciOiJSUzI1NiIsImtpZCI6InhFQU92ZlRVeS1HaExKUVdIWldDZG43MGt5cEM1cmhVQkw4SmVJcEs0WE0iLCJ0eXAiOiJKV1QiLCJjdHkiOiJKV1QifQ.eyJpc3MiOiIxIiwic3ViIjoiMSIsImF1ZCI6Imh0dHBzOi8vc2FsdGlyZS5sdGkuYXBwL3BsYXRmb3JtL3Rva2VuLzcxY2Y0YjQyMmNhYmViMzEwMzNmZjIxMTgzZjUxMDJjIiwiaWF0IjoiMTY3NTQxNzUxMSIsIm5iZiI6IjE2NzU0MTc1MDYiLCJleHAiOiIxNjc1NDE3ODExIiwianRpIjoiU1ptRzFOQlluNHpMV3luUVFPVklBVUt0S2EzSjgyZ2ZVV2d5MEdycE5YUT0ifQ.rCcabTs7G0rn7QldvDexhd5YLBnmrr8-VwxZeOrpGKYgNIWSIoaB0toJ1nRN-fHd3Xw_FDs2s1OOXngadXiX1nhO6JUDwKcS4_NjFzWqijj8jSrTHIMulz_wJMQ6z-IfTF71DojxspIy4O8WLFtREKurBcIdxmFfImP54PD6PKFy85p8Fc5_kRe-rQ067ZW5Ym1CPReUNz26MDvQnGEX13o1YwtT7payclx_5FdYNI7I69U5VoyM576CHQAFQIn0W0WchIV4_k7_BH8V1evlYe2WkZAwqnSWCarne-P9zj2Itw4WBtDH2f7BRjO3Veiv-1Kr1_4IyzmDzLrKrsR1cg

And here is my public key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtKcalKthx1myIpklYH4j
9uQcmgQuZtKO4/beqK17QN3E4X3B/TIfX9twr0Uj3VuV7XNyh0SJxTNMnxzITCD3
807D/SBOAqn+y6GlBRK1BKloeioK58IRXR+fygbFAeOf+TTrBkQpNm/XOIS64U9t
479WZ+xILRvbfiUVc6hCIPUl8UkQ6Jdjm+v1CJk7Mh1IlIAcqrXqq5NG8xEY7HWM
7xRdX1A/KJlVBY03F86q/kTMrTPHBaChd3Egtdle/qKg9RKoCI4DTDV0f89+NT5y
YjJo8zJ2rNSmIr4Pf8nfQt1qDq48J5SsS4znEUuoqUSyBGgUAP5y2CP3U8Hoh8SZ
lQIDAQAB
-----END PUBLIC KEY-----

I verified the token in JWT.io and it was able to verify the signature. And I also tried the same application against Moodle and https://lti-ri.imsglobal.org. It was able to get the access token.

Using doOutcomesService

Hi,

thanks for a wonderful PHP implementation. This is making it possible to use LTI 1.3 in my webapp :-)

I am trying to set up the following:

1/ In Moodle, I have created a Tool that points to a webpage. Once the Tool is opened, the information is stored using the DataConnector.
2/ The user then does a number of things, and later on, my webservice will send back the score to the Moodle website.

I succeeded in setting up the Tool and the webpage. The information is stored in the database. However, now I am trying to send back the information to the Moodle site.

in config\lti.php I have enabled the score scope. I have created on my site a function then will handle sending the score to the Moodle site. This function first retrieves the user from the database, then the resourcelink and then uses $resourceLink->doOutcomesService to send back the outcome to the site.

This however does nothing :-(

I have checked $resourceLink->hasScoreService() and this returns false. Indeed, in the database, the settings field of the resourceLink is []... For example custom_lineitem_url does not exist. I have no clue what this actually should be?

I am a bit out of my depths here. It is quite complex to properly understand all the terminologies of LTI and grasp how to make this setup work with both Moodle and LTI.

Maybe you can help me on the way?

LTI: OAuth 2.0 Error

Hello,

I am using Celtic-project / LTI-PHP package for creating Tool Consumer for LTI Lunch URL But I got the below error message when I lunch LTI URL

Error : LTI: OAuth 2.0 Error

Please find attached the screen with more details
Screenshot_1

Invalid redirect url with the new 4.0.1 library

After upgrading from 4.0.0 to 4.0.1 I seem to be getting an error when trying to launch a resource link from within Blackboard saying 'Invalid redirect url: ...'. Something must have changed for this to happen. When I downgrade to 4.0.0 the problem goes away. I have not been able to pinpoint myself where this issue occurs but it must be one of the recent changes done to the library.

GET as Login Initiation URL

Hey guys, how are you?

I'm facing some issues trying to integrate my provider tool with Blackboard using LTI 1.3.

Looks like Blackboard sends a GET request to the Login Initiation URL and i'm facing the following error:
image

As you can see my code is very simple, i'm just trying to authenticate and send a Hello World.

I've managed to develop a function to authenticate by the GET request but this way I can't use any of the lib functions:
image

Here is the Blackboard documentation:
https://docs.anthology.com/docs/LTI/Tutorials/lti-lti_impl_guide#lti-launch

There is any way to use CeLTIc to authenticate on a GET request?

Thanks in advance!

Exception on request doOutcomesService with SSL certificate problem

We use LTI-PHP 4.5.0

Request doOutcomesService on url https://moodler2.bgpu.ru/mod/lti/service.php return Exception
DOMDocument::loadXML(): Argument #1 ($source) must not be empty
File: .../vendor/celtic/lti/src/ResourceLink.php; line: 1732

curl -d "" https://moodler2.bgpu.ru
say
curl: (60) SSL certificate problem: unable to get local issuer certificate

if append this lines
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
before
$chResp = curl_exec($ch);
in file celtic/lti/src/Http/CurlClient.php
resolve this problem.

How fix this problem?

Internationalisation?

Whilst this library mainly acts as a background connection between two servers, it does return some messages (mainly for errors) which are currently only available in English. Would anyone like to see these being provided in other languages? I would be happy to adapt the library to support this, but would be looking for volunteers to provide translations. Thanks.

Should the Platform handleAuthenticationRequest() method send an id_token?

Hello,

Does this LTI-PHP library include the code for a Platform to generate an id_token when using the handleAuthenticationRequest() method?

The 1EdTech Security Framework defines the following workflow based on the OIDC authentication exchange:
https://www.imsglobal.org/spec/security/v1p1#openid_connect_launch_flow

  1. the Platform starts a login request by sending a message to the Tool's third-party-initiated login endpoint
  2. the Tool sends an authentication request with login_hint and redirect_uri to the Platform's OIDC Authorization endpoint
  3. the Platform sends state and id_token values to the Tool's registered redirect_uri endpoint

The Platform handleAuthenticationRequest() method generates an HTML form with JavaScript to automatically submit the HTML form. However, I only see the state attribute being attached to the HTML form with an input tag. I do not see an id_token input being created. Is this an exercise that is left to the user to add to the form?

Sincerely,
Dan

User.php - can the hasRole() function be public?

src/User.php has a few functions (isAdmin(), isStaff(), isLearner()) to determine what class of user this is. These all use the hasRole() function internally.

Several of our LTI tools make more fine-grained distinctions than the User->isThing() functions allow for. For example, Content Developers can edit content but can't grade student work; Instructors can do both. Both user types right now are lumped into the isStaff() function and can't easily be distinguished by the app.

What do you think about making hasRole() public instead of private, so that interested tools could query specific roles without having to duplicate your role-parsing logic in the app itself?

JWK Signature Check Failing when 'alg' field is missing from JWK

We are having some trouble with a LTI Advantage integration with D2L because their public keyset doesn't contain an alg field (which is optional in the JWK spec). Upon launching we see a "JWT signature check failed". Upon debugging I found that the keyset isn't getting parsed because there is no alg field.

I can get the integration working if I remove the check for alg and specify "RSA256" as the algorithm but this doesn't seem like the right way to go about this. Any thoughts?

Assignment and Grade Services (AGS) OAuth2.0 Access Token Request and Storage

Dear Stephen Vickers,

I am using LTI 1.3/Advantage Assignment and Grade Services (AGS) routines to send grades from my tool to an LMS platform, and am receiving this error message:

Unable to obtain an access token for scope: https://purl.imsglobal.org/spec/lti-ags/scope/score

I suspect that my issue is that I need to first request an access token with the correct scope and store it in the lti2_access_token table. The AccessToken class has a get() method that handles fetching and storing a token from the platform, and the Service/Score class has a constant named $SCORE that holds the correct scope I want for the access token.

At first I thought that the problem was that I wasn't properly storing the $platform->accessTokenUrl value in the lti2_consumer table, but upon reviewing the DataConnector->fixPlatformSettings() method I saw that the lti2_consumer.settings JSON attribute named _oauth2_access_token_url should be translated to the $platform->accessTokenUrl value:

$platform->accessTokenUrl = $platform->getSetting('_oauth2_access_token_url', $platform->accessTokenUrl);

The problem seems to be with the Service class send() method not being able to fetch a version of the access token that has the required scope. We fetch the platform's token, check to see if it has the requested scope, fetch a new token with the requested scope added, check to see if the scope is attached, then try one last time to get a token with just the requested scope. The reason the token never adds the requested scope is because of this line in the AccessToken get() method:

if (!empty($url) && !empty(Tool::$defaultTool) && !empty(Tool::$defaultTool->rsaKey)) {

In my case the Tool::$defaultTool value is always null. This is probably because I have extended the Tool class in my system and have not set its static reference to point to my custom class. I have looked through the code and have not been able to find where the Tool::$defaultTool value is assigned a value. Perhaps I need to extend the AccessToken class as well and change the use section after the namespace to reference my custom class instead:

use App\LTI\myTool as Tool;

I will give this a try and see if it solves my access token issues. Static references make it difficult to extend a core class, but this may be the best way for me to make the necessary changes for my situation. Please let me know if you are aware of a better way to customize the Tool class without having to worry about static references used elsewhere in the code.

Sincerely,
Dan Hammari

SQL problem with GROUP BY clause in DataConnector query

I'm using a Postgres engine that has strict settings when parsing an SQL SELECT statement that has a GROUP BY clause. It requires that each field listed in the SELECT statement must have an aggregate function applied to it else be specifically listed in the SQL statement's GROUP BY clause. The query in lines 53-60 of file LTI/DataConnector/DataConnector_pdo.php has a GROUP BY clause that references only two of the twenty-three fields being asked for:

                $sql = 'SELECT consumer_pk, name, consumer_key, secret, ' .
                    'platform_id, client_id, deployment_id, public_key, ' .
                    'lti_version, signature_method, consumer_name, consumer_version, consumer_guid, ' .
                    'profile, tool_proxy, settings, protected, enabled, ' .
                    'enable_from, enable_until, last_access, created, updated ' .
                    "FROM {$this->dbTableNamePrefix}" . static::PLATFORM_TABLE_NAME . ' ' .
                    'WHERE (platform_id = :platform_id) ' .
                    'GROUP BY platform_id, client_id';

If the GROUP BY clause here is necessary, then it may help to refactor this query to be more universally acceptable across SQL engines that adhere to different levels of strictness. Here is the message that I currently receive from my Postgres SQL engine:

ERROR:  column "lti2_consumer.consumer_pk" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT consumer_pk, name, consumer_key, secret, platform_id,...
               ^
SQL state: 42803
Character: 8

Error in SQL for install

https://github.com/celtic-project/LTI-PHP/wiki/Installation

column user_pk does not exists in lti2_user_result

ALTER TABLE lti2_user_result
ADD CONSTRAINT lti2_user_result_lti2_user_FK1 FOREIGN KEY (user_pk)
REFERENCES lti2_user (user_pk);

ALTER TABLE lti2_user_result
ADD INDEX lti2_user_result_user_pk_IDX (user_pk ASC);

Duplicate key on write or update

ALTER TABLE lti2_share_key
ADD CONSTRAINT lti2_share_key_lti2_resource_link_FK1 FOREIGN KEY (resource_link_pk)
REFERENCES lti2_resource_link (resource_link_pk);

wrong return url for database connection errors for ContentItemSelectionRequest launches

If you simulate a database connection error (i.e. by setting $tool = new MyLtiLTI(null);) and you launch it with a message ContentItemSelectionRequest, the tools instead of doing a GET redirect to the launch_presentation_return_url it does a POST-form to content_item_return_url.

However, since the database is not working, the LMS returns an LTI/Oauth error, instead of the usual message displayed by the launch_presentation_return_url page.

Support for namespaced Outcome XML-message

The code currently does not support namespaced messages from an external outcome service.
We received a message like below:

<?xml version="1.0" encoding="utf-8"?>
<a2:imsx_POXEnvelopeResponse xmlns:a2="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
  <a2:imsx_POXHeader>
    <a2:imsx_POXResponseHeaderInfo>
      <a2:imsx_version>V1.0</a2:imsx_version>
      <a2:imsx_messageIdentifier>5849615-b48df-8945-4de5-a09cb7deb41c</a2:imsx_messageIdentifier>
      <a2:imsx_sendingAgentIdentifier />
      <a2:imsx_statusInfo>
        <a2:imsx_codeMajor>success</a2:imsx_codeMajor>
        <a2:imsx_severity>status</a2:imsx_severity>
        <a2:imsx_description />
        <a2:imsx_messageRefIdentifier />
      </a2:imsx_statusInfo>
    </a2:imsx_POXResponseHeaderInfo>
  </a2:imsx_POXHeader>
  <a2:imsx_POXBody />
</a2:imsx_POXEnvelopeResponse>

Which is perfectly valid, but does not meet the checks in doLTI11Service().

Is it possible to support these namespaced messages, one way or another?

Canvas authorize_redirect request never goes out

Howdy! I have read IMS and your documentation over and over though I feel like I'm shooting in the dark still :( Anyways, using saltire, I able to get a request to work, though only with a redirect uri that is http and not https, which is I'm sure giving the me the related error attached in the image and my tool loads after clicking send anyways.
Screenshot from 2022-05-19 14-55-57
However through Canvas, the hand shake request just seems to never get sent out.
Checking the logs on my tool and using your debugger, the last thing that is logged is this:

[19-May-2022 15:26:57 UTC] [DEBUG] Form submitted using POST to 'https://canvas.instructure.com/api/lti/authorize_redirect' with parameters of:
array (
  'client_id' => 'xxxxxxx',
  'login_hint' => 'xxxxxx',
  'nonce' => 'xxxxxx,
  'prompt' => 'none',
  'redirect_uri' => 'http://mytool.com/ui/lti-new',
  'response_mode' => 'form_post',
  'response_type' => 'id_token',
  'scope' => 'openid',
  'state' => 'xxxxxx',
  'lti_message_hint' => jtw_token_xxxxxxxxxxxxx.xxxxxx,
)

What might be causing this to get hung up?
Is there perhaps more code examples of using your library I could reference by chance? I am having a hard time understanding how to implement this standard.

Missing login_hint parameter

I'm not sure, but it seems the validation whether a login_hint is provided is too strict. For example, for some Moodle logins we receive the next parameters during login:

    [iss] => https://moodle.domain.tld
    [target_link_uri] => https://tool.domain.ltd/our_lti_endpoint/
    [login_hint] => 0
    [lti_message_hint] => 1344568
    [client_id] => aje8dLEm1dI2Uj
    [lti_deployment_id] => 14

Following the LTI 1.3 spec, this is valid, since there is a login_hint.
https://www.imsglobal.org/spec/lti/v1p3/#additional-login-parameters-0

But the check in the code seems to be too strict, and will result in a failed login attempt:
https://github.com/celtic-project/LTI-PHP/blob/master/src/Tool.php#L450

As empty() also classifies a string '0' as empty. Is it possible to use isset() here instead?

A fix in Moodle 3.9 in order to comply to LTI standar makes this lib not work anymore for basicoutcomes

I'm using this library succesfully to integrate with Moodle.

With version 3.9.1 of Moodle the basicoutcome service did work right in order to send scorings but it is broken in the actual 3.9.13+
In fact I think it was broken in 3.9.3.

The breakage is ought to those changes in Moodle:

https://tracker.moodle.org/browse/MDL-68384

By just changing src/Util.php to redefine the "lis_outcome_service_url" and "lis_result_sourcedid" so that it matches the ones that moodle uses the problem seems to go away.

So I guess the questions are:

  • Are they right at moodle? Is this library using a non standard claim URI?

If so we should probably fix this library in order to also use the correct claim mappings.

System::parseRoles fails to parse institution-level roles in LTI 1.3

LTI 1.3 allows system- and institution-level roles. When passed to System::parseRoles(), the return value contains things which are not valid LTI roles (and which are not recognized by helper functions such as User::isAdmin()). For example:
http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator
gets parsed into
/http://purl.imsglobal.org/vocab/lis/v2/institution#person#Administrator
(Note the leading slash and the slash-to-hash substitution at the end.)

This happens because parseRoles() is a big if/else chain which computes a $prefix variable based on what the role starts with, and there's nothing tellilng it what to do with system- or institution-level roles. In my own testing, the problem seems to be fixed by adding handling for those roles at the end of the if/else chain here:

                    } elseif (substr($role, 0, 52) === 'http://purl.imsglobal.org/vocab/lis/v2/system/person') {
                        $prefix = 'http://purl.imsglobal.org/vocab/lis/v2/system/person';
                        $role = substr($role, 52);
                    } elseif (substr($role, 0, 57) === 'http://purl.imsglobal.org/vocab/lis/v2/institution/person') {
                        $prefix = 'http://purl.imsglobal.org/vocab/lis/v2/institution/person';
                        $role = substr($role, 57);
                    }

This problem may exist in the parsing for LTI 1.0 and LTI 2 as well - I didn't look into those too deeply.

Outcomes 1.1 Service is never successful

I experienced some troubles using the outcomes service with this library.

Issue

The return value of ResourceLink.php::doOutcomesService() function is always false.

Environment

Outcomes url is provided by parameter lis_outcome_service_url.

Problem

The result of ResourceLink.php::doLTI11Service() is dropped after computing. In the following line the correct result is put to variable $ok, which is never processed thereafter:

$ok = true;

Solution

One proposal would be to write the value to $response instead. Or to pass the value of $ok to the hook parameters.

Private key(s) store location

Hello, Stephen,

I don't have any issue, but I have a question about using the library (Thanks for this very useful one !).
It seems to me that the idea is to have one set of private and public key per consumer to sign requests sent to the platform consumer. If that's right, what's the best solution to store the private keys of each consumer (the public one being in the public_key field of the consumers table) ?

And can you confirm that when the tool only offers the basiclti launch feature we don't need to configure any key for the consumers.
Thank you in advance.

Error while getting Message Params

A client wants to connect their system through LTI 1.3 to ours. Our tool uses CeLTIc to connect to it. The client has the platform.

When the client wants to run our tool, we get a fatal error:

ceLTIc\LTI\Tool::getMessageParameters(): Return value must be of type array, null returned
App \ Http \ Controllers \ LMS \ LtiTool : 152
createDeploymentIdFromExistingPlatform

--> $messageParms = collect($this->getMessageParameters());

$this->getMessageParameters() is null.

When I dig deeper, I see that at just before this error pops, the following parameters are being transmitted:

dd($this->getRawParameters());
array:3 [▼ // vendor/celtic/lti/src/Tool.php:417
"error" => "login_required"
"state" => "0Y...IV"
"session_state" => "Bkd128ZrUA1Pa0h...bBw.777814299A2187C8BE5BC7CF707AA248"
]

if ($this->messageParameters == null) {
dd($this);
}

App\Http\Controllers\LMS\LtiTool {#2531 ▼ // vendor/celtic/lti/src/Tool.php:435
+platform: null
+returnUrl: null
+userResult: null
+resourceLink: null
+context: null
+defaultEmail: ""
+allowSharing: false
+message: null
+baseUrl: null
+vendor: ceLTIc\LTI\Profile\Item {#2532 ▶}
+product: ceLTIc\LTI\Profile\Item {#2533 ▶}
+requiredServices: []
+optionalServices: []
+resourceHandlers: []
+messageUrl: null
+initiateLoginUrl: null
+redirectionUris: null
#redirectUrl: null
#mediaTypes: null
#contentTypes: null
#fileTypes: null
#documentTargets: null
#output: null
#errorOutput: null
-constraints: []
+ok: false
+ltiVersion: null
+name: null
+secret: null
+signatureMethod: "RS256"
+encryptionMethod: null
+dataConnector: ceLTIc\LTI\DataConnector\DataConnector_pdo_mysql {#2528 ▶}
+rsaKey: """
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----
"""
+requiredScopes: array:4 [▶]
+kid: "key-1"
+jku: null
+reason: "login_required"
+details: []
+warnings: []
+debugMode: false
+enabled: false
+enableFrom: null
+enableUntil: null
+lastAccess: null
+created: null
+updated: null
+idScope: ceLTIc\LTI\Enum\IdScope {#2530 ▶}
#jwt: null
#rawParameters: array:3 [▶]
#messageParameters: null
-id: null
-key: null
-settings: []
-settingsChanged: false
#launchType: ""
}

I do not fully understand where we are and why this specific information is being sent. In any case, CeLTIc goes bananas at that point :-)

To be honest, I don't know if the client implemented LTI 1.3 correctly, but they claim to be connected to other Tool providers through LTI 1.3 already. I do not have access to their code base.

Note: we already have a working system on Moodle through LTI 1.3. This makes me suspect something on the client side if going wrong, but that is difficult from this point to prove...

Any clue as to what is going wrong here?

lti2_nonce.consumer_pk with multi-tenant issuer

I'm running into trouble with the lti2_nonce table's consumer_pk field. Here is the workflow:

  1. platform sends request to tool's login URL with payload that has property claim iss as https://schoology.schoology.com
  2. tool's static function fromPlatformId receives this as $platformId while $clientId and $deploymentId are null
  3. tool's static function fromPlatformId calls DataConnector loadPlatform method using only the $platformId value
  4. tool now has instance of platform with recordId value 25 and builds an lti2_nonce entry with index consumer_pk 25
  5. tool sends request to platform's authentication URL with lti2_nonce value as state in payload
  6. platform receives request and responds with claims iss, aud, and deploymentId and also returns state nonce
  7. tool's static function fromPlatformId receives all three values as $platformId, $clientId, and $deploymentId
  8. tool's static function fromPlatformId calls DataConnector loadPlatform method using all three values
  9. tool now has instance of platform with recordId value 36 since it found an alternate consumer signature in the lti2_consumer table
  10. tool tries to find the nonce record that matches the state claim that was returned, but cannot since the consumer_pk value is now 36 instead of the 25 that was used when creating the lti2_nonce record

The lti2_consumer table seems to be built with the intention of allowing multiple instance of a platform_id value, provided that an alternate composite key of fields platform_id, client_id, and deployment_id remains unique. The problem becomes disambiguating multiple records when only the iss field is provided as a lookup. In particular, the lti2_nonce table relies on the correct foreign key value of consumer_pk when validating the returned state claim.

My solution right now is to alter the DataConnector loadPlatformNonce method and have it ignore the consumer_pk column when validating the state nonce that the platform returns:

BEFORE:

// Load the nonce
        $id = $nonce->getPlatform()->getRecordId();
        $value = $nonce->getValue();
        $sql = "SELECT value T FROM {$this->dbTableNamePrefix}" . static::NONCE_TABLE_NAME . ' WHERE (consumer_pk = :id) AND (value = :value)';
        $query = $this->db->prepare($sql);
        $query->bindValue('id', $id, \PDO::PARAM_INT);
        $query->bindValue('value', $value, \PDO::PARAM_STR);
        $ok = $this->executeQuery($sql, $query, false);

AFTER:

// Load the nonce
        $value = $nonce->getValue();
        $sql = "SELECT value T FROM {$this->dbTableNamePrefix}" . static::NONCE_TABLE_NAME . ' WHERE (value = :value)';
        $query = $this->db->prepare($sql);
        $query->bindValue('value', $value, \PDO::PARAM_STR);
        $ok = $this->executeQuery($sql, $query, false);

Let me know if this is not the correct direction to go. I imagine I will run into this issue repeatedly when registering platforms that host multiple tenants. As in the example above, Schoology will always send the same iss claim value to represent its platform, but each tenant will likely have its own aud claim (clientId), and multiple deployment_id claims. I can understand registering each tenant customer on the platform with its own client_id value, but hesitate to also record a uniquedeployment_id as well. Would it perhaps be okay to alter the DataConnector loadPlatform method and make it so the deployment_id field is not considered when searching for a platform entry in the lti2_consumer table?

Help with basic integration

I'm trying to integrate this library with Laravel using the rating app but I can't save the basic information of the platform
image
I would be very grateful if you could guide me

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.