Skip to content

Commit

Permalink
MDL-58544 oauth2: Allow trusted issuers
Browse files Browse the repository at this point in the history
Add a setting to each issuer that skips the email confirmation when creating and linking accounts.
  • Loading branch information
Damyon Wiese committed Jun 27, 2017
1 parent f4a2d69 commit 859e203
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 20 deletions.
4 changes: 4 additions & 0 deletions admin/tool/oauth2/classes/form/issuer.php
Expand Up @@ -119,6 +119,10 @@ public function definition() {
$mform->addElement('checkbox', 'showonloginpage', get_string('issuershowonloginpage', 'tool_oauth2'));
$mform->addHelpButton('showonloginpage', 'issuershowonloginpage', 'tool_oauth2');

// Require confirmation email for new accounts.
$mform->addElement('advcheckbox', 'requireconfirmation', get_string('issuerrequireconfirmation', 'tool_oauth2'));
$mform->addHelpButton('requireconfirmation', 'issuerrequireconfirmation', 'tool_oauth2');

$mform->addElement('hidden', 'sortorder');
$mform->setType('sortorder', PARAM_INT);

Expand Down
2 changes: 2 additions & 0 deletions admin/tool/oauth2/lang/en/tool_oauth2.php
Expand Up @@ -81,6 +81,8 @@
$string['issuername'] = 'Name';
$string['issuershowonloginpage_help'] = 'If the OpenID Connect Authentication plugin is enabled, this login issuer will be listed on the login page to allow users to log in with accounts from this issuer.';
$string['issuershowonloginpage'] = 'Show on login page.';
$string['issuerrequireconfirmation_help'] = 'Require that all users verify their email address before they can log in with OAuth. This applies to newly created accounts as part of the login process, or when an existing Moodle account is connected to an OAuth login via matching email addresses.';
$string['issuerrequireconfirmation'] = 'Require email verification';
$string['issuers'] = 'Issuers';
$string['loginissuer'] = 'Allow login';
$string['notconfigured'] = 'Not configured';
Expand Down
44 changes: 44 additions & 0 deletions auth/oauth2/classes/api.php
Expand Up @@ -239,6 +239,50 @@ public static function confirm_link_login($userid, $username, $issuerid, $token)
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.
*
Expand Down
50 changes: 32 additions & 18 deletions auth/oauth2/classes/auth.php
Expand Up @@ -450,15 +450,21 @@ public function complete_login(client $client, $redirecturl) {

$moodleuser = \core_user::get_user_by_email($userinfo['email']);
if (!empty($moodleuser)) {
$PAGE->set_url('/auth/oauth2/confirm-link-login.php');
$PAGE->set_context(context_system::instance());

\auth_oauth2\api::send_confirm_link_login_email($userinfo, $issuer, $moodleuser->id);
// Request to link to existing account.
$emailconfirm = get_string('emailconfirmlink', 'auth_oauth2');
$message = get_string('emailconfirmlinksent', 'auth_oauth2', $moodleuser->email);
$this->print_confirm_required($emailconfirm, $message);
exit();
if ($issuer->get('requireconfirmation')) {
$PAGE->set_url('/auth/oauth2/confirm-link-login.php');
$PAGE->set_context(context_system::instance());

\auth_oauth2\api::send_confirm_link_login_email($userinfo, $issuer, $moodleuser->id);
// Request to link to existing account.
$emailconfirm = get_string('emailconfirmlink', 'auth_oauth2');
$message = get_string('emailconfirmlinksent', 'auth_oauth2', $moodleuser->email);
$this->print_confirm_required($emailconfirm, $message);
exit();
} else {
\auth_oauth2\api::link_login($userinfo, $issuer, $moodleuser->id, true);
$userinfo = get_complete_user_data('id', $moodleuser->id);
// No redirect, we will complete this login.
}

} else {
// This is a new account.
Expand Down Expand Up @@ -506,17 +512,25 @@ public function complete_login(client $client, $redirecturl) {
redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php'));
}

$PAGE->set_url('/auth/oauth2/confirm-account.php');
$PAGE->set_context(context_system::instance());
if ($issuer->get('requireconfirmation')) {
$PAGE->set_url('/auth/oauth2/confirm-account.php');
$PAGE->set_context(context_system::instance());

// Create a new (unconfirmed account) and send an email to confirm it.
$user = \auth_oauth2\api::send_confirm_account_email($userinfo, $issuer);
// Create a new (unconfirmed account) and send an email to confirm it.
$user = \auth_oauth2\api::send_confirm_account_email($userinfo, $issuer);

$this->update_picture($user);
$emailconfirm = get_string('emailconfirm');
$message = get_string('emailconfirmsent', '', $userinfo['email']);
$this->print_confirm_required($emailconfirm, $message);
exit();
$this->update_picture($user);
$emailconfirm = get_string('emailconfirm');
$message = get_string('emailconfirmsent', '', $userinfo['email']);
$this->print_confirm_required($emailconfirm, $message);
exit();
} else {
// Create a new confirmed account.
$newuser = \auth_oauth2\api::create_new_confirmed_account($userinfo, $issuer);
$userinfo = get_complete_user_data('id', $newuser->id);

// No redirect, we will complete this login.
}
}
}

Expand Down
42 changes: 42 additions & 0 deletions auth/oauth2/tests/api_test.php
Expand Up @@ -98,4 +98,46 @@ public function test_clean_orphaned_linked_logins_with_issuer_id() {
$this->assertCount(1, $linkedlogins);
}

/**
* Test auto-confirming linked logins.
*/
public function test_linked_logins() {
$this->resetAfterTest();

$this->setAdminUser();
$issuer = \core\oauth2\api::create_standard_issuer('google');

$user = $this->getDataGenerator()->create_user();

$info = [];
$info['username'] = 'banana';
$info['email'] = 'banana@example.com';

\auth_oauth2\api::link_login($info, $issuer, $user->id, false);

// Try and match a user with a linked login.
$match = \auth_oauth2\api::match_username_to_user('banana', $issuer);

$this->assertEquals($user->id, $match->get('userid'));
$linkedlogins = \auth_oauth2\api::get_linked_logins($user->id, $issuer);
\auth_oauth2\api::delete_linked_login($linkedlogins[0]->get('id'));

$match = \auth_oauth2\api::match_username_to_user('banana', $issuer);
$this->assertFalse($match);

$info = [];
$info['username'] = 'apple';
$info['email'] = 'apple@example.com';
$info['firstname'] = 'Apple';
$info['lastname'] = 'Fruit';
$info['url'] = 'http://apple.com/';
$info['alternamename'] = 'Beatles';

$newuser = \auth_oauth2\api::create_new_confirmed_account($info, $issuer);

$match = \auth_oauth2\api::match_username_to_user('apple', $issuer);

$this->assertEquals($newuser->id, $match->get('userid'));
}

}
4 changes: 4 additions & 0 deletions lib/classes/oauth2/issuer.php
Expand Up @@ -100,6 +100,10 @@ protected static function define_properties() {
'sortorder' => array(
'type' => PARAM_INT,
'default' => 0,
),
'requireconfirmation' => array(
'type' => PARAM_BOOL,
'default' => true
)
);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/db/install.xml 100644 → 100755
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20170316" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20170502" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
Expand Down Expand Up @@ -3502,6 +3502,7 @@
<FIELD NAME="enabled" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="showonloginpage" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The defined sort order."/>
<FIELD NAME="requireconfirmation" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down
15 changes: 15 additions & 0 deletions lib/db/upgrade.php
Expand Up @@ -2887,5 +2887,20 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2017061301.00);
}

if ($oldversion < 2017062700.00) {

// Define field requireconfirmation to be added to oauth2_issuer.
$table = new xmldb_table('oauth2_issuer');
$field = new xmldb_field('requireconfirmation', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '1', 'sortorder');

// Conditionally launch add field requireconfirmation.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Main savepoint reached.
upgrade_main_savepoint(true, 2017062700.00);
}

return true;
}
2 changes: 1 addition & 1 deletion version.php
Expand Up @@ -29,7 +29,7 @@

defined('MOODLE_INTERNAL') || die();

$version = 2017062200.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2017062700.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.

Expand Down

0 comments on commit 859e203

Please sign in to comment.