oat-sa / lib-lti1p3-core

PHP library for LTI 1.3 Core implementations as platforms and / or as tools.
GNU General Public License v2.0
33 stars 16 forks source link

Cache Launch message #133

Closed theVannu closed 3 years ago

theVannu commented 3 years ago

Hi all, I would like to know if this library offer some strategy for caching the message Launch in order to retrieve message launch data during subsequent requests, for example consider this Deep Linking Scenario:

Have I to implement my own this strategy? Or do you offer something? Hope all is clear,

ekkinox commented 3 years ago

Hello @theVannu ,

As I understand, you want to use the LTI 1.3 framework as a tool, to react to deep linking requests coming form a platform, am I right ?

If yes, did you first check the dedicated deep linking library for this ? You can find in the documentation how to implement your part of the deep linking flow as a tool in a easier way than just using the core library.

You also have in the framework a LTI 1.3 DevKit available (that can act as both platform and tool, depending on what you need), that can be used to test your LTI implementations.


To come back to your issue: usually, when launched via deep linking, tools offer the content item selection interface depending on the deep linking settings claim values. There is no need to cache those, because they extract the settings and offer the selection interface accordingly during the same request handling (so you have access to them).

There is 2 exceptions:

Both can be put in hidden form inputs in your content item selection form that you offer to the users, and on form submission you can retrieve them to build the deep linking response to send to the platform.

But if you still need to access the message payload from the deep linking request for other reasons, at this time, there is indeed no proper way to cache / store it.

Still, I can see one workaround: caching the id_token JWT string itself and use it later to recreate the payload

<?php

use OAT\Library\Lti1p3Core\Message\Launch\Validator\Result\LaunchValidationResult;
use OAT\Library\Lti1p3Core\Message\Payload\LtiMessagePayload;
use OAT\Library\Lti1p3Core\Security\Jwt\Parser\Parser;

// 1 - when you receive the deep linking request
$result = new LaunchValidationResult(...); // got from the tool launch validation with validatePlatformOriginatingLaunch()
$tokenString = $result->getPayload()->getToken()->toString(); // LTI id_token JWT string that you need to cache

// 2 -  when you need to build the deep linking response (async)
$tokenString = ...; // read from cache the LTI id_token JWT string you previously cached
$token = (new Parser())->parse($tokenString); // use core lib parser to create a token object from cached string
$payload = new LtiMessagePayload($token); // and build a LTI message payload from this token object
// from here, you can access all LTI claims you need from the cached message payload, for example
echo $payload->getDeepLinkingSettings()->getData();

Other solutions, maybe cleaner:

What do you think of this ?

theVannu commented 3 years ago

Hi, thank you for your support. Probably the hidden form input idea is the right way. But I want to show you few line of codes, let me know what do you think about it.

Consider that the following code is Laravel based The following are 2 methods inside a controller class, the show method accept a Deep Linking Request, validate the data, store the validated data into Cache using a uuid Key and finally return a view (passing the uuid key) that show the Item Selection Content.

The user now choose the Items and press a button, this button launch a request managed by sendItem method. This method retrieve the message launch data previously stored into the Cache (using the $launch_id parameters pass as hidden form input). Using this data I'm going to create a DeepLinkingLaunchResponseBuilder().

As you can see I have mixed your form input idea with the cache idea. I see one critical point, if the Cache expire I loose message launch data (error 500), but the good thing is that retrieving data from Cache allows me to avoid to make a query for retrieving registration from my DB.

What do you think?

Thank you, Gian Luca

`public function show(ServerRequestInterface $request) {
        $result = $this->validateLaunch($request, $this->regRepo, $this->nonceRepo);
        $launch_id = 'a'.\Str::uuid();
        \Cache::put($launch_id, $result);
        return view('deepLink', ['launchId' => $launch_id]);
    }`
public function sendItem(Request $request) {
        /**@var LaunchValidationResultInterface $messageLaunch */
        $messageLaunch = \Cache::get($request->input('launchId'));
        if(!$messageLaunch)
            abort(500, 'cache expired');

        $linkBooks = new Link('linkIdentifierBook', route('content.books'));
        $linkQuiz = new Link('linkIdentifierQuiz', route('content.quiz'));
        /* $ltiResourceLink = new LtiResourceLink('ltiResourceLinkIdentifier', ['url' => 'http://tool.com/launch']); 

        // Aggregate them in a collection
        $resourceCollection = new ResourceCollection();
        $resourceCollection
            ->add($linkBooks)
            ->add($linkQuiz);

        /** send response to platform*/
        $builder = new DeepLinkingLaunchResponseBuilder();
        $deepLinkingSettingsClaim = $messageLaunch->getPayload()->getDeepLinkingSettings();

        $registration = $messageLaunch->getRegistration();

        $message = $builder->buildDeepLinkingLaunchResponse(
            $resourceCollection,                                   // [required] content items collection
            $registration,                                         // [required] related registration
            $deepLinkingSettingsClaim->getDeepLinkingReturnUrl(),  // [required] platform url whereto return content items
            null,                                                  // [optional] will use the registration default deployment id, but you can pass a specific one
            $deepLinkingSettingsClaim->getData(),                  // [optional] platform settings data, must be returned unaltered if provided
            '2 content items provided with success'                // [optional] to override the default feedback message
        );

        echo $message->toHtmlRedirectForm();
}
ekkinox commented 3 years ago

Hi @theVannu ,

First of all thanks for your detailed answer, and your interest in this LTI framework :)

Do the launch result caching you do with

$result = $this->validateLaunch($request, $this->regRepo, $this->nonceRepo);
$launch_id = 'a'.\Str::uuid();
\Cache::put($launch_id, $result);

allow you to retrieve all launch details (especially the registration) when you later read from cache without any problem ? I mean is there some serialization issue in this process that would make data unreachable when you read from cache ? So I would need to adapt a bit the models to allow them to be properly serialized / cached ?

If not, I think your way of doing is good, and indeed, if you store your registrations in DB, you spare a DB read. And for the unreachable cache issue (leading to a 500), maybe you can chain some caches to make it more robust on your end.

The only concern I have would be that if you put in cache for a too long TTL, and either you or the platfiorm changes the registration data in between (security key rotation, change of auth endpoints, etc), you may end up with something in cache that is not matching the reality.

So:

I think you're good to go ! :)

Let me know, and if all is good on your side, please tell me so I can close this issue.

theVannu commented 3 years ago

Hi @ekkinox , I think we can close this issue. Just the last clarification, I ask this question because I have read this link and this link. But probably caching message launch is NOT an LTI standard request. Thank you again

ekkinox commented 3 years ago

Indeed, the LTI specs afaik does not say anything about caching, I think caching is a spec implementation detail. And if it works for you, we're good.

Thx for your feedback, I close the issue, don't hesitate to come back if you have any other concern :)