diff --git a/admin/tool/oauth2/classes/form/issuer.php b/admin/tool/oauth2/classes/form/issuer.php index 3a0b64d3cf5..a57b4c21a69 100644 --- a/admin/tool/oauth2/classes/form/issuer.php +++ b/admin/tool/oauth2/classes/form/issuer.php @@ -119,6 +119,10 @@ class issuer extends persistent { $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); diff --git a/admin/tool/oauth2/lang/en/tool_oauth2.php b/admin/tool/oauth2/lang/en/tool_oauth2.php index 3fdb55fd35b..795c0a15f4d 100644 --- a/admin/tool/oauth2/lang/en/tool_oauth2.php +++ b/admin/tool/oauth2/lang/en/tool_oauth2.php @@ -81,6 +81,8 @@ $string['issuername_help'] = 'Name of the identity issuer. May be displayed on l $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'; diff --git a/auth/oauth2/classes/api.php b/auth/oauth2/classes/api.php index 9d678a8b8e8..1b152b58bb7 100644 --- a/auth/oauth2/classes/api.php +++ b/auth/oauth2/classes/api.php @@ -239,6 +239,50 @@ class api { 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. * diff --git a/auth/oauth2/classes/auth.php b/auth/oauth2/classes/auth.php index 812cf988e82..ac32247034e 100644 --- a/auth/oauth2/classes/auth.php +++ b/auth/oauth2/classes/auth.php @@ -450,15 +450,21 @@ class auth extends \auth_plugin_base { $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()); + 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(); + \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. @@ -506,17 +512,25 @@ class auth extends \auth_plugin_base { 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. + } } } diff --git a/auth/oauth2/tests/api_test.php b/auth/oauth2/tests/api_test.php index 817430ac8cd..83bf1a6c71a 100644 --- a/auth/oauth2/tests/api_test.php +++ b/auth/oauth2/tests/api_test.php @@ -98,4 +98,46 @@ class auth_oauth2_external_testcase extends advanced_testcase { $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')); + } + } diff --git a/lib/classes/oauth2/issuer.php b/lib/classes/oauth2/issuer.php index 7960ea654d1..c6cb7567c9a 100644 --- a/lib/classes/oauth2/issuer.php +++ b/lib/classes/oauth2/issuer.php @@ -100,6 +100,10 @@ class issuer extends persistent { 'sortorder' => array( 'type' => PARAM_INT, 'default' => 0, + ), + 'requireconfirmation' => array( + 'type' => PARAM_BOOL, + 'default' => true ) ); } diff --git a/lib/db/install.xml b/lib/db/install.xml old mode 100644 new mode 100755 index ce5dda2b38c..d5c347f2af6 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -3502,6 +3502,7 @@ + diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 7798fecf760..13647e8c285 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -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; } diff --git a/version.php b/version.php index acf34f561cb..4ecc22522c2 100644 --- a/version.php +++ b/version.php @@ -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.