diff --git a/README.md b/README.md index b0331d2c..58faccd1 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ $resource = LTI\LTI_Deep_Link_Resource::new() Everything is set to return the resource to the platform. There are two methods of doing this. -The following method will output the html for an aut-posting form for you. +The following method will output the html for an auto-posting form for you. ```php $dl->output_response_form([$resource]); ``` @@ -209,6 +209,12 @@ Alternatively you can just request the signed JWT that will need posting back to $dl->get_response_jwt([$resource]); ``` +If you've created a JWKS endpoint with `LTI\JWKS_Endpoint::new()`, the kid used in the endpoint can be provided as an additional parameter. +```php +$dl->get_response_jwt([$resource], 'a_unique_KID'); + +``` + ## Calling Services ### Names and Roles Service diff --git a/src/lti/LTI_Assignments_Grades_Service.php b/src/lti/LTI_Assignments_Grades_Service.php index ffd1cde0..08981659 100644 --- a/src/lti/LTI_Assignments_Grades_Service.php +++ b/src/lti/LTI_Assignments_Grades_Service.php @@ -54,8 +54,8 @@ public function find_or_create_lineitem(LTI_Lineitem $new_line_item) { 'application/vnd.ims.lis.v2.lineitemcontainer+json' ); foreach ($line_items['body'] as $line_item) { - if (empty($new_line_item->get_resource_id()) || $line_item['resourceId'] == $new_line_item->get_resource_id()) { - if (empty($new_line_item->get_tag()) || $line_item['tag'] == $new_line_item->get_tag()) { + if (empty($new_line_item->get_resource_id()) || (isset($line_item['resourceId']) && $line_item['resourceId'] == $new_line_item->get_resource_id())) { + if (empty($new_line_item->get_tag()) || (isset($line_item['tag']) && $line_item['tag'] == $new_line_item->get_tag())) { return new LTI_Lineitem($line_item); } } @@ -88,4 +88,4 @@ public function get_grades(LTI_Lineitem $lineitem) { return $scores['body']; } } -?> \ No newline at end of file +?> diff --git a/src/lti/LTI_Deep_Link.php b/src/lti/LTI_Deep_Link.php index c87cb0da..11fb5f76 100644 --- a/src/lti/LTI_Deep_Link.php +++ b/src/lti/LTI_Deep_Link.php @@ -14,10 +14,10 @@ public function __construct($registration, $deployment_id, $deep_link_settings) $this->deep_link_settings = $deep_link_settings; } - public function get_response_jwt($resources) { + public function get_response_jwt($resources, string $kid = null) { $message_jwt = [ "iss" => $this->registration->get_client_id(), - "aud" => [$this->registration->get_issuer()], + "aud" => $this->registration->get_issuer(), "exp" => time() + 600, "iat" => time(), "nonce" => 'nonce' . hash('sha256', random_bytes(64)), @@ -25,13 +25,19 @@ public function get_response_jwt($resources) { "https://purl.imsglobal.org/spec/lti/claim/message_type" => "LtiDeepLinkingResponse", "https://purl.imsglobal.org/spec/lti/claim/version" => "1.3.0", "https://purl.imsglobal.org/spec/lti-dl/claim/content_items" => array_map(function($resource) { return $resource->to_array(); }, $resources), - "https://purl.imsglobal.org/spec/lti-dl/claim/data" => $this->deep_link_settings['data'], + "https://purl.imsglobal.org/spec/lti-dl/claim/data" => $this->deep_link_settings['data']?? "", ]; - return JWT::encode($message_jwt, $this->registration->get_tool_private_key(), 'RS256', $this->registration->get_kid()); + + return JWT::encode( + $message_jwt, + $this->registration->get_tool_private_key(), + 'RS256', + is_null($kid) ? $this->registration->get_kid() : $kid + ); } - public function output_response_form($resources) { - $jwt = $this->get_response_jwt($resources); + public function output_response_form($resources, string $kid = null) { + $jwt = $this->get_response_jwt($resources, $kid); ?>
@@ -42,5 +48,9 @@ public function output_response_form($resources) { deep_link_settings; + } } -?> \ No newline at end of file +?> diff --git a/src/lti/LTI_Deep_Link_Resource.php b/src/lti/LTI_Deep_Link_Resource.php index 8abeadd6..2c7964ea 100644 --- a/src/lti/LTI_Deep_Link_Resource.php +++ b/src/lti/LTI_Deep_Link_Resource.php @@ -11,7 +11,7 @@ class LTI_Deep_Link_Resource { private $target = 'iframe'; public static function new() { - return new LTI_Deep_Link_Resource(); + return new static(); } public function get_type() { @@ -76,13 +76,13 @@ public function to_array() { "presentation" => [ "documentTarget" => $this->target, ], - "custom" => $this->custom_params, ]; + if (count($this->custom_params) > 0) { + $resource["custom"] = $this->custom_params; + } + if ($this->lineitem !== null) { - $resource["lineItem"] = [ - "scoreMaximum" => $this->lineitem->get_score_maximum(), - "label" => $this->lineitem->get_label(), - ]; + $resource["lineItem"] = $this->lineitem->to_array(); } return $resource; } diff --git a/src/lti/LTI_Lineitem.php b/src/lti/LTI_Lineitem.php index ba8e20a7..cc756b70 100644 --- a/src/lti/LTI_Lineitem.php +++ b/src/lti/LTI_Lineitem.php @@ -19,15 +19,15 @@ public function __construct(array $lineitem = null) { $this->label = $lineitem["label"]; $this->resource_id = $lineitem["resourceId"]; $this->tag = $lineitem["tag"]; - $this->start_date_time = $lineitem["startDateTime"]; - $this->end_date_time = $lineitem["endDateTime"]; + $this->start_date_time = $lineitem["startDateTime"]?? date(\DateTime::ISO8601); + $this->end_date_time = $lineitem["endDateTime"]?? date(\DateTime::ISO8601); } /** * Static function to allow for method chaining without having to assign to a variable first. */ public static function new() { - return new LTI_Lineitem(); + return new static(); } public function get_id() { @@ -93,8 +93,8 @@ public function set_end_date_time($value) { return $this; } - public function __toString() { - return json_encode(array_filter([ + public function to_array() { + return [ "id" => $this->id, "scoreMaximum" => $this->score_maximum, "label" => $this->label, @@ -102,7 +102,11 @@ public function __toString() { "tag" => $this->tag, "startDateTime" => $this->start_date_time, "endDateTime" => $this->end_date_time, - ])); + ]; + } + + public function __toString() { + return json_encode(array_filter($this->to_array())); } } -?> \ No newline at end of file +?> diff --git a/src/lti/LTI_Message_Launch.php b/src/lti/LTI_Message_Launch.php index 7a18195d..d6a6d58f 100644 --- a/src/lti/LTI_Message_Launch.php +++ b/src/lti/LTI_Message_Launch.php @@ -3,6 +3,7 @@ use Firebase\JWT\JWK; use Firebase\JWT\JWT; +use Firebase\JWT\Key; JWT::$leeway = 5; @@ -43,7 +44,7 @@ function __construct(Database $database, Cache $cache = null, Cookie $cookie = n * Static function to allow for method chaining without having to assign to a variable first. */ public static function new(Database $database, Cache $cache = null, Cookie $cookie = null) { - return new LTI_Message_Launch($database, $cache, $cookie); + return new static($database, $cache, $cookie); } /** @@ -57,7 +58,7 @@ public static function new(Database $database, Cache $cache = null, Cookie $cook * @return LTI_Message_Launch A populated and validated LTI_Message_Launch. */ public static function from_cache($launch_id, Database $database, Cache $cache = null) { - $new = new LTI_Message_Launch($database, $cache, null); + $new = new static($database, $cache, null); $new->launch_id = $launch_id; $new->jwt = [ 'body' => $new->cache->get_launch_data($launch_id) ]; return $new->validate_registration(); @@ -220,7 +221,7 @@ private function get_public_key() { foreach ($public_key_set['keys'] as $key) { if ($key['kid'] == $this->jwt['header']['kid']) { try { - return openssl_pkey_get_details(JWK::parseKey($key)); + return openssl_pkey_get_details(JWK::parseKey($key, 'RS256')->getKeyMaterial()); } catch(\Exception $e) { return false; } @@ -238,8 +239,8 @@ private function cache_launch_data() { private function validate_state() { // Check State for OIDC. - if ($this->cookie->get_cookie('lti1p3_' . $this->request['state']) !== $this->request['state']) { - // Error if state doesn't match + if (!isset($this->request['state']) || $this->cookie->get_cookie('lti1p3_' . $this->request['state']) !== $this->request['state']) { + // Error if state is not set, or doesn't match throw new LTI_Exception("State not found", 1); } return $this; @@ -270,7 +271,7 @@ private function validate_jwt_format() { private function validate_nonce() { if (!$this->cache->check_nonce($this->jwt['body']['nonce'])) { - //throw new LTI_Exception("Invalid Nonce"); + throw new LTI_Exception("Invalid Nonce"); } return $this; } @@ -299,9 +300,8 @@ private function validate_jwt_signature() { // Validate JWT signature try { - JWT::decode($this->request['id_token'], $public_key['key'], array('RS256')); + JWT::decode($this->request['id_token'], new Key($public_key['key'], 'RS256')); } catch(\Exception $e) { - var_dump($e); // Error validating signature. throw new LTI_Exception("Invalid signature on id_token", 1); } @@ -369,4 +369,4 @@ private function validate_message() { } } -?> \ No newline at end of file +?> diff --git a/src/lti/LTI_OIDC_Login.php b/src/lti/LTI_OIDC_Login.php index 2c135c09..73faed05 100644 --- a/src/lti/LTI_OIDC_Login.php +++ b/src/lti/LTI_OIDC_Login.php @@ -42,16 +42,12 @@ public static function new(Database $database, Cache $cache = null, Cookie $cook * * @return Redirect Returns a redirect object containing the fully formed OIDC login URL. */ - public function do_oidc_login_redirect($launch_url, array $request = null) { + public function do_oidc_login_redirect(string $launch_url = "", array $request = null) { if ($request === null) { $request = $_REQUEST; } - if (empty($launch_url)) { - throw new OIDC_Exception("No launch URL configured", 1); - } - // Validate Request Data. $registration = $this->validate_oidc_login($request); @@ -75,7 +71,7 @@ public function do_oidc_login_redirect($launch_url, array $request = null) { 'response_mode' => 'form_post', // OIDC response is always a form post. 'prompt' => 'none', // Don't prompt user on redirect. 'client_id' => $registration->get_client_id(), // Registered client id. - 'redirect_uri' => $launch_url, // URL to return to after login. + 'redirect_uri' => $launch_url?: $request['target_link_uri'], // 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. @@ -117,4 +113,4 @@ protected function validate_oidc_login($request) { // Return Registration. return $registration; } -} \ No newline at end of file +}