/** * Process an authorization request. * * Operations: * - Auto creates users. * - Sets up user object for linked accounts. * * @param string $oidcuniqid The OIDC unique identifier received. * @param array $tokenparams Received token parameters. * @param \auth_oidc\jwt $idtoken Received id token. * @return bool Success/Failure. */ public function request_user_authorise($oidcuniqid, $tokenparams, $idtoken) { global $USER, $SESSION; $this->must_be_ready(); $username = $oidcuniqid; $email = $idtoken->claim('email'); $firstname = $idtoken->claim('given_name'); $lastname = $idtoken->claim('family_name'); // Office 365 uses "upn". $upn = $idtoken->claim('upn'); if (!empty($upn)) { $username = $upn; $email = $upn; } $create = false; try { $user = new \User(); $user->find_by_instanceid_username($this->instanceid, $username, true); if ($user->get('suspendedcusr')) { die_info(get_string('accountsuspended', 'mahara', strftime(get_string('strftimedaydate'), $user->get('suspendedctime')), $user->get('suspendedreason'))); } } catch (\AuthUnknownUserException $e) { if ($this->can_auto_create_users() === true) { $institution = new \Institution($this->institution); if ($institution->isFull()) { throw new \XmlrpcClientException('OpenID Connect login attempt failed because the institution is full.'); } $user = new \User(); $create = true; } else { return false; } } if ($create === true) { $user->passwordchange = 0; $user->active = 1; $user->deleted = 0; $user->expiry = null; $user->expirymailsent = 0; $user->lastlogin = time(); $user->firstname = $firstname; $user->lastname = $lastname; $user->email = $email; $user->authinstance = $this->instanceid; db_begin(); $user->username = get_new_username($username); $user->id = create_user($user, array(), $this->institution, $this, $username); $userobj = $user->to_stdclass(); $userarray = (array) $userobj; db_commit(); $user = new User(); $user->find_by_id($userobj->id); } $user->commit(); $USER->reanimate($user->id, $this->instanceid); $SESSION->set('authinstance', $this->instanceid); return true; }
/** * Detect the proper auth instance based on received user information. * * @param \auth_oidc\jwt $idtoken JWT ID Token. * @return int|null The auth instance ID if found, or null if none found. */ protected function detect_auth_instance($idtoken) { // Get auth instance. $sql = 'SELECT ai.id as instanceid, i.priority as institutionpriority FROM {auth_instance} ai JOIN {institution} i ON i.name = ai.institution WHERE ai.authname = \'oidc\' ORDER BY i.priority DESC, ai.priority ASC'; $instances = get_records_sql_array($sql); $catchalls = array(); $instanceid = null; foreach ($instances as $instance) { $reqattr = get_config_plugin_instance('auth', $instance->instanceid, 'institutionattribute'); $reqval = get_config_plugin_instance('auth', $instance->instanceid, 'institutionvalue'); if (empty($reqattr) || empty($reqval)) { $catchalls[$instance->institutionpriority][] = $instance; } else { // Check if we received specified attribute. $userattrval = $idtoken->claim($reqattr); if (!empty($userattrval)) { // Match value. if (preg_match('#' . trim($reqval) . '#', $userattrval)) { $instanceid = $instance->instanceid; break; } } } } // If no match on attribute, get the instance id of the first catchall by priority. if (empty($instanceid)) { foreach ($catchalls as $priority => $instances) { foreach ($instances as $instance) { $instanceid = $instance->instanceid; break; } break; } } return $instanceid; }
/** * Handle a login event. * * @param string $oidcuniqid A unique identifier for the user. * @param array $authparams Parameters receieved from the auth request. * @param array $tokenparams Parameters received from the token request. * @param \auth_oidc\jwt $idtoken A JWT object representing the received id_token. */ protected function handlelogin($oidcuniqid, $authparams, $tokenparams, $idtoken) { global $DB, $CFG; $tokenrec = $DB->get_record('auth_oidc_token', ['oidcuniqid' => $oidcuniqid]); if (!empty($tokenrec)) { $username = $tokenrec->username; $this->updatetoken($tokenrec->id, $authparams, $tokenparams); } else { // Use 'upn' if available for username (Azure-specific), or fall back to lower-case oidcuniqid. $username = $idtoken->claim('upn'); if (empty($username)) { $username = strtolower($oidcuniqid); } $matchedwith = $this->check_for_matched($username); if (!empty($matchedwith)) { $matchedwith->aadupn = $username; throw new \moodle_exception('errorusermatched', 'local_o365', null, $matchedwith); } $tokenrec = $this->createtoken($oidcuniqid, $username, $authparams, $tokenparams, $idtoken); } $existinguserparams = ['username' => $username, 'mnethostid' => $CFG->mnet_localhost_id]; if ($DB->record_exists('user', $existinguserparams) !== true) { // User does not exist. Create user if site allows, otherwise fail. if (empty($CFG->authpreventaccountcreation)) { $user = create_user_record($username, null, 'oidc'); } else { // Trigger login failed event. $failurereason = AUTH_LOGIN_NOUSER; $eventdata = ['other' => ['username' => $username, 'reason' => $failurereason]]; $event = \core\event\user_login_failed::create($eventdata); $event->trigger(); throw new \moodle_exception('errorauthloginfailednouser', 'auth_oidc'); } } $user = authenticate_user_login($username, null, true); if (empty($user)) { throw new \moodle_exception('errorauthloginfailednouser', 'auth_oidc'); } complete_user_login($user); return true; }
/** * Create a token for a user, thus linking a Moodle user to an OpenID Connect user. * * @param string $oidcuniqid A unique identifier for the user. * @param array $username The username of the Moodle user to link to. * @param array $authparams Parameters receieved from the auth request. * @param array $tokenparams Parameters received from the token request. * @param \auth_oidc\jwt $idtoken A JWT object representing the received id_token. * @return \stdClass The created token database record. */ protected function createtoken($oidcuniqid, $username, $authparams, $tokenparams, \auth_oidc\jwt $idtoken) { global $DB; // Determine remote username. Use 'upn' if available (Azure-specific), or fall back to standard 'sub'. $oidcusername = $idtoken->claim('upn'); if (empty($oidcusername)) { $oidcusername = $idtoken->claim('sub'); } // We should not fail here (idtoken was verified earlier to at least contain 'sub', but just in case...). if (empty($oidcusername)) { throw new \moodle_exception('errorauthinvalididtoken', 'auth_oidc'); } $tokenrec = new \stdClass(); $tokenrec->oidcuniqid = $oidcuniqid; $tokenrec->username = $username; $tokenrec->oidcusername = $oidcusername; $tokenrec->scope = $tokenparams['scope']; $tokenrec->resource = $tokenparams['resource']; $tokenrec->authcode = $authparams['code']; $tokenrec->token = $tokenparams['access_token']; $tokenrec->expiry = $tokenparams['expires_on']; $tokenrec->refreshtoken = $tokenparams['refresh_token']; $tokenrec->idtoken = $tokenparams['id_token']; $tokenrec->id = $DB->insert_record('auth_oidc_token', $tokenrec); return $tokenrec; }