See Release Notes
Long Term Support Release
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace enrol_lti\local\ltiadvantage\entity; 18 19 use enrol_lti\local\ltiadvantage\repository\legacy_consumer_repository; 20 21 /** 22 * The migration_claim class, instances of which represent information passed in an 'lti1p1' migration claim. 23 * 24 * Provides validation and data retrieval for the claim. 25 * 26 * See https://www.imsglobal.org/spec/lti/v1p3/migr#lti-1-1-migration-claim 27 * 28 * @package enrol_lti 29 * @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com> 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 class migration_claim { 33 34 /** @var string the LTI 1.1 consumer key */ 35 private $consumerkey; 36 37 /** @var string the LTI 1.1 user identifier. 38 * This is only included in the claim if it differs to the value included in the LTI 1.3 'sub' claim. 39 * I.e. https://www.imsglobal.org/spec/security/v1p0#id-token 40 */ 41 private $userid = null; 42 43 /** @var string the LTI 1.1 context identifier. 44 * This is only included in the claim if it differs to the 'id' property of the LTI 1.3 'context' claim. 45 * I.e. https://purl.imsglobal.org/spec/lti/claim/context#id. 46 */ 47 private $contextid = null; 48 49 /** @var string the LTI 1.1 consumer instance GUID. 50 * This is only included in the claim if it differs to the 'guid' property of the LTI 1.3 'tool_platform' claim. 51 * I.e. https://purl.imsglobal.org/spec/lti/claim/tool_platform#guid. 52 */ 53 private $toolconsumerinstanceguid = null; 54 55 /** @var string the LTI 1.1 resource link identifier. 56 * This is only included in the claim if it differs to the 'id' property of the LTI 1.3 'resource_link' claim. 57 * I.e. https://purl.imsglobal.org/spec/lti/claim/resource_link#id. 58 */ 59 private $resourcelinkid = null; 60 61 /** @var legacy_consumer_repository repository instance for querying consumer secrets when verifying signature. */ 62 private $legacyconsumerrepo; 63 64 /** 65 * The migration_claim constructor. 66 * 67 * @param array $claim the array of claim data, as received in a resource link launch. 68 * @param string $deploymentid the deployment id included in the launch. 69 * @param string $platform the platform included in the launch. 70 * @param string $clientid the client id included in the launch. 71 * @param string $exp the exp included in the launch. 72 * @param string $nonce the nonce included in the launch. 73 * @param legacy_consumer_repository $legacyconsumerrepo a legacy consumer repository instance. 74 * @throws \coding_exception if the claim data is invalid. 75 */ 76 public function __construct(array $claim, string $deploymentid, string $platform, string $clientid, string $exp, 77 string $nonce, legacy_consumer_repository $legacyconsumerrepo) { 78 79 // The oauth_consumer_key property MUST be sent. 80 // See: https://www.imsglobal.org/spec/lti/v1p3/migr#oauth_consumer_key. 81 if (empty($claim['oauth_consumer_key'])) { 82 throw new \coding_exception("Missing 'oauth_consumer_key' property in lti1p1 migration claim."); 83 } 84 85 // The oauth_consumer_key_sign property MAY be sent. 86 // For user migration to take place, however, this is deemed a required property. 87 // See: https://www.imsglobal.org/spec/lti/v1p3/migr#oauth_consumer_key_sign. 88 if (empty($claim['oauth_consumer_key_sign'])) { 89 throw new \coding_exception("Missing 'oauth_consumer_key_sign' property in lti1p1 migration claim."); 90 } 91 $this->legacyconsumerrepo = $legacyconsumerrepo; 92 93 if (!$this->verify_signature($claim['oauth_consumer_key'], $claim['oauth_consumer_key_sign'], $deploymentid, 94 $platform, $clientid, $exp, $nonce, $legacyconsumerrepo)) { 95 throw new \coding_exception("Invalid 'oauth_consumer_key_sign' signature in lti1p1 claim."); 96 } 97 98 $this->consumerkey = $claim['oauth_consumer_key']; 99 $this->userid = $claim['user_id'] ?? null; 100 $this->contextid = $claim['context_id'] ?? null; 101 $this->toolconsumerinstanceguid = $claim['tool_consumer_instance_guid'] ?? null; 102 $this->resourcelinkid = $claim['resource_link_id'] ?? null; 103 } 104 105 /** 106 * Verify the claim signature by recalculating it using the launch data and locally stored consumer secret. 107 * 108 * @param string $consumerkey the LTI 1.1 consumer key. 109 * @param string $signature a signature of the LTI 1.1 consumer key and associated launch data. 110 * @param string $deploymentid the deployment id included in the launch. 111 * @param string $platform the platform included in the launch. 112 * @param string $clientid the client id included in the launch. 113 * @param string $exp the exp included in the launch. 114 * @param string $nonce the nonce included in the launch. 115 * @return bool true if the signature was verified, false otherwise. 116 */ 117 private function verify_signature(string $consumerkey, string $signature, string $deploymentid, string $platform, 118 string $clientid, string $exp, string $nonce): bool { 119 120 $base = [ 121 $consumerkey, 122 $deploymentid, 123 $platform, 124 $clientid, 125 $exp, 126 $nonce 127 ]; 128 $basestring = implode('&', $base); 129 130 // Legacy enrol_lti code permits tools to share a consumer key but use different secrets. This results in 131 // potentially many secrets per mapped tool consumer. As such, when generating the migration claim it's 132 // impossible to know which secret the platform will use to sign the consumer key. The consumer key in the 133 // migration claim is thus verified by trying all known secrets for the consumer, until either a match is found 134 // or no signatures match. 135 $consumersecrets = $this->legacyconsumerrepo->get_consumer_secrets($consumerkey); 136 foreach ($consumersecrets as $consumersecret) { 137 $calculatedsignature = base64_encode(hash_hmac('sha256', $basestring, $consumersecret)); 138 139 if ($signature === $calculatedsignature) { 140 return true; 141 } 142 } 143 return false; 144 } 145 146 /** 147 * Return the consumer key stored in the claim. 148 * 149 * @return string the consumer key included in the claim. 150 */ 151 public function get_consumer_key(): string { 152 return $this->consumerkey; 153 } 154 155 /** 156 * Return the LTI 1.1 user id stored in the claim. 157 * 158 * @return string|null the user id, or null if not provided in the claim. 159 */ 160 public function get_user_id(): ?string { 161 return $this->userid; 162 } 163 164 165 /** 166 * Return the LTI 1.1 context id stored in the claim. 167 * 168 * @return string|null the context id, or null if not provided in the claim. 169 */ 170 public function get_context_id(): ?string { 171 return $this->contextid; 172 } 173 174 /** 175 * Return the LTI 1.1 tool consumer instance GUID stored in the claim. 176 * 177 * @return string|null the tool consumer instance GUID, or null if not provided in the claim. 178 */ 179 public function get_tool_consumer_instance_guid(): ?string { 180 return $this->toolconsumerinstanceguid; 181 } 182 183 /** 184 * Return the LTI 1.1 resource link id stored in the claim. 185 * 186 * @return string|null the resource link id, or null if not provided in the claim. 187 */ 188 public function get_resource_link_id(): ?string { 189 return $this->resourcelinkid; 190 } 191 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body