From efae8d36d50af3120cbfc2cb2ec5a3beceabc6ac Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Thu, 2 Mar 2023 15:13:39 +0800 Subject: [PATCH] MDL-76926 libraries: upgrade lib/lti1p3 to patched v5.2.6 --- lib/lti1p3/README.md | 4 +- lib/lti1p3/src/Helpers/Helpers.php | 3 - lib/lti1p3/src/Interfaces/IHttpClient.php | 8 -- lib/lti1p3/src/Interfaces/IHttpException.php | 8 -- lib/lti1p3/src/Interfaces/IHttpResponse.php | 10 -- .../src/Interfaces/ILtiRegistration.php | 1 + .../src/Interfaces/ILtiServiceConnector.php | 4 +- .../src/Interfaces/IMessageValidator.php | 7 +- lib/lti1p3/src/Interfaces/IServiceRequest.php | 1 + lib/lti1p3/src/LtiAbstractService.php | 4 +- lib/lti1p3/src/LtiConstants.php | 5 + lib/lti1p3/src/LtiDeepLink.php | 4 +- lib/lti1p3/src/LtiGrade.php | 1 + lib/lti1p3/src/LtiMessageLaunch.php | 117 +++++++++--------- lib/lti1p3/src/LtiOidcLogin.php | 6 +- lib/lti1p3/src/LtiServiceConnector.php | 17 +-- .../AbstractMessageValidator.php | 35 ++++++ .../DeepLinkMessageValidator.php | 22 +--- .../ResourceMessageValidator.php | 25 +--- .../SubmissionReviewMessageValidator.php | 22 +--- 20 files changed, 146 insertions(+), 158 deletions(-) delete mode 100644 lib/lti1p3/src/Interfaces/IHttpClient.php delete mode 100644 lib/lti1p3/src/Interfaces/IHttpException.php delete mode 100644 lib/lti1p3/src/Interfaces/IHttpResponse.php create mode 100644 lib/lti1p3/src/MessageValidators/AbstractMessageValidator.php diff --git a/lib/lti1p3/README.md b/lib/lti1p3/README.md index 07b4efd3add..17d2e878ef7 100644 --- a/lib/lti1p3/README.md +++ b/lib/lti1p3/README.md @@ -4,13 +4,11 @@ A library used for building IMS-certified LTI 1.3 tool providers in PHP. This library is a fork of the [packbackbooks/lti-1-3-php-library](https://github.com/packbackbooks/lti-1-3-php-library), patched specifically for use in [Moodle](https://github.com/moodle/moodle). -It is currently based on version [5.2.1 of the packbackbooks/lti-1-3-php-library](https://github.com/packbackbooks/lti-1-3-php-library/releases/tag/v5.2.1) library. +It is currently based on version [5.2.6 of the packbackbooks/lti-1-3-php-library](https://github.com/packbackbooks/lti-1-3-php-library/releases/tag/v5.2.6) library. The following changes are included so that the library may be used with Moodle: * Replace the phpseclib dependency with openssl equivalent call in public key generation code. - * Replace the Guzzle dependency with generic HTTP client interfaces for client, response, exception. - * Small fix to http_build_query() calls, which now explicitly include the '&' arg separator param, for compatibility with applications that override PHP's arg_separator.output value via an ini_set() call, like Moodle does. Please see the original [README](https://github.com/packbackbooks/lti-1-3-php-library/blob/master/README.md) for more information about the upstream library. diff --git a/lib/lti1p3/src/Helpers/Helpers.php b/lib/lti1p3/src/Helpers/Helpers.php index ab4195cc13e..9c5267ac6c8 100644 --- a/lib/lti1p3/src/Helpers/Helpers.php +++ b/lib/lti1p3/src/Helpers/Helpers.php @@ -4,9 +4,6 @@ namespace Packback\Lti1p3\Helpers; class Helpers { - /** - * @param $value - */ public static function checkIfNullValue($value): bool { return !is_null($value); diff --git a/lib/lti1p3/src/Interfaces/IHttpClient.php b/lib/lti1p3/src/Interfaces/IHttpClient.php deleted file mode 100644 index bd4cbcd2334..00000000000 --- a/lib/lti1p3/src/Interfaces/IHttpClient.php +++ /dev/null @@ -1,8 +0,0 @@ -serviceConnector = $serviceConnector; $this->registration = $registration; $this->serviceData = $serviceData; diff --git a/lib/lti1p3/src/LtiConstants.php b/lib/lti1p3/src/LtiConstants.php index e157ab6b052..cd3388962f3 100644 --- a/lib/lti1p3/src/LtiConstants.php +++ b/lib/lti1p3/src/LtiConstants.php @@ -91,4 +91,9 @@ class LtiConstants public const COURSE_OFFERING = 'http://purl.imsglobal.org/vocab/lis/v2/course#CourseOffering'; public const COURSE_SECTION = 'http://purl.imsglobal.org/vocab/lis/v2/course#CourseSection'; public const COURSE_GROUP = 'http://purl.imsglobal.org/vocab/lis/v2/course#Group'; + + // Message Types + public const MESSAGE_TYPE_DEEPLINK = 'LtiDeepLinkingRequest'; + public const MESSAGE_TYPE_RESOURCE = 'LtiResourceLinkRequest'; + public const MESSAGE_TYPE_SUBMISSIONREVIEW = 'LtiSubmissionReviewRequest'; } diff --git a/lib/lti1p3/src/LtiDeepLink.php b/lib/lti1p3/src/LtiDeepLink.php index 7734c57b0dc..4ee8d7dae69 100644 --- a/lib/lti1p3/src/LtiDeepLink.php +++ b/lib/lti1p3/src/LtiDeepLink.php @@ -29,7 +29,9 @@ class LtiDeepLink LtiConstants::DEPLOYMENT_ID => $this->deployment_id, LtiConstants::MESSAGE_TYPE => 'LtiDeepLinkingResponse', LtiConstants::VERSION => LtiConstants::V1_3, - LtiConstants::DL_CONTENT_ITEMS => array_map(function ($resource) { return $resource->toArray(); }, $resources), + LtiConstants::DL_CONTENT_ITEMS => array_map(function ($resource) { + return $resource->toArray(); + }, $resources), ]; // https://www.imsglobal.org/spec/lti-dl/v2p0/#deep-linking-request-message diff --git a/lib/lti1p3/src/LtiGrade.php b/lib/lti1p3/src/LtiGrade.php index e7b321d78e8..1f3ff4ee513 100644 --- a/lib/lti1p3/src/LtiGrade.php +++ b/lib/lti1p3/src/LtiGrade.php @@ -12,6 +12,7 @@ class LtiGrade private $timestamp; private $user_id; private $submission_review; + private $canvas_extension; public function __construct(array $grade = null) { diff --git a/lib/lti1p3/src/LtiMessageLaunch.php b/lib/lti1p3/src/LtiMessageLaunch.php index ef2a3ed3cd3..db92c4d6aa5 100644 --- a/lib/lti1p3/src/LtiMessageLaunch.php +++ b/lib/lti1p3/src/LtiMessageLaunch.php @@ -2,13 +2,15 @@ namespace Packback\Lti1p3; +use Exception; use Firebase\JWT\ExpiredException; use Firebase\JWT\JWK; use Firebase\JWT\JWT; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\TransferException; use Packback\Lti1p3\Interfaces\ICache; use Packback\Lti1p3\Interfaces\ICookie; use Packback\Lti1p3\Interfaces\IDatabase; -use Packback\Lti1p3\Interfaces\IHttpException; use Packback\Lti1p3\Interfaces\ILtiServiceConnector; use Packback\Lti1p3\MessageValidators\DeepLinkMessageValidator; use Packback\Lti1p3\MessageValidators\ResourceMessageValidator; @@ -41,7 +43,6 @@ class LtiMessageLaunch public const ERR_MISSING_DEPLOYEMENT_ID = 'No deployment ID was specified'; public const ERR_NO_DEPLOYMENT = 'Unable to find deployment.'; public const ERR_INVALID_MESSAGE_TYPE = 'Invalid message type'; - public const ERR_VALIDATOR_CONFLICT = 'Validator conflict.'; public const ERR_UNRECOGNIZED_MESSAGE_TYPE = 'Unrecognized message type.'; public const ERR_INVALID_MESSAGE = 'Message validation failed.'; public const ERR_INVALID_ALG = 'Invalid alg was specified in the JWT header.'; @@ -69,10 +70,10 @@ class LtiMessageLaunch /** * Constructor. * - * @param IDatabase $database instance of the database interface used for looking up registrations and deployments - * @param ICache $cache instance of the Cache interface used to loading and storing launches - * @param ICookie $cookie instance of the Cookie interface used to set and read cookies - * @param ILtiServiceConnector $serviceConnector instance of the LtiServiceConnector used to by LTI services to make API requests + * @param IDatabase $database Instance of the database interface used for looking up registrations and deployments + * @param ICache $cache Instance of the Cache interface used to loading and storing launches + * @param ICookie $cookie Instance of the Cookie interface used to set and read cookies + * @param ILtiServiceConnector $serviceConnector Instance of the LtiServiceConnector used to by LTI services to make API requests */ public function __construct( IDatabase $database, @@ -104,19 +105,20 @@ class LtiMessageLaunch /** * Load an LtiMessageLaunch from a Cache using a launch id. * - * @param string $launch_id the launch id of the LtiMessageLaunch object that is being pulled from the cache - * @param IDatabase $database instance of the database interface used for looking up registrations and deployments + * @param string $launch_id The launch id of the LtiMessageLaunch object that is being pulled from the cache + * @param IDatabase $database Instance of the database interface used for looking up registrations and deployments * @param ICache $cache Instance of the Cache interface used to loading and storing launches. If non is provided launch data will be store in $_SESSION. * - * @throws LtiException will throw an LtiException if validation fails or launch cannot be found + * @throws LtiException Will throw an LtiException if validation fails or launch cannot be found * - * @return LtiMessageLaunch a populated and validated LtiMessageLaunch + * @return LtiMessageLaunch A populated and validated LtiMessageLaunch */ - public static function fromCache($launch_id, + public static function fromCache( + $launch_id, IDatabase $database, ICache $cache = null, - ILtiServiceConnector $serviceConnector = null) - { + ILtiServiceConnector $serviceConnector = null + ) { $new = new LtiMessageLaunch($database, $cache, null, $serviceConnector); $new->launch_id = $launch_id; $new->jwt = ['body' => $new->cache->getLaunchData($launch_id)]; @@ -129,9 +131,9 @@ class LtiMessageLaunch * * @param array|string $request An array of post request parameters. If not set will default to $_POST. * - * @throws LtiException will throw an LtiException if validation fails + * @throws LtiException Will throw an LtiException if validation fails * - * @return LtiMessageLaunch will return $this if validation is successful + * @return LtiMessageLaunch Will return $this if validation is successful */ public function validate(array $request = null) { @@ -153,7 +155,7 @@ class LtiMessageLaunch /** * Returns whether or not the current launch can use the names and roles service. * - * @return bool returns a boolean indicating the availability of names and roles + * @return bool Returns a boolean indicating the availability of names and roles */ public function hasNrps() { @@ -163,20 +165,21 @@ class LtiMessageLaunch /** * Fetches an instance of the names and roles service for the current launch. * - * @return LtiNamesRolesProvisioningService an instance of the names and roles service that can be used to make calls within the scope of the current launch + * @return LtiNamesRolesProvisioningService An instance of the names and roles service that can be used to make calls within the scope of the current launch */ public function getNrps() { return new LtiNamesRolesProvisioningService( $this->serviceConnector, $this->registration, - $this->jwt['body'][LtiConstants::NRPS_CLAIM_SERVICE]); + $this->jwt['body'][LtiConstants::NRPS_CLAIM_SERVICE] + ); } /** * Returns whether or not the current launch can use the groups service. * - * @return bool returns a boolean indicating the availability of groups + * @return bool Returns a boolean indicating the availability of groups */ public function hasGs() { @@ -186,20 +189,21 @@ class LtiMessageLaunch /** * Fetches an instance of the groups service for the current launch. * - * @return LtiCourseGroupsService an instance of the groups service that can be used to make calls within the scope of the current launch + * @return LtiCourseGroupsService An instance of the groups service that can be used to make calls within the scope of the current launch */ public function getGs() { return new LtiCourseGroupsService( $this->serviceConnector, $this->registration, - $this->jwt['body'][LtiConstants::GS_CLAIM_SERVICE]); + $this->jwt['body'][LtiConstants::GS_CLAIM_SERVICE] + ); } /** * Returns whether or not the current launch can use the assignments and grades service. * - * @return bool returns a boolean indicating the availability of assignments and grades + * @return bool Returns a boolean indicating the availability of assignments and grades */ public function hasAgs() { @@ -209,20 +213,21 @@ class LtiMessageLaunch /** * Fetches an instance of the assignments and grades service for the current launch. * - * @return LtiAssignmentsGradesService an instance of the assignments an grades service that can be used to make calls within the scope of the current launch + * @return LtiAssignmentsGradesService An instance of the assignments an grades service that can be used to make calls within the scope of the current launch */ public function getAgs() { return new LtiAssignmentsGradesService( $this->serviceConnector, $this->registration, - $this->jwt['body'][LtiConstants::AGS_CLAIM_ENDPOINT]); + $this->jwt['body'][LtiConstants::AGS_CLAIM_ENDPOINT] + ); } /** * Returns whether or not the current launch is a deep linking launch. * - * @return bool returns true if the current launch is a deep linking launch + * @return bool Returns true if the current launch is a deep linking launch */ public function isDeepLinkLaunch() { @@ -232,20 +237,21 @@ class LtiMessageLaunch /** * Fetches a deep link that can be used to construct a deep linking response. * - * @return LtiDeepLink an instance of a deep link to construct a deep linking response for the current launch + * @return LtiDeepLink An instance of a deep link to construct a deep linking response for the current launch */ public function getDeepLink() { return new LtiDeepLink( $this->registration, $this->jwt['body'][LtiConstants::DEPLOYMENT_ID], - $this->jwt['body'][LtiConstants::DL_DEEP_LINK_SETTINGS]); + $this->jwt['body'][LtiConstants::DL_DEEP_LINK_SETTINGS] + ); } /** * Returns whether or not the current launch is a submission review launch. * - * @return bool returns true if the current launch is a submission review launch + * @return bool Returns true if the current launch is a submission review launch */ public function isSubmissionReviewLaunch() { @@ -255,7 +261,7 @@ class LtiMessageLaunch /** * Returns whether or not the current launch is a resource launch. * - * @return bool returns true if the current launch is a resource launch + * @return bool Returns true if the current launch is a resource launch */ public function isResourceLaunch() { @@ -265,7 +271,7 @@ class LtiMessageLaunch /** * Fetches the decoded body of the JWT used in the current launch. * - * @return array|object returns the decoded json body of the launch as an array + * @return array|object Returns the decoded json body of the launch as an array */ public function getLaunchData() { @@ -275,7 +281,7 @@ class LtiMessageLaunch /** * Get the unique launch id for the current launch. * - * @return string a unique identifier used to re-reference the current launch in subsequent requests + * @return string A unique identifier used to re-reference the current launch in subsequent requests */ public function getLaunchId() { @@ -306,7 +312,7 @@ class LtiMessageLaunch // Download key set try { $response = $this->serviceConnector->makeRequest($request); - } catch (IHttpException $e) { + } catch (TransferException $e) { throw new LtiException(static::ERR_NO_PUBLIC_KEY); } $publicKeySet = $this->serviceConnector->getResponseBody($response); @@ -325,7 +331,7 @@ class LtiMessageLaunch $keySet = JWK::parseKeySet([ 'keys' => [$key], ]); - } catch (\Exception $e) { + } catch (Exception $e) { // Do nothing } @@ -484,36 +490,31 @@ class LtiMessageLaunch throw new LtiException(static::ERR_INVALID_MESSAGE_TYPE); } - /** - * @todo Fix this nonsense - */ + $validator = $this->getMessageValidator($this->jwt['body']); - // Create instances of all validators - $validators = [ - new DeepLinkMessageValidator(), - new ResourceMessageValidator(), - new SubmissionReviewMessageValidator(), - ]; - - $message_validator = false; - foreach ($validators as $validator) { - if ($validator->canValidate($this->jwt['body'])) { - if ($message_validator !== false) { - // Can't have more than one validator apply at a time. - throw new LtiException(static::ERR_VALIDATOR_CONFLICT); - } - $message_validator = $validator; - } - } - - if ($message_validator === false) { + if (!isset($validator)) { throw new LtiException(static::ERR_UNRECOGNIZED_MESSAGE_TYPE); } - if (!$message_validator->validate($this->jwt['body'])) { - throw new LtiException(static::ERR_INVALID_MESSAGE); - } + $validator::validate($this->jwt['body']); return $this; } + + private function getMessageValidator(array $jwtBody): ?string + { + $availableValidators = [ + DeepLinkMessageValidator::class, + ResourceMessageValidator::class, + SubmissionReviewMessageValidator::class, + ]; + + // Filter out validators that cannot validate the message + $applicableValidators = array_filter($availableValidators, function ($validator) use ($jwtBody) { + return $validator::canValidate($jwtBody); + }); + + // There should be 0-1 validators. This will either return the validator, or null if none apply. + return array_shift($applicableValidators); + } } diff --git a/lib/lti1p3/src/LtiOidcLogin.php b/lib/lti1p3/src/LtiOidcLogin.php index 18d3afd1372..2cea3a49f61 100644 --- a/lib/lti1p3/src/LtiOidcLogin.php +++ b/lib/lti1p3/src/LtiOidcLogin.php @@ -21,9 +21,9 @@ class LtiOidcLogin /** * Constructor. * - * @param IDatabase $database instance of the database interface used for looking up registrations and deployments - * @param ICache $cache Instance of the Cache interface used to loading and storing launches. If non is provided launch data will be store in $_SESSION. - * @param ICookie $cookie Instance of the Cookie interface used to set and read cookies. Will default to using $_COOKIE and setcookie. + * @param IDatabase $database Instance of the Database interface used for looking up registrations and deployments + * @param ICache $cache instance of the Cache interface used to loading and storing launches + * @param ICookie $cookie instance of the Cookie interface used to set and read cookies */ public function __construct(IDatabase $database, ICache $cache = null, ICookie $cookie = null) { diff --git a/lib/lti1p3/src/LtiServiceConnector.php b/lib/lti1p3/src/LtiServiceConnector.php index b206f5eeb0b..79d0ba434ef 100644 --- a/lib/lti1p3/src/LtiServiceConnector.php +++ b/lib/lti1p3/src/LtiServiceConnector.php @@ -2,11 +2,12 @@ namespace Packback\Lti1p3; +use Exception; use Firebase\JWT\JWT; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Psr7\Response; use Packback\Lti1p3\Interfaces\ICache; -use Packback\Lti1p3\Interfaces\IHttpClient; -use Packback\Lti1p3\Interfaces\IHttpException; -use Packback\Lti1p3\Interfaces\IHttpResponse; use Packback\Lti1p3\Interfaces\ILtiRegistration; use Packback\Lti1p3\Interfaces\ILtiServiceConnector; use Packback\Lti1p3\Interfaces\IServiceRequest; @@ -21,7 +22,7 @@ class LtiServiceConnector implements ILtiServiceConnector public function __construct( ICache $cache, - IHttpClient $client + Client $client ) { $this->cache = $cache; $this->client = $client; @@ -100,7 +101,7 @@ class LtiServiceConnector implements ILtiServiceConnector return $response; } - public function getResponseHeaders(IHttpResponse $response): ?array + public function getResponseHeaders(Response $response): ?array { $responseHeaders = $response->getHeaders(); array_walk($responseHeaders, function (&$value) { @@ -110,7 +111,7 @@ class LtiServiceConnector implements ILtiServiceConnector return $responseHeaders; } - public function getResponseBody(IHttpResponse $response): ?array + public function getResponseBody(Response $response): ?array { $responseBody = (string) $response->getBody(); @@ -127,7 +128,7 @@ class LtiServiceConnector implements ILtiServiceConnector try { $response = $this->makeRequest($request); - } catch (IHttpException $e) { + } catch (ClientException $e) { $status = $e->getResponse()->getStatusCode(); // If the error was due to invalid authentication and the request @@ -156,7 +157,7 @@ class LtiServiceConnector implements ILtiServiceConnector string $key = null ): array { if ($request->getMethod() !== ServiceRequest::METHOD_GET) { - throw new \Exception('An invalid method was specified by an LTI service requesting all items.'); + throw new Exception('An invalid method was specified by an LTI service requesting all items.'); } $results = []; diff --git a/lib/lti1p3/src/MessageValidators/AbstractMessageValidator.php b/lib/lti1p3/src/MessageValidators/AbstractMessageValidator.php new file mode 100644 index 00000000000..7c52929171a --- /dev/null +++ b/lib/lti1p3/src/MessageValidators/AbstractMessageValidator.php @@ -0,0 +1,35 @@ +