See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
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 /** 18 * Class for loading/storing oauth2 linked logins from the DB. 19 * 20 * @package auth_oauth2 21 * @copyright 2017 Damyon Wiese 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 namespace auth_oauth2; 25 26 use context_user; 27 use stdClass; 28 use moodle_exception; 29 use moodle_url; 30 31 defined('MOODLE_INTERNAL') || die(); 32 33 /** 34 * Static list of api methods for auth oauth2 configuration. 35 * 36 * @package auth_oauth2 37 * @copyright 2017 Damyon Wiese 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class api { 41 42 /** 43 * Remove all linked logins that are using issuers that have been deleted. 44 * 45 * @param int $issuerid The issuer id of the issuer to check, or false to check all (defaults to all) 46 * @return boolean 47 */ 48 public static function clean_orphaned_linked_logins($issuerid = false) { 49 return linked_login::delete_orphaned($issuerid); 50 } 51 52 /** 53 * List linked logins 54 * 55 * Requires auth/oauth2:managelinkedlogins capability at the user context. 56 * 57 * @param int $userid (defaults to $USER->id) 58 * @return boolean 59 */ 60 public static function get_linked_logins($userid = false) { 61 global $USER; 62 63 if ($userid === false) { 64 $userid = $USER->id; 65 } 66 67 if (\core\session\manager::is_loggedinas()) { 68 throw new moodle_exception('notwhileloggedinas', 'auth_oauth2'); 69 } 70 71 $context = context_user::instance($userid); 72 require_capability('auth/oauth2:managelinkedlogins', $context); 73 74 return linked_login::get_records(['userid' => $userid, 'confirmtoken' => '']); 75 } 76 77 /** 78 * See if there is a match for this username and issuer in the linked_login table. 79 * 80 * @param string $username as returned from an oauth client. 81 * @param \core\oauth2\issuer $issuer 82 * @return stdClass User record if found. 83 */ 84 public static function match_username_to_user($username, $issuer) { 85 $params = [ 86 'issuerid' => $issuer->get('id'), 87 'username' => $username 88 ]; 89 $result = linked_login::get_record($params); 90 91 if ($result) { 92 $user = \core_user::get_user($result->get('userid')); 93 if (!empty($user) && !$user->deleted) { 94 return $result; 95 } 96 } 97 return false; 98 } 99 100 /** 101 * Link a login to this account. 102 * 103 * Requires auth/oauth2:managelinkedlogins capability at the user context. 104 * 105 * @param array $userinfo as returned from an oauth client. 106 * @param \core\oauth2\issuer $issuer 107 * @param int $userid (defaults to $USER->id) 108 * @param bool $skippermissions During signup we need to set this before the user is setup for capability checks. 109 * @return bool 110 */ 111 public static function link_login($userinfo, $issuer, $userid = false, $skippermissions = false) { 112 global $USER; 113 114 if ($userid === false) { 115 $userid = $USER->id; 116 } 117 118 if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) { 119 throw new moodle_exception('alreadylinked', 'auth_oauth2'); 120 } 121 122 if (\core\session\manager::is_loggedinas()) { 123 throw new moodle_exception('notwhileloggedinas', 'auth_oauth2'); 124 } 125 126 $context = context_user::instance($userid); 127 if (!$skippermissions) { 128 require_capability('auth/oauth2:managelinkedlogins', $context); 129 } 130 131 $record = new stdClass(); 132 $record->issuerid = $issuer->get('id'); 133 $record->username = $userinfo['username']; 134 $record->userid = $userid; 135 $existing = linked_login::get_record((array)$record); 136 if ($existing) { 137 $existing->set('confirmtoken', ''); 138 $existing->update(); 139 return $existing; 140 } 141 $record->email = $userinfo['email']; 142 $record->confirmtoken = ''; 143 $record->confirmtokenexpires = 0; 144 $linkedlogin = new linked_login(0, $record); 145 return $linkedlogin->create(); 146 } 147 148 /** 149 * Send an email with a link to confirm linking this account. 150 * 151 * @param array $userinfo as returned from an oauth client. 152 * @param \core\oauth2\issuer $issuer 153 * @param int $userid (defaults to $USER->id) 154 * @return bool 155 */ 156 public static function send_confirm_link_login_email($userinfo, $issuer, $userid) { 157 $record = new stdClass(); 158 $record->issuerid = $issuer->get('id'); 159 $record->username = $userinfo['username']; 160 $record->userid = $userid; 161 if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) { 162 throw new moodle_exception('alreadylinked', 'auth_oauth2'); 163 } 164 $record->email = $userinfo['email']; 165 $record->confirmtoken = random_string(32); 166 $expires = new \DateTime('NOW'); 167 $expires->add(new \DateInterval('PT30M')); 168 $record->confirmtokenexpires = $expires->getTimestamp(); 169 170 $linkedlogin = new linked_login(0, $record); 171 $linkedlogin->create(); 172 173 // Construct the email. 174 $site = get_site(); 175 $supportuser = \core_user::get_support_user(); 176 $user = get_complete_user_data('id', $userid); 177 178 $data = new stdClass(); 179 $data->fullname = fullname($user); 180 $data->sitename = format_string($site->fullname); 181 $data->admin = generate_email_signoff(); 182 $data->issuername = format_string($issuer->get('name')); 183 $data->linkedemail = format_string($linkedlogin->get('email')); 184 185 $subject = get_string('confirmlinkedloginemailsubject', 'auth_oauth2', format_string($site->fullname)); 186 187 $params = [ 188 'token' => $linkedlogin->get('confirmtoken'), 189 'userid' => $userid, 190 'username' => $userinfo['username'], 191 'issuerid' => $issuer->get('id'), 192 ]; 193 $confirmationurl = new moodle_url('/auth/oauth2/confirm-linkedlogin.php', $params); 194 195 $data->link = $confirmationurl->out(false); 196 $message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data); 197 198 $data->link = $confirmationurl->out(); 199 $messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true); 200 201 $user->mailformat = 1; // Always send HTML version as well. 202 203 // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber. 204 return email_to_user($user, $supportuser, $subject, $message, $messagehtml); 205 } 206 207 /** 208 * Look for a waiting confirmation token, and if we find a match - confirm it. 209 * 210 * @param int $userid 211 * @param string $username 212 * @param int $issuerid 213 * @param string $token 214 * @return boolean True if we linked. 215 */ 216 public static function confirm_link_login($userid, $username, $issuerid, $token) { 217 if (empty($token) || empty($userid) || empty($issuerid) || empty($username)) { 218 return false; 219 } 220 $params = [ 221 'userid' => $userid, 222 'username' => $username, 223 'issuerid' => $issuerid, 224 'confirmtoken' => $token, 225 ]; 226 227 $login = linked_login::get_record($params); 228 if (empty($login)) { 229 return false; 230 } 231 $expires = $login->get('confirmtokenexpires'); 232 if (time() > $expires) { 233 $login->delete(); 234 return; 235 } 236 $login->set('confirmtokenexpires', 0); 237 $login->set('confirmtoken', ''); 238 $login->update(); 239 return true; 240 } 241 242 /** 243 * Create an account with a linked login that is already confirmed. 244 * 245 * @param array $userinfo as returned from an oauth client. 246 * @param \core\oauth2\issuer $issuer 247 * @return bool 248 */ 249 public static function create_new_confirmed_account($userinfo, $issuer) { 250 global $CFG, $DB; 251 require_once($CFG->dirroot.'/user/profile/lib.php'); 252 require_once($CFG->dirroot.'/user/lib.php'); 253 254 $user = new stdClass(); 255 $user->auth = 'oauth2'; 256 $user->mnethostid = $CFG->mnet_localhost_id; 257 $user->secret = random_string(15); 258 $user->password = ''; 259 $user->confirmed = 1; // Set the user to confirmed. 260 261 $user = self::save_user($userinfo, $user); 262 263 // The linked account is pre-confirmed. 264 $record = new stdClass(); 265 $record->issuerid = $issuer->get('id'); 266 $record->username = $userinfo['username']; 267 $record->userid = $user->id; 268 $record->email = $userinfo['email']; 269 $record->confirmtoken = ''; 270 $record->confirmtokenexpires = 0; 271 272 $linkedlogin = new linked_login(0, $record); 273 $linkedlogin->create(); 274 275 return $user; 276 } 277 278 /** 279 * Send an email with a link to confirm creating this account. 280 * 281 * @param array $userinfo as returned from an oauth client. 282 * @param \core\oauth2\issuer $issuer 283 * @param int $userid (defaults to $USER->id) 284 * @return bool 285 */ 286 public static function send_confirm_account_email($userinfo, $issuer) { 287 global $CFG, $DB; 288 require_once($CFG->dirroot.'/user/profile/lib.php'); 289 require_once($CFG->dirroot.'/user/lib.php'); 290 291 if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) { 292 throw new moodle_exception('alreadylinked', 'auth_oauth2'); 293 } 294 295 $user = new stdClass(); 296 $user->auth = 'oauth2'; 297 $user->mnethostid = $CFG->mnet_localhost_id; 298 $user->secret = random_string(15); 299 $user->password = ''; 300 $user->confirmed = 0; // The user is not yet confirmed. 301 302 $user = self::save_user($userinfo, $user); 303 304 // The linked account is pre-confirmed. 305 $record = new stdClass(); 306 $record->issuerid = $issuer->get('id'); 307 $record->username = $userinfo['username']; 308 $record->userid = $user->id; 309 $record->email = $userinfo['email']; 310 $record->confirmtoken = ''; 311 $record->confirmtokenexpires = 0; 312 313 $linkedlogin = new linked_login(0, $record); 314 $linkedlogin->create(); 315 316 // Construct the email. 317 $site = get_site(); 318 $supportuser = \core_user::get_support_user(); 319 $user = get_complete_user_data('id', $user->id); 320 321 $data = new stdClass(); 322 $data->fullname = fullname($user); 323 $data->sitename = format_string($site->fullname); 324 $data->admin = generate_email_signoff(); 325 326 $subject = get_string('confirmaccountemailsubject', 'auth_oauth2', format_string($site->fullname)); 327 328 $params = [ 329 'token' => $user->secret, 330 'username' => $userinfo['username'] 331 ]; 332 $confirmationurl = new moodle_url('/auth/oauth2/confirm-account.php', $params); 333 334 $data->link = $confirmationurl->out(false); 335 $message = get_string('confirmaccountemail', 'auth_oauth2', $data); 336 337 $data->link = $confirmationurl->out(); 338 $messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true); 339 340 $user->mailformat = 1; // Always send HTML version as well. 341 342 // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber. 343 email_to_user($user, $supportuser, $subject, $message, $messagehtml); 344 return $user; 345 } 346 347 /** 348 * Delete linked login 349 * 350 * Requires auth/oauth2:managelinkedlogins capability at the user context. 351 * 352 * @param int $linkedloginid 353 * @return boolean 354 */ 355 public static function delete_linked_login($linkedloginid) { 356 $login = new linked_login($linkedloginid); 357 $userid = $login->get('userid'); 358 359 if (\core\session\manager::is_loggedinas()) { 360 throw new moodle_exception('notwhileloggedinas', 'auth_oauth2'); 361 } 362 363 $context = context_user::instance($userid); 364 require_capability('auth/oauth2:managelinkedlogins', $context); 365 366 $login->delete(); 367 } 368 369 /** 370 * Delete linked logins for a user. 371 * 372 * @param \core\event\user_deleted $event 373 * @return boolean 374 */ 375 public static function user_deleted(\core\event\user_deleted $event) { 376 global $DB; 377 378 $userid = $event->objectid; 379 380 return $DB->delete_records(linked_login::TABLE, ['userid' => $userid]); 381 } 382 383 /** 384 * Is the plugin enabled. 385 * 386 * @return bool 387 */ 388 public static function is_enabled() { 389 return is_enabled_auth('oauth2'); 390 } 391 392 /** 393 * Create a new user & update the profile fields 394 * 395 * @param array $userinfo 396 * @param object $user 397 * @return object 398 */ 399 private static function save_user(array $userinfo, object $user): object { 400 // Map supplied issuer user info to Moodle user fields. 401 $userfieldmapping = new \core\oauth2\user_field_mapping(); 402 $userfieldlist = $userfieldmapping->get_internalfields(); 403 $hasprofilefield = false; 404 foreach ($userfieldlist as $field) { 405 if (isset($userinfo[$field]) && $userinfo[$field]) { 406 $user->$field = $userinfo[$field]; 407 408 // Check whether the profile fields exist or not. 409 $hasprofilefield = $hasprofilefield || strpos($field, \core_user\fields::PROFILE_FIELD_PREFIX) === 0; 410 } 411 } 412 413 // Create a new user. 414 $user->id = user_create_user($user, false, true); 415 416 // If profile fields exist then save custom profile fields data. 417 if ($hasprofilefield) { 418 profile_save_data($user); 419 } 420 421 return $user; 422 } 423 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body