You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
409 lines
14 KiB
409 lines
14 KiB
2 years ago
|
<?php
|
||
|
// This file is part of Moodle - http://moodle.org/
|
||
|
//
|
||
|
// Moodle is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// Moodle is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU General Public License
|
||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
/**
|
||
|
* Class for loading/storing oauth2 linked logins from the DB.
|
||
|
*
|
||
|
* @package auth_oauth2
|
||
|
* @copyright 2017 Damyon Wiese
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
namespace auth_oauth2;
|
||
|
|
||
|
use context_user;
|
||
|
use stdClass;
|
||
|
use moodle_exception;
|
||
|
use moodle_url;
|
||
|
|
||
|
defined('MOODLE_INTERNAL') || die();
|
||
|
|
||
|
/**
|
||
|
* Static list of api methods for auth oauth2 configuration.
|
||
|
*
|
||
|
* @package auth_oauth2
|
||
|
* @copyright 2017 Damyon Wiese
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
class api {
|
||
|
|
||
|
/**
|
||
|
* Remove all linked logins that are using issuers that have been deleted.
|
||
|
*
|
||
|
* @param int $issuerid The issuer id of the issuer to check, or false to check all (defaults to all)
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public static function clean_orphaned_linked_logins($issuerid = false) {
|
||
|
return linked_login::delete_orphaned($issuerid);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* List linked logins
|
||
|
*
|
||
|
* Requires auth/oauth2:managelinkedlogins capability at the user context.
|
||
|
*
|
||
|
* @param int $userid (defaults to $USER->id)
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public static function get_linked_logins($userid = false) {
|
||
|
global $USER;
|
||
|
|
||
|
if ($userid === false) {
|
||
|
$userid = $USER->id;
|
||
|
}
|
||
|
|
||
|
if (\core\session\manager::is_loggedinas()) {
|
||
|
throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
|
||
|
}
|
||
|
|
||
|
$context = context_user::instance($userid);
|
||
|
require_capability('auth/oauth2:managelinkedlogins', $context);
|
||
|
|
||
|
return linked_login::get_records(['userid' => $userid, 'confirmtoken' => '']);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* See if there is a match for this username and issuer in the linked_login table.
|
||
|
*
|
||
|
* @param string $username as returned from an oauth client.
|
||
|
* @param \core\oauth2\issuer $issuer
|
||
|
* @return stdClass User record if found.
|
||
|
*/
|
||
|
public static function match_username_to_user($username, $issuer) {
|
||
|
$params = [
|
||
|
'issuerid' => $issuer->get('id'),
|
||
|
'username' => $username
|
||
|
];
|
||
|
$result = linked_login::get_record($params);
|
||
|
|
||
|
if ($result) {
|
||
|
$user = \core_user::get_user($result->get('userid'));
|
||
|
if (!empty($user) && !$user->deleted) {
|
||
|
return $result;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Link a login to this account.
|
||
|
*
|
||
|
* Requires auth/oauth2:managelinkedlogins capability at the user context.
|
||
|
*
|
||
|
* @param array $userinfo as returned from an oauth client.
|
||
|
* @param \core\oauth2\issuer $issuer
|
||
|
* @param int $userid (defaults to $USER->id)
|
||
|
* @param bool $skippermissions During signup we need to set this before the user is setup for capability checks.
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function link_login($userinfo, $issuer, $userid = false, $skippermissions = false) {
|
||
|
global $USER;
|
||
|
|
||
|
if ($userid === false) {
|
||
|
$userid = $USER->id;
|
||
|
}
|
||
|
|
||
|
if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
|
||
|
throw new moodle_exception('alreadylinked', 'auth_oauth2');
|
||
|
}
|
||
|
|
||
|
if (\core\session\manager::is_loggedinas()) {
|
||
|
throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
|
||
|
}
|
||
|
|
||
|
$context = context_user::instance($userid);
|
||
|
if (!$skippermissions) {
|
||
|
require_capability('auth/oauth2:managelinkedlogins', $context);
|
||
|
}
|
||
|
|
||
|
$record = new stdClass();
|
||
|
$record->issuerid = $issuer->get('id');
|
||
|
$record->username = $userinfo['username'];
|
||
|
$record->userid = $userid;
|
||
|
$existing = linked_login::get_record((array)$record);
|
||
|
if ($existing) {
|
||
|
$existing->set('confirmtoken', '');
|
||
|
$existing->update();
|
||
|
return $existing;
|
||
|
}
|
||
|
$record->email = $userinfo['email'];
|
||
|
$record->confirmtoken = '';
|
||
|
$record->confirmtokenexpires = 0;
|
||
|
$linkedlogin = new linked_login(0, $record);
|
||
|
return $linkedlogin->create();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send an email with a link to confirm linking this account.
|
||
|
*
|
||
|
* @param array $userinfo as returned from an oauth client.
|
||
|
* @param \core\oauth2\issuer $issuer
|
||
|
* @param int $userid (defaults to $USER->id)
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function send_confirm_link_login_email($userinfo, $issuer, $userid) {
|
||
|
$record = new stdClass();
|
||
|
$record->issuerid = $issuer->get('id');
|
||
|
$record->username = $userinfo['username'];
|
||
|
$record->userid = $userid;
|
||
|
if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
|
||
|
throw new moodle_exception('alreadylinked', 'auth_oauth2');
|
||
|
}
|
||
|
$record->email = $userinfo['email'];
|
||
|
$record->confirmtoken = random_string(32);
|
||
|
$expires = new \DateTime('NOW');
|
||
|
$expires->add(new \DateInterval('PT30M'));
|
||
|
$record->confirmtokenexpires = $expires->getTimestamp();
|
||
|
|
||
|
$linkedlogin = new linked_login(0, $record);
|
||
|
$linkedlogin->create();
|
||
|
|
||
|
// Construct the email.
|
||
|
$site = get_site();
|
||
|
$supportuser = \core_user::get_support_user();
|
||
|
$user = get_complete_user_data('id', $userid);
|
||
|
|
||
|
$data = new stdClass();
|
||
|
$data->fullname = fullname($user);
|
||
|
$data->sitename = format_string($site->fullname);
|
||
|
$data->admin = generate_email_signoff();
|
||
|
$data->issuername = format_string($issuer->get('name'));
|
||
|
$data->linkedemail = format_string($linkedlogin->get('email'));
|
||
|
|
||
|
$subject = get_string('confirmlinkedloginemailsubject', 'auth_oauth2', format_string($site->fullname));
|
||
|
|
||
|
$params = [
|
||
|
'token' => $linkedlogin->get('confirmtoken'),
|
||
|
'userid' => $userid,
|
||
|
'username' => $userinfo['username'],
|
||
|
'issuerid' => $issuer->get('id'),
|
||
|
];
|
||
|
$confirmationurl = new moodle_url('/auth/oauth2/confirm-linkedlogin.php', $params);
|
||
|
|
||
|
$data->link = $confirmationurl->out(false);
|
||
|
$message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
|
||
|
|
||
|
$data->link = $confirmationurl->out();
|
||
|
$messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true);
|
||
|
|
||
|
$user->mailformat = 1; // Always send HTML version as well.
|
||
|
|
||
|
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
|
||
|
return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Look for a waiting confirmation token, and if we find a match - confirm it.
|
||
|
*
|
||
|
* @param int $userid
|
||
|
* @param string $username
|
||
|
* @param int $issuerid
|
||
|
* @param string $token
|
||
|
* @return boolean True if we linked.
|
||
|
*/
|
||
|
public static function confirm_link_login($userid, $username, $issuerid, $token) {
|
||
|
if (empty($token) || empty($userid) || empty($issuerid) || empty($username)) {
|
||
|
return false;
|
||
|
}
|
||
|
$params = [
|
||
|
'userid' => $userid,
|
||
|
'username' => $username,
|
||
|
'issuerid' => $issuerid,
|
||
|
'confirmtoken' => $token,
|
||
|
];
|
||
|
|
||
|
$login = linked_login::get_record($params);
|
||
|
if (empty($login)) {
|
||
|
return false;
|
||
|
}
|
||
|
$expires = $login->get('confirmtokenexpires');
|
||
|
if (time() > $expires) {
|
||
|
$login->delete();
|
||
|
return;
|
||
|
}
|
||
|
$login->set('confirmtokenexpires', 0);
|
||
|
$login->set('confirmtoken', '');
|
||
|
$login->update();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create an account with a linked login that is already confirmed.
|
||
|
*
|
||
|
* @param array $userinfo as returned from an oauth client.
|
||
|
* @param \core\oauth2\issuer $issuer
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function create_new_confirmed_account($userinfo, $issuer) {
|
||
|
global $CFG, $DB;
|
||
|
require_once($CFG->dirroot.'/user/profile/lib.php');
|
||
|
require_once($CFG->dirroot.'/user/lib.php');
|
||
|
|
||
|
$user = new stdClass();
|
||
|
$user->username = $userinfo['username'];
|
||
|
$user->email = $userinfo['email'];
|
||
|
$user->auth = 'oauth2';
|
||
|
$user->mnethostid = $CFG->mnet_localhost_id;
|
||
|
$user->lastname = isset($userinfo['lastname']) ? $userinfo['lastname'] : '';
|
||
|
$user->firstname = isset($userinfo['firstname']) ? $userinfo['firstname'] : '';
|
||
|
$user->url = isset($userinfo['url']) ? $userinfo['url'] : '';
|
||
|
$user->alternatename = isset($userinfo['alternatename']) ? $userinfo['alternatename'] : '';
|
||
|
$user->secret = random_string(15);
|
||
|
|
||
|
$user->password = '';
|
||
|
// This user is confirmed.
|
||
|
$user->confirmed = 1;
|
||
|
|
||
|
$user->id = user_create_user($user, false, true);
|
||
|
|
||
|
// The linked account is pre-confirmed.
|
||
|
$record = new stdClass();
|
||
|
$record->issuerid = $issuer->get('id');
|
||
|
$record->username = $userinfo['username'];
|
||
|
$record->userid = $user->id;
|
||
|
$record->email = $userinfo['email'];
|
||
|
$record->confirmtoken = '';
|
||
|
$record->confirmtokenexpires = 0;
|
||
|
|
||
|
$linkedlogin = new linked_login(0, $record);
|
||
|
$linkedlogin->create();
|
||
|
|
||
|
return $user;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send an email with a link to confirm creating this account.
|
||
|
*
|
||
|
* @param array $userinfo as returned from an oauth client.
|
||
|
* @param \core\oauth2\issuer $issuer
|
||
|
* @param int $userid (defaults to $USER->id)
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function send_confirm_account_email($userinfo, $issuer) {
|
||
|
global $CFG, $DB;
|
||
|
require_once($CFG->dirroot.'/user/profile/lib.php');
|
||
|
require_once($CFG->dirroot.'/user/lib.php');
|
||
|
|
||
|
if (linked_login::has_existing_issuer_match($issuer, $userinfo['username'])) {
|
||
|
throw new moodle_exception('alreadylinked', 'auth_oauth2');
|
||
|
}
|
||
|
|
||
|
$user = new stdClass();
|
||
|
$user->username = $userinfo['username'];
|
||
|
$user->email = $userinfo['email'];
|
||
|
$user->auth = 'oauth2';
|
||
|
$user->mnethostid = $CFG->mnet_localhost_id;
|
||
|
$user->lastname = isset($userinfo['lastname']) ? $userinfo['lastname'] : '';
|
||
|
$user->firstname = isset($userinfo['firstname']) ? $userinfo['firstname'] : '';
|
||
|
$user->url = isset($userinfo['url']) ? $userinfo['url'] : '';
|
||
|
$user->alternatename = isset($userinfo['alternatename']) ? $userinfo['alternatename'] : '';
|
||
|
$user->secret = random_string(15);
|
||
|
|
||
|
$user->password = '';
|
||
|
// This user is not confirmed.
|
||
|
$user->confirmed = 0;
|
||
|
|
||
|
$user->id = user_create_user($user, false, true);
|
||
|
|
||
|
// The linked account is pre-confirmed.
|
||
|
$record = new stdClass();
|
||
|
$record->issuerid = $issuer->get('id');
|
||
|
$record->username = $userinfo['username'];
|
||
|
$record->userid = $user->id;
|
||
|
$record->email = $userinfo['email'];
|
||
|
$record->confirmtoken = '';
|
||
|
$record->confirmtokenexpires = 0;
|
||
|
|
||
|
$linkedlogin = new linked_login(0, $record);
|
||
|
$linkedlogin->create();
|
||
|
|
||
|
// Construct the email.
|
||
|
$site = get_site();
|
||
|
$supportuser = \core_user::get_support_user();
|
||
|
$user = get_complete_user_data('id', $user->id);
|
||
|
|
||
|
$data = new stdClass();
|
||
|
$data->fullname = fullname($user);
|
||
|
$data->sitename = format_string($site->fullname);
|
||
|
$data->admin = generate_email_signoff();
|
||
|
|
||
|
$subject = get_string('confirmaccountemailsubject', 'auth_oauth2', format_string($site->fullname));
|
||
|
|
||
|
$params = [
|
||
|
'token' => $user->secret,
|
||
|
'username' => $userinfo['username']
|
||
|
];
|
||
|
$confirmationurl = new moodle_url('/auth/oauth2/confirm-account.php', $params);
|
||
|
|
||
|
$data->link = $confirmationurl->out(false);
|
||
|
$message = get_string('confirmaccountemail', 'auth_oauth2', $data);
|
||
|
|
||
|
$data->link = $confirmationurl->out();
|
||
|
$messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true);
|
||
|
|
||
|
$user->mailformat = 1; // Always send HTML version as well.
|
||
|
|
||
|
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
|
||
|
email_to_user($user, $supportuser, $subject, $message, $messagehtml);
|
||
|
return $user;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete linked login
|
||
|
*
|
||
|
* Requires auth/oauth2:managelinkedlogins capability at the user context.
|
||
|
*
|
||
|
* @param int $linkedloginid
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public static function delete_linked_login($linkedloginid) {
|
||
|
$login = new linked_login($linkedloginid);
|
||
|
$userid = $login->get('userid');
|
||
|
|
||
|
if (\core\session\manager::is_loggedinas()) {
|
||
|
throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
|
||
|
}
|
||
|
|
||
|
$context = context_user::instance($userid);
|
||
|
require_capability('auth/oauth2:managelinkedlogins', $context);
|
||
|
|
||
|
$login->delete();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete linked logins for a user.
|
||
|
*
|
||
|
* @param \core\event\user_deleted $event
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public static function user_deleted(\core\event\user_deleted $event) {
|
||
|
global $DB;
|
||
|
|
||
|
$userid = $event->objectid;
|
||
|
|
||
|
return $DB->delete_records(linked_login::TABLE, ['userid' => $userid]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Is the plugin enabled.
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function is_enabled() {
|
||
|
$plugininfo = \core_plugin_manager::instance()->get_plugin_info('auth_oauth2');
|
||
|
return $plugininfo->is_enabled();
|
||
|
}
|
||
|
}
|