From e2cf58cf33211b2e7835e7a201e70805bb711476 Mon Sep 17 00:00:00 2001 From: Davo Hynds Date: Wed, 13 Dec 2023 11:46:33 -0600 Subject: [PATCH 1/6] Add a getOidcLoginUrl method and prep for v6 --- src/Lti1p1Key.php | 2 +- src/LtiAbstractService.php | 2 +- src/LtiAssignmentsGradesService.php | 6 ++-- src/LtiDeepLinkDateTimeInterval.php | 2 +- src/LtiDeepLinkResourceIframe.php | 2 +- src/LtiDeepLinkResourceWindow.php | 2 +- src/LtiGrade.php | 2 +- src/LtiGradeSubmissionReview.php | 2 +- src/LtiLineitem.php | 2 +- src/LtiMessageLaunch.php | 20 +++++------ src/LtiOidcLogin.php | 35 +++++++++++++------ src/LtiServiceConnector.php | 2 +- src/Redirect.php | 2 +- .../Certification/Lti13CertificationTest.php | 2 +- 14 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/Lti1p1Key.php b/src/Lti1p1Key.php index 7bd5026e..b67a699d 100644 --- a/src/Lti1p1Key.php +++ b/src/Lti1p1Key.php @@ -12,7 +12,7 @@ class Lti1p1Key private $key; private $secret; - public function __construct(array $key = null) + public function __construct(?array $key = null) { $this->key = $key['key'] ?? null; $this->secret = $key['secret'] ?? null; diff --git a/src/LtiAbstractService.php b/src/LtiAbstractService.php index 0f33d3ad..8f08dcfa 100644 --- a/src/LtiAbstractService.php +++ b/src/LtiAbstractService.php @@ -52,7 +52,7 @@ protected function makeServiceRequest(IServiceRequest $request): array ); } - protected function getAll(IServiceRequest $request, string $key = null): array + protected function getAll(IServiceRequest $request, ?string $key = null): array { return $this->serviceConnector->getAll( $this->registration, diff --git a/src/LtiAssignmentsGradesService.php b/src/LtiAssignmentsGradesService.php index 52035ecb..9700684e 100644 --- a/src/LtiAssignmentsGradesService.php +++ b/src/LtiAssignmentsGradesService.php @@ -28,7 +28,7 @@ public function getResourceLaunchLineItem(): ?LtiLineitem return LtiLineitem::new()->setId($serviceData['lineitem']); } - public function putGrade(LtiGrade $grade, LtiLineitem $lineitem = null) + public function putGrade(LtiGrade $grade, ?LtiLineitem $lineitem = null) { $this->validateScopes([LtiConstants::AGS_SCOPE_SCORE]); @@ -107,7 +107,7 @@ public function findOrCreateLineitem(LtiLineitem $newLineItem): LtiLineitem return $this->findLineItem($newLineItem) ?? $this->createLineitem($newLineItem); } - public function getGrades(LtiLineitem $lineitem = null) + public function getGrades(?LtiLineitem $lineitem = null) { $lineitem = $this->ensureLineItemExists($lineitem); $resultsUrl = $this->appendLineItemPath($lineitem, '/results'); @@ -159,7 +159,7 @@ public function getLineItem(string $url): LtiLineitem return new LtiLineitem($response); } - private function ensureLineItemExists(LtiLineitem $lineitem = null): LtiLineitem + private function ensureLineItemExists(?LtiLineitem $lineitem = null): LtiLineitem { // If no line item is passed in, attempt to use the one associated with // this launch. diff --git a/src/LtiDeepLinkDateTimeInterval.php b/src/LtiDeepLinkDateTimeInterval.php index b26c6687..8639c4aa 100644 --- a/src/LtiDeepLinkDateTimeInterval.php +++ b/src/LtiDeepLinkDateTimeInterval.php @@ -9,7 +9,7 @@ class LtiDeepLinkDateTimeInterval private ?DateTime $start; private ?DateTime $end; - public function __construct(DateTime $start = null, DateTime $end = null) + public function __construct(?DateTime $start = null, ?DateTime $end = null) { if ($start !== null && $end !== null && $end < $start) { throw new LtiException('Interval start time cannot be greater than end time'); diff --git a/src/LtiDeepLinkResourceIframe.php b/src/LtiDeepLinkResourceIframe.php index 3346c31a..de60ec3f 100644 --- a/src/LtiDeepLinkResourceIframe.php +++ b/src/LtiDeepLinkResourceIframe.php @@ -8,7 +8,7 @@ class LtiDeepLinkResourceIframe private ?int $height; private ?string $src; - public function __construct(int $width = null, int $height = null, string $src = null) + public function __construct(?int $width = null, ?int $height = null, ?string $src = null) { $this->width = $width ?? null; $this->height = $height ?? null; diff --git a/src/LtiDeepLinkResourceWindow.php b/src/LtiDeepLinkResourceWindow.php index 323d706c..194f32a7 100644 --- a/src/LtiDeepLinkResourceWindow.php +++ b/src/LtiDeepLinkResourceWindow.php @@ -9,7 +9,7 @@ class LtiDeepLinkResourceWindow private ?int $height; private ?string $window_features; - public function __construct(string $targetName = null, int $width = null, int $height = null, string $windowFeatures = null) + public function __construct(?string $targetName = null, ?int $width = null, ?int $height = null, ?string $windowFeatures = null) { $this->target_name = $targetName ?? null; $this->width = $width ?? null; diff --git a/src/LtiGrade.php b/src/LtiGrade.php index bca75be8..d3f0e18a 100644 --- a/src/LtiGrade.php +++ b/src/LtiGrade.php @@ -14,7 +14,7 @@ class LtiGrade private $submission_review; private $canvas_extension; - public function __construct(array $grade = null) + public function __construct(?array $grade = null) { $this->score_given = $grade['scoreGiven'] ?? null; $this->score_maximum = $grade['scoreMaximum'] ?? null; diff --git a/src/LtiGradeSubmissionReview.php b/src/LtiGradeSubmissionReview.php index a185930f..335590a7 100644 --- a/src/LtiGradeSubmissionReview.php +++ b/src/LtiGradeSubmissionReview.php @@ -9,7 +9,7 @@ class LtiGradeSubmissionReview private $url; private $custom; - public function __construct(array $gradeSubmission = null) + public function __construct(?array $gradeSubmission = null) { $this->reviewable_status = $gradeSubmission['reviewableStatus'] ?? null; $this->label = $gradeSubmission['label'] ?? null; diff --git a/src/LtiLineitem.php b/src/LtiLineitem.php index 9492c1a6..4223dedc 100644 --- a/src/LtiLineitem.php +++ b/src/LtiLineitem.php @@ -14,7 +14,7 @@ class LtiLineitem private $end_date_time; private ?bool $grades_released; - public function __construct(array $lineitem = null) + public function __construct(?array $lineitem = null) { $this->id = $lineitem['id'] ?? null; $this->score_maximum = $lineitem['scoreMaximum'] ?? null; diff --git a/src/LtiMessageLaunch.php b/src/LtiMessageLaunch.php index 2f15c69e..d2e00a7e 100644 --- a/src/LtiMessageLaunch.php +++ b/src/LtiMessageLaunch.php @@ -80,9 +80,9 @@ class LtiMessageLaunch */ public function __construct( IDatabase $database, - ICache $cache = null, - ICookie $cookie = null, - ILtiServiceConnector $serviceConnector = null + ?ICache $cache = null, + ?ICookie $cookie = null, + ?ILtiServiceConnector $serviceConnector = null ) { $this->db = $database; @@ -98,9 +98,9 @@ public function __construct( */ public static function new( IDatabase $database, - ICache $cache = null, - ICookie $cookie = null, - ILtiServiceConnector $serviceConnector = null + ?ICache $cache = null, + ?ICookie $cookie = null, + ?ILtiServiceConnector $serviceConnector = null ) { return new LtiMessageLaunch($database, $cache, $cookie, $serviceConnector); } @@ -118,8 +118,8 @@ public static function new( public static function fromCache( $launch_id, IDatabase $database, - ICache $cache = null, - ILtiServiceConnector $serviceConnector = null + ?ICache $cache = null, + ?ILtiServiceConnector $serviceConnector = null ) { // @todo: Fix the null here on the next major version $new = new LtiMessageLaunch($database, $cache, null, $serviceConnector); @@ -153,7 +153,7 @@ public function initialize(array $request) * * @throws LtiException Will throw an LtiException if validation fails */ - public function validate(array $request = null) + public function validate(?array $request = null) { // @TODO: Remove this on the next major release. if (!isset($this->request)) { @@ -337,7 +337,7 @@ public function getLaunchId() return $this->launch_id; } - public static function getMissingRegistrationErrorMsg(string $issuerUrl, string $clientId = null): string + public static function getMissingRegistrationErrorMsg(string $issuerUrl, ?string $clientId = null): string { // Guard against client ID being null if (!isset($clientId)) { diff --git a/src/LtiOidcLogin.php b/src/LtiOidcLogin.php index f6582107..3dd16ac6 100644 --- a/src/LtiOidcLogin.php +++ b/src/LtiOidcLogin.php @@ -16,6 +16,7 @@ class LtiOidcLogin private $db; private $cache; private $cookie; + private array $request; /** * Constructor. @@ -24,7 +25,7 @@ class LtiOidcLogin * @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) + public function __construct(IDatabase $database, ?ICache $cache = null, ?ICookie $cookie = null) { $this->db = $database; $this->cache = $cache; @@ -34,11 +35,16 @@ public function __construct(IDatabase $database, ICache $cache = null, ICookie $ /** * Static function to allow for method chaining without having to assign to a variable first. */ - public static function new(IDatabase $database, ICache $cache = null, ICookie $cookie = null) + public static function new(IDatabase $database, ?ICache $cache = null, ?ICookie $cookie = null) { return new LtiOidcLogin($database, $cache, $cookie); } + public function setRequest(array $request) + { + $this->request = $request; + } + /** * Calculate the redirect location to return to based on an OIDC third party initiated login request. * @@ -46,19 +52,29 @@ public static function new(IDatabase $database, ICache $cache = null, ICookie $c * @param array $request An array of request parameters. If not set will default to $_REQUEST. * @return Redirect returns a redirect object containing the fully formed OIDC login URL */ - public function doOidcLoginRedirect($launchUrl, array $request = null) + public function doOidcLoginRedirect($launchUrl, ?array $request = null) { // @todo remove this in v6.0 if ($request === null) { $request = $_REQUEST; } + $this->setRequest($request); + + $authLoginReturnUrl = $this->getOidcLoginUrl($launchUrl); + + // Return auth redirect. + return new Redirect($authLoginReturnUrl); + } + + public function getOidcLoginUrl($launchUrl) + { if (empty($launchUrl)) { throw new OidcException(static::ERROR_MSG_LAUNCH_URL, 1); } // Validate Request Data. - $registration = $this->validateOidcLogin($request); + $registration = $this->validateOidcLogin($this->request); /* * Build OIDC Auth Response. @@ -83,19 +99,16 @@ public function doOidcLoginRedirect($launchUrl, array $request = null) 'redirect_uri' => $launchUrl, // URL to return to after login. 'state' => $state, // State to identify browser session. 'nonce' => $nonce, // Prevent replay attacks. - 'login_hint' => $request['login_hint'], // Login hint to identify platform session. + 'login_hint' => $this->request['login_hint'], // Login hint to identify platform session. ]; // Pass back LTI message hint if we have it. - if (isset($request['lti_message_hint'])) { + if (isset($this->request['lti_message_hint'])) { // LTI message hint to identify LTI context within the platform. - $authParams['lti_message_hint'] = $request['lti_message_hint']; + $authParams['lti_message_hint'] = $this->request['lti_message_hint']; } - $authLoginReturnUrl = Helpers::buildUrlWithQueryParams($registration->getAuthLoginUrl(), $authParams); - - // Return auth redirect. - return new Redirect($authLoginReturnUrl); + return Helpers::buildUrlWithQueryParams($registration->getAuthLoginUrl(), $authParams); } public function validateOidcLogin($request) diff --git a/src/LtiServiceConnector.php b/src/LtiServiceConnector.php index 9cff50d3..16bb9478 100644 --- a/src/LtiServiceConnector.php +++ b/src/LtiServiceConnector.php @@ -154,7 +154,7 @@ public function getAll( ILtiRegistration $registration, array $scopes, IServiceRequest $request, - string $key = null + ?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.'); diff --git a/src/Redirect.php b/src/Redirect.php index f90f6961..fa4ba87a 100644 --- a/src/Redirect.php +++ b/src/Redirect.php @@ -10,7 +10,7 @@ class Redirect private $referer_query; private static $CAN_302_COOKIE = 'LTI_302_Redirect'; - public function __construct(string $location, string $referer_query = null) + public function __construct(string $location, ?string $referer_query = null) { $this->location = $location; $this->referer_query = $referer_query; diff --git a/tests/Certification/Lti13CertificationTest.php b/tests/Certification/Lti13CertificationTest.php index 3c094727..14575d83 100644 --- a/tests/Certification/Lti13CertificationTest.php +++ b/tests/Certification/Lti13CertificationTest.php @@ -581,7 +581,7 @@ public function testValidCertificationCases() $this->assertEquals($casesCount, $testedCases); } - private function launch($payload, IDatabase $db = null) + private function launch($payload, ?IDatabase $db = null) { $db = $db ?? $this->db; From f397c3f4a508219bedb0277ed1dfa1ffbbb76713 Mon Sep 17 00:00:00 2001 From: Davo Hynds Date: Wed, 13 Dec 2023 11:52:24 -0600 Subject: [PATCH 2/6] Make it more stateless --- src/LtiOidcLogin.php | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/LtiOidcLogin.php b/src/LtiOidcLogin.php index 3dd16ac6..fe53e6e8 100644 --- a/src/LtiOidcLogin.php +++ b/src/LtiOidcLogin.php @@ -16,7 +16,6 @@ class LtiOidcLogin private $db; private $cache; private $cookie; - private array $request; /** * Constructor. @@ -40,11 +39,6 @@ public static function new(IDatabase $database, ?ICache $cache = null, ?ICookie return new LtiOidcLogin($database, $cache, $cookie); } - public function setRequest(array $request) - { - $this->request = $request; - } - /** * Calculate the redirect location to return to based on an OIDC third party initiated login request. * @@ -59,22 +53,20 @@ public function doOidcLoginRedirect($launchUrl, ?array $request = null) $request = $_REQUEST; } - $this->setRequest($request); - - $authLoginReturnUrl = $this->getOidcLoginUrl($launchUrl); + $authLoginReturnUrl = $this->getOidcLoginUrl($launchUrl, $request); // Return auth redirect. return new Redirect($authLoginReturnUrl); } - public function getOidcLoginUrl($launchUrl) + public function getOidcLoginUrl($launchUrl, array $request) { if (empty($launchUrl)) { throw new OidcException(static::ERROR_MSG_LAUNCH_URL, 1); } // Validate Request Data. - $registration = $this->validateOidcLogin($this->request); + $registration = $this->validateOidcLogin($request); /* * Build OIDC Auth Response. @@ -99,13 +91,13 @@ public function getOidcLoginUrl($launchUrl) 'redirect_uri' => $launchUrl, // URL to return to after login. 'state' => $state, // State to identify browser session. 'nonce' => $nonce, // Prevent replay attacks. - 'login_hint' => $this->request['login_hint'], // Login hint to identify platform session. + 'login_hint' => $request['login_hint'], // Login hint to identify platform session. ]; // Pass back LTI message hint if we have it. - if (isset($this->request['lti_message_hint'])) { + if (isset($request['lti_message_hint'])) { // LTI message hint to identify LTI context within the platform. - $authParams['lti_message_hint'] = $this->request['lti_message_hint']; + $authParams['lti_message_hint'] = $request['lti_message_hint']; } return Helpers::buildUrlWithQueryParams($registration->getAuthLoginUrl(), $authParams); From 1e1a3b4dabbc195df1d3dac60f9f216c2da3e0f3 Mon Sep 17 00:00:00 2001 From: Davo Hynds Date: Wed, 13 Dec 2023 12:01:06 -0600 Subject: [PATCH 3/6] Mark redirect for deprecation --- src/LtiOidcLogin.php | 24 ++++++++++++------------ src/Redirect.php | 4 ++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/LtiOidcLogin.php b/src/LtiOidcLogin.php index fe53e6e8..079cca95 100644 --- a/src/LtiOidcLogin.php +++ b/src/LtiOidcLogin.php @@ -40,31 +40,31 @@ public static function new(IDatabase $database, ?ICache $cache = null, ?ICookie } /** - * Calculate the redirect location to return to based on an OIDC third party initiated login request. - * - * @param string $launchUrl URL to redirect back to after the OIDC login. This URL must match exactly a URL white listed in the platform. - * @param array $request An array of request parameters. If not set will default to $_REQUEST. - * @return Redirect returns a redirect object containing the fully formed OIDC login URL + * @deprecated Use getRedirectUrl() to get the URL and then redirect to it yourself */ public function doOidcLoginRedirect($launchUrl, ?array $request = null) { - // @todo remove this in v6.0 + trigger_error('Method '.__METHOD__.' is deprecated', E_USER_DEPRECATED); + if ($request === null) { $request = $_REQUEST; } - $authLoginReturnUrl = $this->getOidcLoginUrl($launchUrl, $request); + $authLoginReturnUrl = $this->getRedirectUrl($launchUrl, $request); // Return auth redirect. return new Redirect($authLoginReturnUrl); } - public function getOidcLoginUrl($launchUrl, array $request) + /** + * Calculate the redirect location to return to based on an OIDC third party initiated login request. + * + * @param string $launchUrl URL to redirect back to after the OIDC login. This URL must match exactly a URL white listed in the platform. + * @param array $request An array of request parameters. + * @return string returns the fully formed OIDC login URL + */ + public function getRedirectUrl(string $launchUrl, array $request): string { - if (empty($launchUrl)) { - throw new OidcException(static::ERROR_MSG_LAUNCH_URL, 1); - } - // Validate Request Data. $registration = $this->validateOidcLogin($request); diff --git a/src/Redirect.php b/src/Redirect.php index fa4ba87a..2e48cd4b 100644 --- a/src/Redirect.php +++ b/src/Redirect.php @@ -4,6 +4,9 @@ use Packback\Lti1p3\Interfaces\ICookie; +/** + * @deprecated Use LtiOidcLogin::getRedirectUrl() to get the URL and then redirect to it yourself + */ class Redirect { private $location; @@ -18,6 +21,7 @@ public function __construct(string $location, ?string $referer_query = null) public function doRedirect() { + trigger_error('Method '.__METHOD__.' is deprecated', E_USER_DEPRECATED); header('Location: '.$this->location, true, 302); exit; } From 1b2013eb45359c32c21c2451eb6239608ded5cb9 Mon Sep 17 00:00:00 2001 From: Davo Hynds Date: Wed, 13 Dec 2023 12:05:19 -0600 Subject: [PATCH 4/6] Add a to-do --- src/LtiOidcLogin.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/LtiOidcLogin.php b/src/LtiOidcLogin.php index 079cca95..a3d94c13 100644 --- a/src/LtiOidcLogin.php +++ b/src/LtiOidcLogin.php @@ -13,6 +13,10 @@ class LtiOidcLogin public const ERROR_MSG_LAUNCH_URL = 'No launch URL configured'; public const ERROR_MSG_ISSUER = 'Could not find issuer'; public const ERROR_MSG_LOGIN_HINT = 'Could not find login hint'; + + /** + * @todo Type these in v6 + */ private $db; private $cache; private $cookie; @@ -26,6 +30,9 @@ class LtiOidcLogin */ public function __construct(IDatabase $database, ?ICache $cache = null, ?ICookie $cookie = null) { + /** + * @todo Make these arguments not nullable in v6 + */ $this->db = $database; $this->cache = $cache; $this->cookie = $cookie; From e6ba7eeef5068b2031de3294e580d78e2ca349c4 Mon Sep 17 00:00:00 2001 From: Davo Hynds Date: Fri, 15 Dec 2023 10:07:03 -0600 Subject: [PATCH 5/6] PR Feedback --- composer.json | 2 +- src/LtiOidcLogin.php | 6 +++++- src/Redirect.php | 8 ++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index b97a97de..53515b77 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "phpseclib/phpseclib": "^3.0" }, "require-dev": { - "jubeki/laravel-code-style": "^1.0|^2.0", + "jubeki/laravel-code-style": "^2.0", "mockery/mockery": "^1.4", "nesbot/carbon": "^2.43", "phpunit/phpunit": "^9.5" diff --git a/src/LtiOidcLogin.php b/src/LtiOidcLogin.php index a3d94c13..c6661068 100644 --- a/src/LtiOidcLogin.php +++ b/src/LtiOidcLogin.php @@ -47,7 +47,7 @@ public static function new(IDatabase $database, ?ICache $cache = null, ?ICookie } /** - * @deprecated Use getRedirectUrl() to get the URL and then redirect to it yourself + * @deprecated Use getRedirectUrl() to get the URL and then redirect to it yourself. Will be removed in v6.0 */ public function doOidcLoginRedirect($launchUrl, ?array $request = null) { @@ -57,6 +57,10 @@ public function doOidcLoginRedirect($launchUrl, ?array $request = null) $request = $_REQUEST; } + if (empty($launchUrl)) { + throw new OidcException(static::ERROR_MSG_LAUNCH_URL, 1); + } + $authLoginReturnUrl = $this->getRedirectUrl($launchUrl, $request); // Return auth redirect. diff --git a/src/Redirect.php b/src/Redirect.php index 2e48cd4b..bf24d651 100644 --- a/src/Redirect.php +++ b/src/Redirect.php @@ -19,6 +19,9 @@ public function __construct(string $location, ?string $referer_query = null) $this->referer_query = $referer_query; } + /** + * @deprecated + */ public function doRedirect() { trigger_error('Method '.__METHOD__.' is deprecated', E_USER_DEPRECATED); @@ -39,8 +42,13 @@ public function doHybridRedirect(ICookie $cookie) $this->doJsRedirect(); } + /** + * @deprecated + */ public function getRedirectUrl() { + trigger_error('Method '.__METHOD__.' is deprecated', E_USER_DEPRECATED); + return $this->location; } From 5a4b2ed4c71ccf6b823c71023f9b11d540f5a8e6 Mon Sep 17 00:00:00 2001 From: Davo Hynds Date: Fri, 15 Dec 2023 10:09:00 -0600 Subject: [PATCH 6/6] Fix version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 53515b77..b97a97de 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "phpseclib/phpseclib": "^3.0" }, "require-dev": { - "jubeki/laravel-code-style": "^2.0", + "jubeki/laravel-code-style": "^1.0|^2.0", "mockery/mockery": "^1.4", "nesbot/carbon": "^2.43", "phpunit/phpunit": "^9.5"