MDL-58544 oauth2: Allow trusted issuers
authorDamyon Wiese <damyon@moodle.com>
Tue, 2 May 2017 05:46:19 +0000 (13:46 +0800)
committerDamyon Wiese <damyon@moodle.com>
Tue, 27 Jun 2017 08:50:27 +0000 (16:50 +0800)
Add a setting to each issuer that skips the email confirmation when creating and linking accounts.

admin/tool/oauth2/classes/form/issuer.php
admin/tool/oauth2/lang/en/tool_oauth2.php
auth/oauth2/classes/api.php
auth/oauth2/classes/auth.php
auth/oauth2/tests/api_test.php
lib/classes/oauth2/issuer.php
lib/db/install.xml [changed mode: 0644->0755]
lib/db/upgrade.php
version.php

index 3a0b64d..a57b4c2 100644 (file)
@@ -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);
 
index 3fdb55f..795c0a1 100644 (file)
@@ -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';
index 9d678a8..1b152b5 100644 (file)
@@ -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.
      *
index 812cf98..ac32247 100644 (file)
@@ -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());
-
-                \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.
@@ -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.
+                }
             }
         }
 
index 817430a..83bf1a6 100644 (file)
@@ -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'));
+    }
+
 }
index 7960ea6..c6cb756 100644 (file)
@@ -100,6 +100,10 @@ class issuer extends persistent {
             'sortorder' => array(
                 'type' => PARAM_INT,
                 'default' => 0,
+            ),
+            'requireconfirmation' => array(
+                'type' => PARAM_BOOL,
+                'default' => true
             )
         );
     }
old mode 100644 (file)
new mode 100755 (executable)
index ce5dda2..d5c347f
@@ -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"
 >
         <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"/>
index 7798fec..13647e8 100644 (file)
@@ -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;
 }
index acf34f5..4ecc225 100644 (file)
@@ -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.