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 auth_lti\local\ltiadvantage\entity; 18 19 /** 20 * A simplified representation of a 'https://purl.imsglobal.org/spec/lti/claim/lti1p1' migration claim. 21 * 22 * This serves the purpose of migrating a legacy user account only. Claim properties that do not relate to user migration are not 23 * included or handled by this representation. 24 * 25 * See https://www.imsglobal.org/spec/lti/v1p3/migr#lti-1-1-migration-claim 26 * 27 * @package auth_lti 28 * @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com> 29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 */ 31 class user_migration_claim { 32 33 /** @var string the LTI 1.1 consumer key */ 34 private $consumerkey; 35 36 /** @var string the LTI 1.1 user identifier. 37 * This is only included in the claim if it differs to the value included in the LTI 1.3 'sub' claim. 38 * If not included, the value will be taken from 'sub'. 39 */ 40 private $userid; 41 42 /** 43 * The migration_claim constructor. 44 * 45 * The signature of a migration claim must be verifiable. To achieve this, the constructor takes a list of secrets 46 * corresponding to the 'oauth_consumer_key' provided in the 'https://purl.imsglobal.org/spec/lti/claim/lti1p1' 47 * claim. How these secrets are determined is not the responsibility of this class. The constructor assumes these 48 * correspond. 49 * 50 * @param array $jwt the array of claim data, as received in a resource link launch JWT. 51 * @param array $consumersecrets a list of consumer secrets for the consumerkey included in the migration claim. 52 * @throws \coding_exception if the claim data is invalid. 53 */ 54 public function __construct(array $jwt, array $consumersecrets) { 55 // Can't get a claim instance without the claim data. 56 if (empty($jwt['https://purl.imsglobal.org/spec/lti/claim/lti1p1'])) { 57 throw new \coding_exception("Missing the 'https://purl.imsglobal.org/spec/lti/claim/lti1p1' JWT claim"); 58 } 59 $claim = $jwt['https://purl.imsglobal.org/spec/lti/claim/lti1p1']; 60 61 // The oauth_consumer_key property MUST be sent. 62 // See: https://www.imsglobal.org/spec/lti/v1p3/migr#oauth_consumer_key. 63 if (empty($claim['oauth_consumer_key'])) { 64 throw new \coding_exception("Missing 'oauth_consumer_key' property in lti1p1 migration claim."); 65 } 66 67 // The oauth_consumer_key_sign property MAY be sent. 68 // For user migration to take place, however, this is deemed a required property since Moodle identified its 69 // legacy users through a combination of consumerkey and userid. 70 // See: https://www.imsglobal.org/spec/lti/v1p3/migr#oauth_consumer_key_sign. 71 if (empty($claim['oauth_consumer_key_sign'])) { 72 throw new \coding_exception("Missing 'oauth_consumer_key_sign' property in lti1p1 migration claim."); 73 } 74 75 if (!$this->verify_signature( 76 $claim['oauth_consumer_key'], 77 $claim['oauth_consumer_key_sign'], 78 $jwt['https://purl.imsglobal.org/spec/lti/claim/deployment_id'], 79 $jwt['iss'], 80 $jwt['aud'], 81 $jwt['exp'], 82 $jwt['nonce'], 83 $consumersecrets 84 )) { 85 throw new \coding_exception("Invalid 'oauth_consumer_key_sign' signature in lti1p1 claim."); 86 } 87 88 $this->consumerkey = $claim['oauth_consumer_key']; 89 $this->userid = $claim['user_id'] ?? $jwt['sub']; 90 } 91 92 /** 93 * Verify the claim signature by recalculating it using the launch data and cross-checking consumer secrets. 94 * 95 * @param string $consumerkey the LTI 1.1 consumer key. 96 * @param string $signature a signature of the LTI 1.1 consumer key and associated launch data. 97 * @param string $deploymentid the deployment id included in the launch. 98 * @param string $platform the platform included in the launch. 99 * @param string $clientid the client id included in the launch. 100 * @param string $exp the exp included in the launch. 101 * @param string $nonce the nonce included in the launch. 102 * @param array $consumersecrets the list of consumer secrets used with the given $consumerkey param 103 * @return bool true if the signature was verified, false otherwise. 104 */ 105 private function verify_signature(string $consumerkey, string $signature, string $deploymentid, string $platform, 106 string $clientid, string $exp, string $nonce, array $consumersecrets): bool { 107 108 $base = [ 109 $consumerkey, 110 $deploymentid, 111 $platform, 112 $clientid, 113 $exp, 114 $nonce 115 ]; 116 $basestring = implode('&', $base); 117 118 // Legacy enrol_lti code permits tools to share a consumer key but use different secrets. This results in 119 // potentially many secrets per mapped tool consumer. As such, when generating the migration claim it's 120 // impossible to know which secret the platform will use to sign the consumer key. The consumer key in the 121 // migration claim is thus verified by trying all known secrets for the consumer, until either a match is found 122 // or no signatures match. 123 foreach ($consumersecrets as $consumersecret) { 124 $calculatedsignature = base64_encode(hash_hmac('sha256', $basestring, $consumersecret)); 125 126 if ($signature === $calculatedsignature) { 127 return true; 128 } 129 } 130 return false; 131 } 132 133 /** 134 * Return the consumer key stored in the claim. 135 * 136 * @return string the consumer key included in the claim. 137 */ 138 public function get_consumer_key(): string { 139 return $this->consumerkey; 140 } 141 142 /** 143 * Return the LTI 1.1 user id stored in the claim. 144 * 145 * @return string the user id, or null if not provided in the claim. 146 */ 147 public function get_user_id(): string { 148 return $this->userid; 149 } 150 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body