diff --git a/admin/oauth2callback.php b/admin/oauth2callback.php
index 08bb01d78e3..709e28ce99b 100644
--- a/admin/oauth2callback.php
+++ b/admin/oauth2callback.php
@@ -30,6 +30,17 @@
require_once(__DIR__ . '/../config.php');
+$error = optional_param('error', '', PARAM_RAW);
+if ($error) {
+ $message = optional_param('error_description', '', PARAM_RAW);
+ if ($message) {
+ print_error($message);
+ } else {
+ print_error($error);
+ }
+ die();
+}
+
// The authorization code generated by the authorization server.
$code = required_param('code', PARAM_RAW);
// The state parameter we've given (used in moodle as a redirect url).
diff --git a/admin/tool/oauth2/classes/form/issuer.php b/admin/tool/oauth2/classes/form/issuer.php
new file mode 100644
index 00000000000..ce03af63e4a
--- /dev/null
+++ b/admin/tool/oauth2/classes/form/issuer.php
@@ -0,0 +1,107 @@
+.
+
+/**
+ * This file contains the form add/update oauth2 issuer.
+ *
+ * @package tool_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_oauth2\form;
+defined('MOODLE_INTERNAL') || die();
+
+use stdClass;
+use core\form\persistent;
+
+/**
+ * Issuer form.
+ *
+ * @package tool_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class issuer extends persistent {
+
+ protected static $persistentclass = 'core\\oauth2\\issuer';
+
+ protected static $fieldstoremove = array('submitbutton', 'action');
+
+ /**
+ * Define the form - called by parent constructor
+ */
+ public function definition() {
+ global $PAGE;
+
+ $mform = $this->_form;
+ $provider = $this->get_persistent();
+
+ $mform->addElement('header', 'generalhdr', get_string('general'));
+
+ // Name.
+ $mform->addElement('text', 'name', get_string('issuername', 'tool_oauth2'), 'maxlength="255"');
+ $mform->addRule('name', null, 'required', null, 'client');
+ $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
+ $mform->addHelpButton('name', 'issuername', 'tool_oauth2');
+
+ // Client ID.
+ $mform->addElement('text', 'clientid', get_string('issuerclientid', 'tool_oauth2'), 'maxlength="255"');
+ $mform->addRule('clientid', null, 'required', null, 'client');
+ $mform->addRule('clientid', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
+ $mform->addHelpButton('clientid', 'issuerclientid', 'tool_oauth2');
+
+ // Client Secret.
+ $mform->addElement('text', 'clientsecret', get_string('issuerclientsecret', 'tool_oauth2'), 'maxlength="255"');
+ $mform->addRule('clientsecret', null, 'required', null, 'client');
+ $mform->addRule('clientsecret', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
+ $mform->addHelpButton('clientsecret', 'issuerclientsecret', 'tool_oauth2');
+
+ // Base Url.
+ $mform->addElement('text', 'baseurl', get_string('issuerbaseurl', 'tool_oauth2'), 'maxlength="1024"');
+ $mform->addRule('baseurl', null, 'required', null, 'client');
+ $mform->addRule('baseurl', get_string('maximumchars', '', 1024), 'maxlength', 1024, 'client');
+ $mform->addHelpButton('baseurl', 'issuerbaseurl', 'tool_oauth2');
+
+ // Offline access type
+ $options = $provider->get_behaviour_list();
+ $mform->addElement('select', 'behaviour', get_string('issuerbehaviour', 'tool_oauth2'), $options);
+ $mform->addHelpButton('behaviour', 'issuerbehaviour', 'tool_oauth2');
+
+ // Image.
+ $mform->addElement('text', 'image', get_string('issuerimage', 'tool_oauth2'), 'maxlength="1024"');
+ $mform->addRule('image', get_string('maximumchars', '', 1024), 'maxlength', 1024, 'client');
+ $mform->addHelpButton('image', 'issuername', 'tool_oauth2');
+
+ // Show on login page.
+ $mform->addElement('checkbox', 'showonloginpage', get_string('issuershowonloginpage', 'tool_oauth2'));
+ $mform->addHelpButton('showonloginpage', 'issuershowonloginpage', 'tool_oauth2');
+
+
+ $mform->addElement('hidden', 'sortorder');
+ $mform->setType('sortorder', PARAM_INT);
+
+ $mform->addElement('hidden', 'action', 'edit');
+ $mform->setType('action', PARAM_RAW);
+
+ $mform->addElement('hidden', 'id', $provider->get('id'));
+ $mform->setType('id', PARAM_INT);
+
+ $this->add_action_buttons(true, get_string('savechanges', 'tool_oauth2'));
+ }
+
+}
+
diff --git a/admin/tool/oauth2/classes/output/renderer.php b/admin/tool/oauth2/classes/output/renderer.php
new file mode 100644
index 00000000000..f26ca777f91
--- /dev/null
+++ b/admin/tool/oauth2/classes/output/renderer.php
@@ -0,0 +1,167 @@
+.
+
+/**
+ * Output rendering for the plugin.
+ *
+ * @package tool_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_oauth2\output;
+
+use plugin_renderer_base;
+use html_table;
+use html_table_cell;
+use html_table_row;
+use html_writer;
+use core\oauth2\issuer;
+use core\oauth2\api;
+use moodle_url;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Implements the plugin renderer
+ *
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderer extends plugin_renderer_base {
+ /**
+ * This function will render one beautiful table with all the issuers.
+ *
+ * @param \core\oauth2\issuer[] $issuers - list of all issuers.
+ * @return string HTML to output.
+ */
+ public function issuers_table($issuers) {
+ global $CFG, $OUTPUT;
+
+ $table = new html_table();
+ $table->head = [
+ get_string('name'),
+ get_string('configuredstatus', 'tool_oauth2'),
+ get_string('loginissuer', 'tool_oauth2'),
+ get_string('discoverystatus', 'tool_oauth2'),
+ get_string('systemauthstatus', 'tool_oauth2'),
+ get_string('edit'),
+ ];
+ $table->attributes['class'] = 'admintable generaltable';
+ $data = [];
+
+ $index = 0;
+
+ foreach ($issuers as $issuer) {
+ // We need to handle the first and last ones specially.
+ $first = false;
+ if ($index == 0) {
+ $first = true;
+ }
+ $last = false;
+ if ($index == count($issuers) - 1) {
+ $last = true;
+ }
+
+ // Name.
+ $name = $issuer->get('name');
+ $image = $issuer->get('image');
+ if ($image) {
+ $name = '
' . $name;
+ }
+ $namecell = new html_table_cell($name);
+ $namecell->header = true;
+
+ // Configured.
+ if (!empty($issuer->get('clientid')) && !empty($issuer->get('clientsecret'))) {
+ $configured = $OUTPUT->pix_icon('yes', get_string('configured', 'tool_oauth2'), 'tool_oauth2');
+ } else {
+ $configured = $OUTPUT->pix_icon('no', get_string('notconfigured', 'tool_oauth2'), 'tool_oauth2');
+ }
+ $configuredstatuscell = new html_table_cell($configured);
+
+ // Login issuer.
+ if (!empty($issuer->get('showonloginpage'))) {
+ $loginissuer = $OUTPUT->pix_icon('yes', get_string('loginissuer', 'tool_oauth2'), 'tool_oauth2');
+ } else {
+ $loginissuer = $OUTPUT->pix_icon('no', get_string('notloginissuer', 'tool_oauth2'), 'tool_oauth2');
+ }
+ $loginissuerstatuscell = new html_table_cell($loginissuer);
+
+ // Discovered.
+ if (!empty($issuer->get('scopessupported'))) {
+ $discovered = $OUTPUT->pix_icon('yes', get_string('discovered', 'tool_oauth2'), 'tool_oauth2');
+ } else {
+ $discovered = $OUTPUT->pix_icon('no', get_string('notdiscovered', 'tool_oauth2'), 'tool_oauth2');
+ }
+ $discoverystatuscell = new html_table_cell($discovered);
+
+ // Connected.
+ if ($issuer->is_system_account_connected()) {
+ $systemauth = $OUTPUT->pix_icon('yes', get_string('systemaccountconnected', 'tool_oauth2'), 'tool_oauth2');
+ } else {
+ $systemauth = $OUTPUT->pix_icon('no', get_string('systemaccountnotconnected', 'tool_oauth2'), 'tool_oauth2');
+ }
+
+ if ($issuer->is_system_account_setup_supported()) {
+ $params = ['id' => $issuer->get('id'), 'action' => 'auth'];
+ $authurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
+ $icon = $OUTPUT->pix_icon('auth', get_string('connectsystemaccount', 'tool_oauth2'), 'tool_oauth2');
+ $authlink = html_writer::link($authurl, $icon);
+ $systemauth .= ' ' . $authlink;
+ }
+
+ $systemauthstatuscell = new html_table_cell($systemauth);
+
+ // Action links.
+ $links = '';
+ $editurl = new moodle_url('/admin/tool/oauth2/issuers.php', ['id' => $issuer->get('id'), 'action' => 'edit']);
+ $editlink = html_writer::link($editurl, $OUTPUT->pix_icon('t/edit', get_string('edit')));
+
+ $links .= ' ' . $editlink;
+ $deleteurl = new moodle_url('/admin/tool/oauth2/issuers.php', ['id' => $issuer->get('id'), 'action' => 'delete']);
+ $deletelink = html_writer::link($deleteurl, $OUTPUT->pix_icon('t/delete', get_string('delete')));
+ $links .= ' ' . $deletelink;
+ if (!$last) {
+ $params = ['id' => $issuer->get('id'), 'action' => 'movedown', 'sesskey' => sesskey()];
+ $movedownurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
+ $movedownlink = html_writer::link($movedownurl, $OUTPUT->pix_icon('t/down', get_string('movedown')));
+ $links .= ' ' . $movedownlink;
+ }
+ if (!$first) {
+ $params = ['id' => $issuer->get('id'), 'action' => 'moveup', 'sesskey' => sesskey()];
+ $moveupurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
+ $moveuplink = html_writer::link($moveupurl, $OUTPUT->pix_icon('t/up', get_string('moveup')));
+ $links .= ' ' . $moveuplink;
+ }
+
+ $editcell = new html_table_cell($links);
+
+ $row = new html_table_row([
+ $namecell,
+ $configuredstatuscell,
+ $loginissuerstatuscell,
+ $discoverystatuscell,
+ $systemauthstatuscell,
+ $editcell,
+ ]);
+
+ $data[] = $row;
+ $index++;
+ }
+ $table->data = $data;
+ return html_writer::table($table);
+ }
+}
diff --git a/admin/tool/oauth2/issuers.php b/admin/tool/oauth2/issuers.php
new file mode 100644
index 00000000000..d80f91d1e2a
--- /dev/null
+++ b/admin/tool/oauth2/issuers.php
@@ -0,0 +1,142 @@
+.
+
+/**
+ * OAuth 2 Configuration page.
+ *
+ * @package tool_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/tablelib.php');
+
+$PAGE->set_url('/admin/tool/oauth2/issuers.php');
+$PAGE->set_context(context_system::instance());
+$PAGE->set_pagelayout('admin');
+$strheading = get_string('pluginname', 'tool_oauth2');
+$PAGE->set_title($strheading);
+$PAGE->set_heading($strheading);
+
+require_login();
+
+require_capability('moodle/site:config', context_system::instance());
+
+$renderer = $PAGE->get_renderer('tool_oauth2');
+
+$action = optional_param('action', '', PARAM_ALPHAEXT);
+$idpid = optional_param('id', '', PARAM_RAW);
+$issuer = null;
+$mform = null;
+
+if ($idpid) {
+ $issuer = \core\oauth2\api::get_issuer($idpid);
+ if (!$issuer) {
+ print_error('invaliddata');
+ }
+}
+
+if ($action == 'edit') {
+ if ($issuer) {
+ $PAGE->navbar->add(get_string('editissuer', 'tool_oauth2', $issuer->get('name')));
+ } else {
+ $PAGE->navbar->add(get_string('createnewissuer', 'tool_oauth2'));
+ }
+
+ $mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer]);
+}
+
+if ($mform && $mform->is_cancelled()) {
+ redirect(new moodle_url('/admin/tool/oauth2/issuers.php'));
+} else if ($action == 'edit') {
+
+ if ($data = $mform->get_data()) {
+
+ try {
+ if (!empty($data->id)) {
+ core\oauth2\api::update_issuer($data);
+ } else {
+ core\oauth2\api::create_issuer($data);
+ }
+ redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
+ } catch (Exception $e) {
+ redirect($PAGE->url, $e->getMessage(), null, \core\output\notification::NOTIFY_ERROR);
+ }
+ } else {
+ echo $OUTPUT->header();
+ if ($issuer) {
+ echo $OUTPUT->heading(get_string('editissuer', 'tool_oauth2', $issuer->get('name')));
+ } else {
+ echo $OUTPUT->heading(get_string('createnewissuer', 'tool_oauth2'));
+ }
+ $mform->display();
+ echo $OUTPUT->footer();
+ }
+
+} else if ($action == 'delete') {
+
+ if (!optional_param('confirm', false, PARAM_BOOL)) {
+ $continueparams = ['action' => 'delete', 'id' => $idpid, 'sesskey' => sesskey(), 'confirm' => true];
+ $continueurl = new moodle_url('/admin/tool/oauth2/issuers.php', $continueparams);
+ $cancelurl = new moodle_url('/admin/tool/oauth2/issuers.php');
+ echo $OUTPUT->header();
+ echo $OUTPUT->confirm(get_string('deleteconfirm', 'tool_oauth2', $issuer->get('name')), $continueurl, $cancelurl);
+ echo $OUTPUT->footer();
+ } else {
+ require_sesskey();
+ core\oauth2\api::delete_issuer($idpid);
+ redirect($PAGE->url, get_string('issuerdeleted', 'tool_oauth2'), null, \core\output\notification::NOTIFY_SUCCESS);
+ }
+
+} else if ($action == 'auth') {
+
+ if (!optional_param('confirm', false, PARAM_BOOL)) {
+ $continueparams = ['action' => 'auth', 'id' => $idpid, 'sesskey' => sesskey(), 'confirm' => true];
+ $continueurl = new moodle_url('/admin/tool/oauth2/issuers.php', $continueparams);
+ $cancelurl = new moodle_url('/admin/tool/oauth2/issuers.php');
+ echo $OUTPUT->header();
+ echo $OUTPUT->confirm(get_string('authconfirm', 'tool_oauth2', $issuer->get('name')), $continueurl, $cancelurl);
+ echo $OUTPUT->footer();
+ } else {
+ require_sesskey();
+ $params = ['sesskey' => sesskey(), 'id' => $idpid, 'action' => 'auth', 'confirm' => true, 'response' => true];
+ if (core\oauth2\api::connect_system_account($issuer, new moodle_url('/admin/tool/oauth2/issuers.php', $params))) {
+ redirect($PAGE->url, get_string('authconnected', 'tool_oauth2'), null, \core\output\notification::NOTIFY_SUCCESS);
+ } else {
+ redirect($PAGE->url, get_string('authnotconnected', 'tool_oauth2'), null, \core\output\notification::NOTIFY_ERROR);
+ }
+ }
+} else if ($action == 'moveup') {
+ require_sesskey();
+ core\oauth2\api::move_up_issuer($idpid);
+ redirect($PAGE->url);
+
+} else if ($action == 'movedown') {
+ require_sesskey();
+ core\oauth2\api::move_down_issuer($idpid);
+ redirect($PAGE->url);
+
+} else {
+ echo $OUTPUT->header();
+ $issuers = core\oauth2\api::get_all_issuers();
+ echo $renderer->issuers_table($issuers);
+
+ $addurl = new moodle_url('/admin/tool/oauth2/issuers.php', ['action' => 'edit']);
+ echo $renderer->single_button($addurl, get_string('createnewissuer', 'tool_oauth2'));
+ echo $OUTPUT->footer();
+}
diff --git a/admin/tool/oauth2/lang/en/tool_oauth2.php b/admin/tool/oauth2/lang/en/tool_oauth2.php
new file mode 100644
index 00000000000..73bce13bed4
--- /dev/null
+++ b/admin/tool/oauth2/lang/en/tool_oauth2.php
@@ -0,0 +1,63 @@
+.
+
+/**
+ * Strings for component 'tool_oauth2', language 'en'
+ *
+ * @package tool_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['pluginname'] = 'Open ID Connect configuration';
+$string['editissuer'] = 'Edit identity issuer: {$a}';
+$string['issuername'] = 'Name';
+$string['issuername_help'] = 'Name of the identity issuer. May be displayed on login page.';
+$string['issuerimage'] = 'Logo URL';
+$string['issuerimage_help'] = 'An image url used to show a logo for this issuer. May be displayed on login page.';
+$string['issuerclientid'] = 'Client Id';
+$string['issuerclientid_help'] = 'The OAuth client ID for this issuer.';
+$string['issuerclientsecret'] = 'Client Secret';
+$string['issuerclientsecret_help'] = 'The OAuth client secret for this issuer.';
+$string['issuerbaseurl'] = 'Service base url';
+$string['issuerbaseurl_help'] = 'Base url used to access the service.';
+$string['issuershowonloginpage'] = 'Show on login page.';
+$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 login with accounts from this issuer.';
+$string['issuerbehaviour'] = 'Behaviour';
+$string['issuerbehaviour_help'] = 'Choose from one of the supported behaviours.
+
+* OAuth 2.0 - OAuth 2.0 API with no authentication
+* OpenID Connect - Standards based OAuth 2.0 API with Authentication
+* Microsoft OAuth 2.0 - Non-standard OAuth 2.0 combined with Microsoft Graph API';
+$string['savechanges'] = 'Save changes';
+$string['configuredstatus'] = 'Configured';
+$string['discoverystatus'] = 'Discovery';
+$string['systemauthstatus'] = 'System account connected';
+$string['configured'] = 'Configured';
+$string['notconfigured'] = 'Not configured';
+$string['discovered'] = 'Service discovery successful';
+$string['notdiscovered'] = 'Service discovery not successful';
+$string['loginissuer'] = 'Allow login';
+$string['notloginissuer'] = 'Do not allow login';
+$string['systemaccountconnected'] = 'System account connected';
+$string['systemaccountnotconnected'] = 'System account not connected';
+$string['createnewissuer'] = 'Create new identity issuer';
+$string['deleteconfirm'] = 'Are you sure you want to delete the identity issuer "{$a}"? Any plugins relying on this issuer will stop working.';
+$string['issuerdeleted'] = 'Identity issuer deleted';
+$string['connectsystemaccount'] = 'Connect to a system account';
+$string['authconfirm'] = 'This action will grant permanent API access to Moodle for the authenticated account. This is intended to be used as a system account for managing files owned by Moodle.';
+$string['authconnected'] = 'The system account is now connected for offline access';
+$string['authnotconnected'] = 'The system account was not connected for offline access';
diff --git a/admin/tool/oauth2/pix/auth.svg b/admin/tool/oauth2/pix/auth.svg
new file mode 100644
index 00000000000..409824ad998
--- /dev/null
+++ b/admin/tool/oauth2/pix/auth.svg
@@ -0,0 +1,3 @@
+
+]>
\ No newline at end of file
diff --git a/admin/tool/oauth2/pix/no.svg b/admin/tool/oauth2/pix/no.svg
new file mode 100644
index 00000000000..0185d868b95
--- /dev/null
+++ b/admin/tool/oauth2/pix/no.svg
@@ -0,0 +1,3 @@
+
+]>
\ No newline at end of file
diff --git a/admin/tool/oauth2/pix/yes.svg b/admin/tool/oauth2/pix/yes.svg
new file mode 100644
index 00000000000..714d4c73c79
--- /dev/null
+++ b/admin/tool/oauth2/pix/yes.svg
@@ -0,0 +1,3 @@
+
+]>
\ No newline at end of file
diff --git a/admin/tool/oauth2/settings.php b/admin/tool/oauth2/settings.php
new file mode 100644
index 00000000000..950a02137d1
--- /dev/null
+++ b/admin/tool/oauth2/settings.php
@@ -0,0 +1,29 @@
+.
+
+/**
+ * Oauth2 system configuration.
+ *
+ * @package tool_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if ($hassiteconfig) {
+ $ADMIN->add('server', new admin_externalpage('oauth2', new lang_string('pluginname','tool_oauth2'), "$CFG->wwwroot/$CFG->admin/tool/oauth2/issuers.php"));
+}
diff --git a/admin/tool/oauth2/tests/behat/manage_tasks.feature b/admin/tool/oauth2/tests/behat/manage_tasks.feature
new file mode 100644
index 00000000000..73fc27dd61b
--- /dev/null
+++ b/admin/tool/oauth2/tests/behat/manage_tasks.feature
@@ -0,0 +1,53 @@
+@tool @tool_task @javascript
+Feature: Manage scheduled tasks
+ In order to configure scheduled tasks
+ As an admin
+ I need to be able to disable, enable, edit and reset to default scheduled tasks
+
+ Background:
+ Given I log in as "admin"
+ And I navigate to "Scheduled tasks" node in "Site administration > Server"
+
+ Scenario: Disable scheduled task
+ When I click on "Edit task schedule: Log table cleanup" "link" in the "Log table cleanup" "table_row"
+ Then I should see "Edit task schedule: Log table cleanup"
+ And I set the following fields to these values:
+ | disabled | 1 |
+ And I press "Save changes"
+ Then I should see "Changes saved"
+ And I should see "Task disabled" in the "Log table cleanup" "table_row"
+
+ Scenario: Enable scheduled task
+ When I click on "Edit task schedule: Log table cleanup" "link" in the "Log table cleanup" "table_row"
+ Then I should see "Edit task schedule: Log table cleanup"
+ And I set the following fields to these values:
+ | disabled | 0 |
+ And I press "Save changes"
+ Then I should see "Changes saved"
+ And I should not see "Task disabled" in the "Log table cleanup" "table_row"
+
+ Scenario: Edit scheduled task
+ When I click on "Edit task schedule: Log table cleanup" "link" in the "Log table cleanup" "table_row"
+ Then I should see "Edit task schedule: Log table cleanup"
+ And I set the following fields to these values:
+ | minute | */5 |
+ | hour | 1 |
+ | day | 2 |
+ | month | 3 |
+ | dayofweek | 4 |
+ And I press "Save changes"
+ Then I should see "Changes saved"
+ And the following should exist in the "admintable" table:
+ | Component | Minute | Hour | Day | Day of week | Month |
+ | Standard log | */5 | 1 | 2 | 4 | 3 |
+
+ Scenario: Reset scheduled task to default
+ When I click on "Edit task schedule: Log table cleanup" "link" in the "Log table cleanup" "table_row"
+ Then I should see "Edit task schedule: Log table cleanup"
+ And I set the following fields to these values:
+ | resettodefaults | 1 |
+ And I press "Save changes"
+ Then I should see "Changes saved"
+ And the following should not exist in the "admintable" table:
+ | Name | Component | Minute | Hour | Day | Day of week | Month |
+ | Log table cleanup | Standard log | */5 | 1 | 2 | 4 | 3 |
\ No newline at end of file
diff --git a/admin/tool/oauth2/tests/form_test.php b/admin/tool/oauth2/tests/form_test.php
new file mode 100644
index 00000000000..a9b8aa12bf9
--- /dev/null
+++ b/admin/tool/oauth2/tests/form_test.php
@@ -0,0 +1,275 @@
+.
+
+/**
+ * File containing tests for the mform class.
+ *
+ * @package tool_task
+ * @copyright 2014 onwards Ankit Agarwal
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+/**
+ * Mform test class.
+ *
+ * @package tool_task
+ * @copyright 2014 onwards Ankit Agarwal
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late
+ */
+class tool_task_form_testcase extends advanced_testcase {
+
+ /**
+ * Test validations for minute field.
+ */
+ public function test_validate_fields_minute() {
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '*');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '65');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '*/');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '*/1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '*/20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '*/65');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '1,2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '2,20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '20,30,45');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '65,20,30');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '25,75');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '1-2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '2-20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '20-30');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '65-20');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('minute', '25-75');
+ $this->assertFalse($valid);
+ }
+
+ /**
+ * Test validations for minute hour.
+ */
+ public function test_validate_fields_hour() {
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '*');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '65');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '*/');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '*/1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '*/20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '*/65');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '1,2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '2,20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '20,30,45');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '65,20,30');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '25,75');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '1-2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '2-20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '20-30');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '65-20');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('hour', '25-75');
+ $this->assertFalse($valid);
+ }
+
+ /**
+ * Test validations for day field.
+ */
+ public function test_validate_fields_day() {
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '*');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '65');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '35');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '*/');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '*/1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '*/20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '*/65');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '*/35');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '1,2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '2,20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '20,30,25');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '65,20,30');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '25,35');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '1-2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '2-20');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '20-30');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '65-20');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('day', '25-35');
+ $this->assertFalse($valid);
+ }
+
+ /**
+ * Test validations for month field.
+ */
+ public function test_validate_fields_month() {
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '*');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '10');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '13');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '35');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '*/');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '*/1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '*/12');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '*/13');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '*/35');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '1,2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '2,11');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '2,10,12');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '65,2,13');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '25,35');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '1-2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '2-12');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '3-6');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '65-2');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('month', '25-26');
+ $this->assertFalse($valid);
+ }
+
+ /**
+ * Test validations for dayofweek field.
+ */
+ public function test_validate_fields_dayofweek() {
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '*');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '0');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '6');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '7');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '20');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '*/');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '*/1');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '*/6');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '*/13');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '*/35');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '1,2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '2,6');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '2,6,3');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '65,2,13');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '25,35');
+ $this->assertFalse($valid);
+
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '1-2');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '2-6');
+ $this->assertTrue($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '65-2');
+ $this->assertFalse($valid);
+ $valid = \tool_task_edit_scheduled_task_form::validate_fields('dayofweek', '3-7');
+ $this->assertFalse($valid);
+ }
+}
+
diff --git a/admin/tool/oauth2/version.php b/admin/tool/oauth2/version.php
new file mode 100644
index 00000000000..8d18262c360
--- /dev/null
+++ b/admin/tool/oauth2/version.php
@@ -0,0 +1,30 @@
+.
+
+/**
+ * Plugin version info
+ *
+ * @package tool_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2016120500; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2016112900; // Requires this Moodle version
+$plugin->component = 'tool_oauth2'; // Full name of the plugin (used for diagnostics)
+
diff --git a/auth/classes/output/login.php b/auth/classes/output/login.php
index a58dfe64433..bebfed86915 100644
--- a/auth/classes/output/login.php
+++ b/auth/classes/output/login.php
@@ -121,10 +121,17 @@ class login implements renderable, templatable {
}
public function export_for_template(renderer_base $output) {
- global $CFG;
+ global $CFG, $OUTPUT;
$identityproviders = array_map(function($idp) use ($output) {
- $idp['icon'] = $idp['icon']->export_for_template($output);
+ global $OUTPUT;
+
+ if (!empty($idp['icon'])) {
+ $idp['iconurl'] = $OUTPUT->pix_url($idp['icon']->key, $idp['icon']->component);
+ } else if ($idp['iconurl'] instanceof moodle_url) {
+ $idp['iconurl'] = $idp['iconurl']->out(false);
+ }
+ unset($idp['icon']);
if ($idp['url'] instanceof moodle_url) {
$idp['url'] = $idp['url']->out(false);
}
diff --git a/auth/oauth2/auth.php b/auth/oauth2/auth.php
new file mode 100644
index 00000000000..0d5ecd7f79a
--- /dev/null
+++ b/auth/oauth2/auth.php
@@ -0,0 +1,40 @@
+.
+
+/**
+ * Open ID authentication.
+ *
+ * @package auth_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/authlib.php');
+
+/**
+ * Plugin for oauth2 authentication.
+ *
+ * @package auth_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+class auth_plugin_oauth2 extends \auth_oauth2\auth {
+
+}
+
+
diff --git a/auth/oauth2/classes/auth.php b/auth/oauth2/classes/auth.php
new file mode 100644
index 00000000000..4bf2cc9fb68
--- /dev/null
+++ b/auth/oauth2/classes/auth.php
@@ -0,0 +1,350 @@
+.
+
+/**
+ * Anobody can login with any password.
+ *
+ * @package auth_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+
+namespace auth_oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+use pix_icon;
+use moodle_url;
+use core_text;
+use stdClass;
+use core\oauth2\issuer;
+use core\oauth2\client;
+
+require_once($CFG->libdir.'/authlib.php');
+
+/**
+ * Plugin for oauth2 authentication.
+ *
+ * @package auth_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+class auth extends \auth_plugin_base {
+
+ /**
+ * @var stdClass $userinfo The set of user info returned from the oauth handshake
+ */
+ private static $userinfo;
+
+ /**
+ * @var stdClass $userpicture The url to a picture.
+ */
+ private static $userpicture;
+
+ /**
+ * Constructor.
+ */
+ public function __construct() {
+ $this->authtype = 'oauth2';
+ $this->config = get_config('auth_oauth2');
+ }
+
+ /**
+ * Returns true if the username and password work or don't exist and false
+ * if the user exists and the password is wrong.
+ *
+ * @param string $username The username
+ * @param string $password The password
+ * @return bool Authentication success or failure.
+ */
+ public function user_login($username, $password) {
+ $cached = $this->get_static_user_info();
+ $verifyusername = $cached['username'];
+ if ($verifyusername == $username) {
+ return true;
+ }
+ }
+
+ /**
+ * We don't want to allow users setting an internal password.
+ *
+ * @return bool
+ */
+ public function prevent_local_passwords() {
+ return true;
+ }
+
+ /**
+ * Returns true if this authentication plugin is 'internal'.
+ *
+ * @return bool
+ */
+ public function is_internal() {
+ return false;
+ }
+
+ /**
+ * Indicates if moodle should automatically update internal user
+ * records with data from external sources using the information
+ * from auth_plugin_base::get_userinfo().
+ *
+ * @return bool true means automatically copy data from ext to user table
+ */
+ public function is_synchronised_with_external() {
+ return true;
+ }
+
+ /**
+ * Returns true if this authentication plugin can change the user's
+ * password.
+ *
+ * @return bool
+ */
+ public function can_change_password() {
+ return false;
+ }
+
+ /**
+ * Returns the URL for changing the user's pw, or empty if the default can
+ * be used.
+ *
+ * @return moodle_url
+ */
+ public function change_password_url() {
+ return null;
+ }
+
+ /**
+ * Returns true if plugin allows resetting of internal password.
+ *
+ * @return bool
+ */
+ public function can_reset_password() {
+ return false;
+ }
+
+ /**
+ * Returns true if plugin can be manually set.
+ *
+ * @return bool
+ */
+ public function can_be_manually_set() {
+ return true;
+ }
+
+ /**
+ * Prints a form for configuring this authentication plugin.
+ *
+ * This function is called from admin/auth.php, and outputs a full page with
+ * a form for configuring this plugin.
+ *
+ * @param stdClass $config
+ * @param string $err
+ * @param array userfields
+ */
+ public function config_form($config, $err, $userfields) {
+ echo get_string('plugindescription', 'auth_oauth2');
+
+ // Force all fields updated on login and locked.
+
+ foreach ($userfields as $field) {
+ set_config('field_updatelocal_' . $field, 'onlogin', 'auth_oauth2');
+ set_config('field_lock_' . $field, 'unlockedifempty', 'auth_oauth2');
+ }
+ return;
+ }
+
+ /**
+ * Return the userinfo from the oauth handshake. Will only be valid
+ * for the logged in user.
+ */
+ public function get_userinfo($username) {
+ $cached = $this->get_static_user_info();
+ if (!empty($cached) && $cached['username'] == $username) {
+ return $cached;
+ }
+ return false;
+ }
+
+ private function is_ready_for_login_page($issuer) {
+ return !empty($issuer->get('clientid')) &&
+ !empty($issuer->get('clientsecret')) &&
+ $issuer->is_authentication_supported() &&
+ !empty($issuer->get('showonloginpage'));
+ }
+
+ /**
+ * Return a list of identity providers to display on the login page.
+ */
+ public function loginpage_idp_list($wantsurl) {
+ $providers = \core\oauth2\api::get_all_issuers();
+ $result = [];
+ if (empty($wantsurl)) {
+ $wantsurl = '/';
+ }
+ foreach ($providers as $idp) {
+ if ($this->is_ready_for_login_page($idp)) {
+ $params = ['id' => $idp->get('id'), 'wantsurl' => $wantsurl, 'sesskey' => sesskey()];
+ $url = new moodle_url('/auth/oauth2/login.php', $params);
+ $icon = $idp->get('image');
+ $result[] = ['url' => $url, 'iconurl' => $icon, 'name' => $idp->get('name')];
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Statically cache the user info from the oauth handshake
+ * @param stdClass $userinfo
+ */
+ private function set_static_user_info($userinfo) {
+ self::$userinfo = $userinfo;
+ }
+
+ /**
+ * Get the static cached user info
+ * @return stdClass
+ */
+ private function get_static_user_info() {
+ return self::$userinfo;
+ }
+
+ /**
+ * Statically cache the user picture from the oauth handshake
+ * @param string $userpicture
+ */
+ private function set_static_user_picture($userpicture) {
+ self::$userpicture = $userpicture;
+ }
+
+ /**
+ * Get the static cached user picture
+ * @return string
+ */
+ private function get_static_user_picture() {
+ return self::$userpicture;
+ }
+
+ /**
+ * If this user has no picture - but we got one from oauth - set it.
+ * @return boolean True if the image was updated.
+ */
+ private function update_picture($user) {
+ global $CFG, $DB, $USER;
+
+ require_once($CFG->libdir . '/filelib.php');
+ require_once($CFG->libdir . '/gdlib.php');
+
+ $fs = get_file_storage();
+ $userid = $user->id;
+ if (!empty($user->picture)) {
+ return false;
+ }
+ $picture = $this->get_static_user_picture();
+ if (empty($picture)) {
+ return false;
+ }
+
+ $context = \context_user::instance($userid, MUST_EXIST);
+ $fs->delete_area_files($context->id, 'user', 'newicon');
+
+ $filerecord = array(
+ 'contextid' => $context->id,
+ 'component' => 'user',
+ 'filearea' => 'newicon',
+ 'itemid' => 0,
+ 'filepath' => '/',
+ 'filename' => 'image'
+ );
+
+ try {
+ $fs->create_file_from_string($filerecord, $picture);
+ } catch (\file_exception $e) {
+ return get_string($e->errorcode, $e->module, $e->a);
+ }
+
+ $iconfile = $fs->get_area_files($context->id, 'user', 'newicon', false, 'itemid', false);
+
+ // There should only be one.
+ $iconfile = reset($iconfile);
+
+ // Something went wrong while creating temp file - remove the uploaded file.
+ if (!$iconfile = $iconfile->copy_content_to_temp()) {
+ $fs->delete_area_files($context->id, 'user', 'newicon');
+ return false;
+ }
+
+ // Copy file to temporary location and the send it for processing icon.
+ $newpicture = (int) process_new_icon($context, 'user', 'icon', 0, $iconfile);
+ // Delete temporary file.
+ @unlink($iconfile);
+ // Remove uploaded file.
+ $fs->delete_area_files($context->id, 'user', 'newicon');
+ // Set the user's picture.
+ $updateuser = new stdClass();
+ $updateuser->id = $userid;
+ $updateuser->picture = $newpicture;
+ $USER->picture = $newpicture;
+ user_update_user($updateuser);
+ return true;
+ }
+
+ /**
+ * Complete the login process after oauth handshake is complete.
+ * @param \core\oauth2\client $client
+ * @param string $redirecturl
+ * @return none Either redirects or throws an exception
+ */
+ public function complete_login(client $client, $redirecturl) {
+ global $CFG, $SESSION;
+
+ $userinfo = $client->get_userinfo();
+
+ if (!$userinfo) {
+ $errormsg = get_string('notloggedin', 'auth_oauth2');
+ $SESSION->loginerrormsg = $errormsg;
+ redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php'));
+ }
+
+ $userinfo['username'] = trim(core_text::strtolower($userinfo['username']));
+
+ if (!empty($userinfo['picture'])) {
+ $this->set_static_user_picture($userinfo['picture']);
+ unset($userinfo['picture']);
+ }
+
+ if (!empty($userinfo['lang'])) {
+ $userinfo['lang'] = str_replace('-', '_', trim(core_text::strtolower($userinfo['lang'])));
+ if (!get_string_manager()->translation_exists($userinfo['lang'], false)) {
+ unset($userinfo['lang']);
+ }
+ }
+ $this->set_static_user_info($userinfo);
+
+ $user = authenticate_user_login($userinfo['username'], '');
+
+ if ($user) {
+ complete_user_login($user);
+ $this->update_picture($user);
+ redirect($redirecturl);
+ }
+ $errormsg = get_string('notloggedin', 'auth_oauth2');
+ $SESSION->loginerrormsg = $errormsg;
+ redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php'));
+ }
+}
+
+
diff --git a/auth/oauth2/lang/en/auth_oauth2.php b/auth/oauth2/lang/en/auth_oauth2.php
new file mode 100644
index 00000000000..d221f328364
--- /dev/null
+++ b/auth/oauth2/lang/en/auth_oauth2.php
@@ -0,0 +1,29 @@
+.
+
+/**
+ * Strings for component 'auth_oauth2', language 'en'.
+ *
+ * @package auth_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['auth_oauth2description'] = 'OAuth 2 standards based authentication';
+$string['auth_oauth2settings'] = 'OAuth 2 authentication settings.';
+$string['pluginname'] = 'OAuth 2';
+$string['plugindescription'] = 'This authentication plugin displays a list of the configured identity providers on the moodle login page. Selecting an identity provider allows users to login with their credentials from an OAuth 2 provider.';
+$string['notloggedin'] = 'The login attempt failed.';
diff --git a/auth/oauth2/login.php b/auth/oauth2/login.php
new file mode 100644
index 00000000000..835a39b9ba7
--- /dev/null
+++ b/auth/oauth2/login.php
@@ -0,0 +1,45 @@
+.
+
+/**
+ * Open ID authentication.
+ *
+ * @package auth_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+
+require_once('../../config.php');
+
+$issuerid = required_param('id', PARAM_INT);
+$wantsurl = new moodle_url(optional_param('wantsurl', '/', PARAM_URL));
+
+require_sesskey();
+
+$issuer = new \core\oauth2\issuer($issuerid);
+
+$returnparams = ['wantsurl' => $wantsurl, 'sesskey' => sesskey(), 'id' => $issuerid];
+$returnurl = new moodle_url('/auth/oauth2/login.php', $returnparams);
+
+$client = \core\oauth2\api::get_user_oauth_client($issuer, $returnurl);
+
+if ($client) {
+ $auth = new \auth_oauth2\auth();
+ $auth->complete_login($client, $wantsurl);
+} else {
+ throw new moodle_exception('Could not get an OAuth client.');
+}
+
diff --git a/auth/oauth2/version.php b/auth/oauth2/version.php
new file mode 100644
index 00000000000..11f8fe144dc
--- /dev/null
+++ b/auth/oauth2/version.php
@@ -0,0 +1,29 @@
+.
+
+/**
+ * Version information
+ *
+ * @package auth_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2016120500; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2016112900; // Requires this Moodle version
+$plugin->component = 'auth_oauth2'; // Full name of the plugin (used for diagnostics)
diff --git a/lib/classes/oauth2/api.php b/lib/classes/oauth2/api.php
new file mode 100644
index 00000000000..00230d1620e
--- /dev/null
+++ b/lib/classes/oauth2/api.php
@@ -0,0 +1,352 @@
+.
+
+/**
+ * Class for loading/storing oauth2 endpoints from the DB.
+ *
+ * @package core_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\oauth2;
+
+require_once($CFG->libdir . '/filelib.php');
+
+use context_system;
+use curl;
+use stdClass;
+use moodle_exception;
+use moodle_url;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Static list of api methods for system oauth2 configuration.
+ *
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class api {
+
+ /**
+ * Called from install.php and upgrade.php - install the default list of issuers
+ * @return int The number of issuers installed.
+ */
+ public static function install_default_issuers() {
+ // Setup default list of identity issuers.
+ $record = (object) [
+ 'name' => 'Google',
+ 'image' => 'https://accounts.google.com/favicon.ico',
+ 'behaviour' => issuer::BEHAVIOUR_OPENID_CONNECT,
+ 'baseurl' => 'http://accounts.google.com/',
+ 'clientid' => '',
+ 'clientsecret' => '',
+ 'showonloginpage' => true
+ ];
+
+ $issuer = new issuer(0, $record);
+ $issuer->create();
+
+ $record = (object) [
+ 'issuerid' => $issuer->get('id'),
+ 'name' => 'discovery_endpoint',
+ 'url' => 'https://accounts.google.com/.well-known/openid-configuration'
+ ];
+ $endpoint = new endpoint(0, $record);
+ $endpoint->create();
+
+ $record = (object) [
+ 'name' => 'Microsoft',
+ 'image' => 'https://www.microsoft.com/favicon.ico',
+ 'behaviour' => issuer::BEHAVIOUR_MICROSOFT,
+ 'baseurl' => 'http://login.microsoftonline.com/common/oauth2/v2.0/',
+ 'clientid' => '',
+ 'clientsecret' => '',
+ 'showonloginpage' => true
+ ];
+
+ $issuer = new issuer(0, $record);
+ $issuer->create();
+
+ $record = (object) [
+ 'issuerid' => $issuer->get('id'),
+ 'name' => 'authorization_endpoint',
+ 'url' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
+ ];
+ $endpoint = new endpoint(0, $record);
+ $endpoint->create();
+
+ $record = (object) [
+ 'issuerid' => $issuer->get('id'),
+ 'name' => 'token_endpoint',
+ 'url' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
+ ];
+ $endpoint = new endpoint(0, $record);
+ $endpoint->create();
+
+ $record = (object) [
+ 'issuerid' => $issuer->get('id'),
+ 'name' => 'end_session_endpoint',
+ 'url' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
+ ];
+ $endpoint = new endpoint(0, $record);
+ $endpoint->create();
+ return issuer::count_records();
+ }
+
+ public static function get_all_issuers() {
+ return issuer::get_records([], 'sortorder');
+ }
+
+ public static function get_issuer($id) {
+ return new issuer($id);
+ }
+
+ public static function get_system_account(issuer $issuer) {
+ return system_account::get_record(['issuerid' => $issuer->get('id')]);
+ }
+
+ public static function get_system_oauth_client(issuer $issuer) {
+ }
+
+ public static function get_user_oauth_client(issuer $issuer, moodle_url $currenturl, $additionalscopes = '') {
+ $client = \core\oauth2\client::create($issuer, $currenturl, $additionalscopes);
+
+ if (!$client->is_logged_in()) {
+ redirect($client->get_login_url());
+ }
+ return $client;
+ }
+
+ public static function get_endpoints(issuer $issuer) {
+ require_capability('moodle/site:config', context_system::instance());
+
+ return endpoint::get_records(['issuerid' => $issuer->get('id')]);
+ }
+
+ protected static function guess_image($issuer) {
+ if (empty($issuer->get('image'))) {
+ $baseurl = parse_url($issuer->get('discoveryurl'));
+ $imageurl = $baseurl['scheme'] . '://' . $baseurl['host'] . '/favicon.ico';
+ $issuer->set('image', $imageurl);
+ $issuer->update();
+ }
+ }
+
+ /**
+ * If the behaviour supports discovery for this issuer, try and determine the list of valid endpoints.
+ *
+ * @param issuer $issuer
+ * @return int The number of discovered services.
+ */
+ protected static function discover_endpoints($issuer) {
+ $curl = new curl();
+
+ if ($issuer->get('behaviour') != issuer::BEHAVIOUR_OPENID_CONNECT) {
+ return 0;
+ }
+
+ $url = $issuer->get_endpoint_url('discovery');
+ if (!$url) {
+ $url = $issuer->get('url') . '/.well-known/openid-configuration';
+ }
+
+ if (!$json = $curl->get($issuer->get_endpoint_url('discovery'))) {
+ $msg = 'Could not discover end points for identity issuer' . $issuer->get('name');
+ throw new moodle_exception($msg);
+ }
+
+ if ($msg = $curl->error) {
+ throw new moodle_exception('Could not discover service endpoints: ' . $msg);
+ }
+
+ $info = json_decode($json);
+ if (empty($info)) {
+ $msg = 'Could not discover end points for identity issuer' . $issuer->get('name');
+ throw new moodle_exception($msg);
+ }
+
+ foreach (endpoint::get_records(['issuerid' => $issuer->get('id')]) as $endpoint) {
+ if ($endpoint->get('name') != 'discovery_endpoint') {
+ $endpoint->delete();
+ }
+ }
+
+ foreach ($info as $key => $value) {
+ if (substr_compare($key, '_endpoint', - strlen('_endpoint')) === 0) {
+ $record = new stdClass();
+ $record->issuerid = $issuer->get('id');
+ $record->name = $key;
+ $record->url = $value;
+
+ $endpoint = new endpoint(0, $record);
+ $endpoint->create();
+ }
+
+ if ($key == 'scopes_supported') {
+ $issuer->set('scopessupported', implode(' ', $value));
+ $issuer->update();
+ }
+ }
+
+ return endpoint::count_records(['issuerid' => $issuer->get('id')]);
+ }
+
+ public static function update_issuer($data) {
+ require_capability('moodle/site:config', context_system::instance());
+ $issuer = new issuer(0, $data);
+
+ // Will throw exceptions on validation failures.
+ $issuer->update();
+
+ // Perform service discovery.
+ self::discover_endpoints($issuer);
+ self::guess_image($issuer);
+ }
+
+ public static function create_issuer($data) {
+ require_capability('moodle/site:config', context_system::instance());
+ $issuer = new issuer(0, $data);
+
+ // Will throw exceptions on validation failures.
+ $issuer->create();
+
+ // Perform service discovery.
+ self::discover_endpoints($issuer);
+ self::guess_image($issuer);
+ }
+
+ /**
+ * Reorder this identity issuer.
+ *
+ * Requires moodle/site:config capability at the system context.
+ *
+ * @param int $id The id of the identity issuer to move.
+ * @return boolean
+ */
+ public static function move_up_issuer($id) {
+ require_capability('moodle/site:config', context_system::instance());
+ $current = new issuer($id);
+
+ $sortorder = $current->get('sortorder');
+ if ($sortorder == 0) {
+ return false;
+ }
+
+ $sortorder = $sortorder - 1;
+ $current->set('sortorder', $sortorder);
+
+ $filters = array('sortorder' => $sortorder);
+ $children = issuer::get_records($filters, 'id');
+ foreach ($children as $needtoswap) {
+ $needtoswap->set('sortorder', $sortorder + 1);
+ $needtoswap->update();
+ }
+
+ // OK - all set.
+ $result = $current->update();
+
+ return $result;
+ }
+
+ public static function move_down_issuer($id) {
+ require_capability('moodle/site:config', context_system::instance());
+ $current = new issuer($id);
+
+ $max = issuer::count_records();
+ if ($max > 0) {
+ $max--;
+ }
+
+ $sortorder = $current->get('sortorder');
+ if ($sortorder >= $max) {
+ return false;
+ }
+ $sortorder = $sortorder + 1;
+ $current->set('sortorder', $sortorder);
+
+ $filters = array('sortorder' => $sortorder);
+ $children = issuer::get_records($filters);
+ foreach ($children as $needtoswap) {
+ $needtoswap->set('sortorder', $sortorder - 1);
+ $needtoswap->update();
+ }
+
+ // OK - all set.
+ $result = $current->update();
+
+ return $result;
+ }
+
+ public static function delete_issuer($id) {
+ require_capability('moodle/site:config', context_system::instance());
+ $issuer = new issuer($id);
+
+ $systemaccount = self::get_system_account($issuer);
+ if ($systemaccount) {
+ $systemaccount->delete();
+ }
+ $endpoints = self::get_endpoints($issuer);
+ if ($endpoints) {
+ foreach ($endpoints as $endpoint) {
+ $endpoint->delete();
+ }
+ }
+
+ // Will throw exceptions on validation failures.
+ $issuer->delete();
+ }
+
+ public static function connect_system_account($issuer, $returnurl) {
+ require_capability('moodle/site:config', context_system::instance());
+
+ // We need to authenticate with an oauth 2 client AS a system user and get a refresh token for offline access.
+ $scopesrequired = 'openid email profile';
+
+ // Allow callbacks to inject non-standard scopes to the auth request.
+
+ $client = client::create($issuer, $returnurl, $scopesrequired, true);
+
+ if (!optional_param('response', false, PARAM_BOOL)) {
+ $client->log_out();
+ }
+
+ if (!$client->is_logged_in()) {
+ redirect($client->get_login_url());
+ }
+
+ $refreshtoken = $client->get_refresh_token();
+ if (!$refreshtoken) {
+ return false;
+ }
+
+ $systemaccount = self::get_system_account($issuer);
+ if ($systemaccount) {
+ $systemaccount->delete();
+ }
+ $record = new stdClass();
+ $record->issuerid = $issuer->get('id');
+ $record->refreshtoken = $refreshtoken;
+ $record->grantedscopes = $scopesrequired;
+
+ $systemaccount = new system_account(0, $record);
+
+ $systemaccount->create();
+
+ $client->log_out();
+ return true;
+ }
+}
diff --git a/lib/classes/oauth2/client.php b/lib/classes/oauth2/client.php
new file mode 100644
index 00000000000..7410e64fad5
--- /dev/null
+++ b/lib/classes/oauth2/client.php
@@ -0,0 +1,117 @@
+.
+
+/**
+ * Configurable oauth2 client class.
+ *
+ * @package core\oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/oauthlib.php');
+require_once($CFG->libdir . '/filelib.php');
+
+use moodle_url;
+use curl;
+
+/**
+ * Configurable oauth2 client class where the urls come from DB.
+ *
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class client extends \oauth2_client {
+
+ /** @var \core\oauth2\issuer $issuer */
+ private $issuer;
+
+ /** @var bool $system */
+ protected $system = false;
+
+ /**
+ * Constructor.
+ *
+ * @param issuer $issuer
+ * @param moodle_url $returnurl
+ */
+ public function __construct(issuer $issuer, moodle_url $returnurl, $scopesrequired, $system) {
+ $this->issuer = $issuer;
+ $this->system = $system;
+ $scopes = $this->get_login_scopes();
+ $additionalscopes = explode(' ', $scopesrequired);
+
+ foreach ($additionalscopes as $scope) {
+ if (strpos(' ' . $scopes . ' ', ' ' . $scope . ' ') === false) {
+ $scopes .= ' ' . $scope;
+ }
+ }
+ parent::__construct($issuer->get('clientid'), $issuer->get('clientsecret'), $returnurl, $scopes);
+ }
+
+ public static function create(issuer $issuer, moodle_url $returnurl, $scopesrequired, $system = false) {
+ if ($issuer->get('behaviour') == issuer::BEHAVIOUR_OPENID_CONNECT) {
+ return new client_openid_connect($issuer, $returnurl, $scopesrequired, $system);
+ } else if ($issuer->get('behaviour') == issuer::BEHAVIOUR_OAUTH2) {
+ return new client_oauth2($issuer, $returnurl, $scopesrequired, $system);
+ } else if ($issuer->get('behaviour') == issuer::BEHAVIOUR_MICROSOFT) {
+ return new client_microsoft($issuer, $returnurl, $scopesrequired, $system);
+ }
+ }
+
+ /**
+ * Returns the auth url for OAuth 2.0 request
+ * @return string the auth url
+ */
+ protected function auth_url() {
+ return $this->issuer->get_endpoint_url('authorization');
+ }
+
+ public function get_issuer() {
+ return $this->issuer;
+ }
+
+ public function get_additional_login_parameters() {
+ if ($this->issuer->get('behaviour') == issuer::BEHAVIOUR_OPENID_CONNECT) {
+ return ['access_type' => 'offline', 'prompt' => 'consent'];
+ }
+ return [];
+ }
+
+ protected function get_login_scopes() {
+ return 'openid profile email';
+ }
+
+ /**
+ * Returns the token url for OAuth 2.0 request
+ * @return string the auth url
+ */
+ protected function token_url() {
+ return $this->issuer->get_endpoint_url('token');
+ }
+
+ protected function get_tokenname() {
+ $name = static::class;
+ if ($this->system) {
+ $name .= '-system';
+ }
+ return $name;
+ }
+
+}
diff --git a/lib/classes/oauth2/client_microsoft.php b/lib/classes/oauth2/client_microsoft.php
new file mode 100644
index 00000000000..3d0bcd617e7
--- /dev/null
+++ b/lib/classes/oauth2/client_microsoft.php
@@ -0,0 +1,54 @@
+.
+
+/**
+ * Configurable oauth2 client class.
+ *
+ * @package core\oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+use moodle_url;
+
+/**
+ * We have to call directly to the graph APIs because the Microsoft Open ID Connect API is
+ * lame.
+ *
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class client_microsoft extends client {
+
+ public function get_additional_login_parameters() {
+ return ['prompt' => 'consent'];
+ }
+
+ public function get_login_scopes() {
+ return 'openid profile email user.read';
+ }
+
+ public function get_userinfo() {
+ $me = $client->get('https://graph.microsoft.com/v1.0/me/');
+ var_dump($me);
+
+
+ }
+
+}
diff --git a/lib/classes/oauth2/client_oauth2.php b/lib/classes/oauth2/client_oauth2.php
new file mode 100644
index 00000000000..96fb804abd6
--- /dev/null
+++ b/lib/classes/oauth2/client_oauth2.php
@@ -0,0 +1,43 @@
+.
+
+/**
+ * Configurable oauth2 client class.
+ *
+ * @package core\oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+use moodle_url;
+
+/**
+ * Configurable oauth2 client class where the urls come from DB.
+ *
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class client_oauth2 extends client {
+
+ public function get_additional_login_parameters() {
+ return ['access_type' => 'offline', 'prompt' => 'consent'];
+ }
+
+
+}
diff --git a/lib/classes/oauth2/client_openid_connect.php b/lib/classes/oauth2/client_openid_connect.php
new file mode 100644
index 00000000000..4ff9837f0cb
--- /dev/null
+++ b/lib/classes/oauth2/client_openid_connect.php
@@ -0,0 +1,98 @@
+.
+
+/**
+ * Configurable oauth2 client class.
+ *
+ * @package core\oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+use moodle_url;
+use stdClass;
+use Exception;
+
+/**
+ * Configurable oauth2 client class where the urls come from DB.
+ *
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class client_openid_connect extends client {
+
+ /**
+ * Returns a mapping of openid properties to moodle properties.
+ *
+ * @return array
+ */
+ private function get_mapping() {
+ return [
+ 'given_name' => 'firstname',
+ 'middle_name' => 'middlename',
+ 'family_name' => 'lastname',
+ 'email' => 'email',
+ 'username' => 'username',
+ 'website' => 'url',
+ 'nickname' => 'alternatename',
+ 'picture' => 'picture',
+ 'address' => 'address',
+ 'phone' => 'phone',
+ 'locale' => 'lang'
+ ];
+ }
+
+ public function get_additional_login_parameters() {
+ if ($this->system) {
+ return ['access_type' => 'offline', 'prompt' => 'consent'];
+ }
+ return [];
+ }
+
+ public function get_userinfo() {
+ $url = $this->get_issuer()->get_endpoint_url('userinfo');
+ $response = $this->get($url);
+ if (!$response) {
+ return false;
+ }
+ $userinfo = new stdClass();
+ try {
+ $userinfo = json_decode($response);
+ } catch (Exception $e) {
+ return false;
+ }
+ if (!empty($userinfo->preferred_username)) {
+ $userinfo->username = $userinfo->preferred_username;
+ } else {
+ $userinfo->username = $userinfo->sub;
+ }
+
+ $map = $this->get_mapping();
+
+ $user = new stdClass();
+ foreach ($map as $openidproperty => $moodleproperty) {
+ if (!empty($userinfo->$openidproperty)) {
+ $user->$moodleproperty = $userinfo->$openidproperty;
+ }
+ }
+
+ return (array)$user;
+ }
+
+}
diff --git a/lib/classes/oauth2/endpoint.php b/lib/classes/oauth2/endpoint.php
new file mode 100644
index 00000000000..fe3a0122e90
--- /dev/null
+++ b/lib/classes/oauth2/endpoint.php
@@ -0,0 +1,58 @@
+.
+
+/**
+ * Class for loading/storing oauth2 endpoints from the DB.
+ *
+ * @package core_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\persistent;
+
+/**
+ * Class for loading/storing oauth2 endpoints from the DB
+ *
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class endpoint extends persistent {
+
+ const TABLE = 'oauth2_endpoint';
+
+ /**
+ * Return the definition of the properties of this model.
+ *
+ * @return array
+ */
+ protected static function define_properties() {
+ return array(
+ 'issuerid' => array(
+ 'type' => PARAM_INT
+ ),
+ 'name' => array(
+ 'type' => PARAM_RAW,
+ ),
+ 'url' => array(
+ 'type' => PARAM_URL,
+ )
+ );
+ }
+}
diff --git a/lib/classes/oauth2/issuer.php b/lib/classes/oauth2/issuer.php
new file mode 100644
index 00000000000..71eec0994bb
--- /dev/null
+++ b/lib/classes/oauth2/issuer.php
@@ -0,0 +1,131 @@
+.
+
+/**
+ * Class for loading/storing issuers from the DB.
+ *
+ * @package core_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\persistent;
+
+/**
+ * Class for loading/storing issuer from the DB
+ *
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class issuer extends persistent {
+
+ const TABLE = 'oauth2_issuer';
+
+ const BEHAVIOUR_OPENID_CONNECT = 'Open ID Connect';
+ const BEHAVIOUR_MICROSOFT = 'Microsoft OAuth 2.0';
+ const BEHAVIOUR_OAUTH2 = 'OAuth 2.0';
+
+ /**
+ * Return the definition of the properties of this model.
+ *
+ * @return array
+ */
+ protected static function define_properties() {
+ return array(
+ 'name' => array(
+ 'type' => PARAM_TEXT
+ ),
+ 'image' => array(
+ 'type' => PARAM_URL,
+ 'null' => NULL_ALLOWED,
+ 'default' => null
+ ),
+ 'clientid' => array(
+ 'type' => PARAM_RAW
+ ),
+ 'clientsecret' => array(
+ 'type' => PARAM_RAW
+ ),
+ 'behaviour' => array(
+ 'type' => PARAM_NOTAGS,
+ 'choices' => array(self::BEHAVIOUR_OPENID_CONNECT, self::BEHAVIOUR_MICROSOFT, self::BEHAVIOUR_OAUTH2),
+ 'default' => self::BEHAVIOUR_OPENID_CONNECT
+ ),
+ 'baseurl' => array(
+ 'type' => PARAM_URL
+ ),
+ 'showonloginpage' => array(
+ 'type' => PARAM_BOOL,
+ 'default' => false
+ ),
+ 'scopessupported' => array(
+ 'type' => PARAM_RAW,
+ 'null' => NULL_ALLOWED,
+ 'default' => null
+ ),
+ 'sortorder' => array(
+ 'type' => PARAM_INT,
+ 'default' => 0,
+ )
+ );
+ }
+
+ public function get_endpoint_url($type) {
+ $endpoint = endpoint::get_record([
+ 'issuerid' => $this->get('id'),
+ 'name' => $type . '_endpoint'
+ ]);
+
+ if ($endpoint) {
+ return $endpoint->get('url');
+ }
+ return false;
+ }
+ public function is_authentication_supported() {
+ $supportedloginbehaviours = [
+ self::BEHAVIOUR_OPENID_CONNECT,
+ self::BEHAVIOUR_MICROSOFT,
+ ];
+ return in_array($this->get('behaviour'), $supportedloginbehaviours);
+ }
+
+ public function is_system_account_setup_supported() {
+ $supportedsystemaccountbehaviours = [
+ self::BEHAVIOUR_OPENID_CONNECT,
+ self::BEHAVIOUR_MICROSOFT,
+ ];
+ return in_array($this->get('behaviour'), $supportedsystemaccountbehaviours);
+ }
+
+ public function get_behaviour_list() {
+ return [
+ self::BEHAVIOUR_OPENID_CONNECT => self::BEHAVIOUR_OPENID_CONNECT,
+ self::BEHAVIOUR_OAUTH2 => self::BEHAVIOUR_OAUTH2,
+ self::BEHAVIOUR_MICROSOFT => self::BEHAVIOUR_MICROSOFT
+ ];
+ }
+
+ public function is_system_account_connected() {
+ $sys = system_account::get_record(['issuerid' => $this->get('id')]);
+ if (!empty($sys) and !empty($sys->get('refreshtoken'))) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/lib/classes/oauth2/system_account.php b/lib/classes/oauth2/system_account.php
new file mode 100644
index 00000000000..ee682a7978f
--- /dev/null
+++ b/lib/classes/oauth2/system_account.php
@@ -0,0 +1,60 @@
+.
+
+/**
+ * When using OAuth sometimes it makes sense to authenticate as a system user, and not the current user.
+ * In this case we use a refresh token to get an access token and the system admin must manually authorize the
+ * system account.
+ *
+ * @package core_oauth2
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\persistent;
+
+/**
+ * Class for loading/storing oauth2 refresh tokens from the DB.
+ *
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class system_account extends persistent {
+
+ const TABLE = 'oauth2_system_account';
+
+ /**
+ * Return the definition of the properties of this model.
+ *
+ * @return array
+ */
+ protected static function define_properties() {
+ return array(
+ 'issuerid' => array(
+ 'type' => PARAM_INT
+ ),
+ 'refreshtoken' => array(
+ 'type' => PARAM_RAW,
+ ),
+ 'grantedscopes' => array(
+ 'type' => PARAM_RAW,
+ )
+ );
+ }
+}
diff --git a/lib/db/install.php b/lib/db/install.php
index f291828a1fa..8a45a6aa4ff 100644
--- a/lib/db/install.php
+++ b/lib/db/install.php
@@ -321,4 +321,6 @@ function xmldb_main_install() {
require_once($CFG->libdir . '/db/upgradelib.php');
make_default_scale();
make_competence_scale();
+
+ \core\oauth2\api::install_default_providers();
}
diff --git a/lib/db/install.xml b/lib/db/install.xml
old mode 100644
new mode 100755
index 2e52477544f..0233dedcf37
--- a/lib/db/install.xml
+++ b/lib/db/install.xml
@@ -1,5 +1,5 @@
-
@@ -3463,5 +3463,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php
index 69ef988fa08..189c3338734 100644
--- a/lib/db/upgrade.php
+++ b/lib/db/upgrade.php
@@ -2611,5 +2611,98 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2017031400.00);
}
+ if ($oldversion < 2017032400.21) {
+
+ // Define table oauth2_issuer to be created.
+ $table = new xmldb_table('oauth2_issuer');
+
+ // Adding fields to table oauth2_issuer.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('image', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+ $table->add_field('baseurl', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+ $table->add_field('clientid', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+ $table->add_field('clientsecret', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+ $table->add_field('behaviour', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, 'none');
+ $table->add_field('scopessupported', XMLDB_TYPE_TEXT, null, null, null, null, null);
+ $table->add_field('showonloginpage', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '1');
+ $table->add_field('sortorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+ // Adding keys to table oauth2_issuer.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+ // Conditionally launch create table for oauth2_issuer.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2017032400.21);
+ }
+
+ if ($oldversion < 2017032400.22) {
+
+ // Define table oauth2_endpoint to be created.
+ $table = new xmldb_table('oauth2_endpoint');
+
+ // Adding fields to table oauth2_endpoint.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('url', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+ $table->add_field('issuerid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+ // Adding keys to table oauth2_endpoint.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $table->add_key('issuer_id_key', XMLDB_KEY_FOREIGN, array('issuerid'), 'oauth2_issuer', array('id'));
+
+ // Conditionally launch create table for oauth2_endpoint.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2017032400.22);
+ }
+
+ if ($oldversion < 2017032400.23) {
+
+ // Define table oauth2_system_account to be created.
+ $table = new xmldb_table('oauth2_system_account');
+
+ // Adding fields to table oauth2_system_account.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('issuerid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('refreshtoken', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+ $table->add_field('grantedscopes', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+
+ // Adding keys to table oauth2_system_account.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $table->add_key('issueridkey', XMLDB_KEY_FOREIGN_UNIQUE, array('issuerid'), 'oauth2_issuer', array('id'));
+
+ // Conditionally launch create table for oauth2_system_account.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2017032400.23);
+ }
+
+ if ($oldversion < 2017033100.01) {
+
+ \core\oauth2\api::install_default_issuers();
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2017033100.01);
+ }
+
return true;
}
diff --git a/lib/filelib.php b/lib/filelib.php
index 9cca2b9ad70..bdc90bd114b 100644
--- a/lib/filelib.php
+++ b/lib/filelib.php
@@ -2766,9 +2766,9 @@ class curl {
// All settings of this class should be init here.
$this->resetopt();
- if (!empty($settings['debug'])) {
+ //if (!empty($settings['debug'])) {
$this->debug = true;
- }
+ //}
if (!empty($settings['cookie'])) {
if($settings['cookie'] === true) {
$this->cookie = $CFG->dataroot.'/curl_cookie.txt';
@@ -2949,6 +2949,7 @@ class curl {
* Set HTTP Request Header
*
* @param array $header
+ * @param bool $replace If true, will remove any existing headers before appending the new one.
*/
public function setHeader($header) {
if (is_array($header)) {
diff --git a/lib/oauthlib.php b/lib/oauthlib.php
index 7d83e82e68b..9efb6d100f6 100644
--- a/lib/oauthlib.php
+++ b/lib/oauthlib.php
@@ -393,6 +393,8 @@ abstract class oauth2_client extends curl {
private $scope = '';
/** var stdClass access token object */
private $accesstoken = null;
+ /** var stdClass refresh token string */
+ private $refreshtoken = '';
/**
* Returns the auth url for OAuth 2.0 request
@@ -463,6 +465,10 @@ abstract class oauth2_client extends curl {
return new moodle_url('/admin/oauth2callback.php');
}
+ public function get_additional_login_parameters() {
+ return [];
+ }
+
/**
* Returns the login link for this oauth request
*
@@ -471,15 +477,32 @@ abstract class oauth2_client extends curl {
public function get_login_url() {
$callbackurl = self::callback_url();
- $url = new moodle_url($this->auth_url(),
- array('client_id' => $this->clientid,
- 'response_type' => 'code',
- 'redirect_uri' => $callbackurl->out(false),
- 'state' => $this->returnurl->out_as_local_url(false),
- 'scope' => $this->scope,
- ));
+ $params = array_merge(
+ [
+ 'client_id' => $this->clientid,
+ 'response_type' => 'code',
+ 'redirect_uri' => $callbackurl->out(false),
+ 'state' => $this->returnurl->out_as_local_url(false),
+ 'scope' => $this->scope,
+ ],
+ $this->get_additional_login_parameters()
+ );
- return $url;
+ return new moodle_url($this->auth_url(), $params);
+ }
+
+ /**
+ * Given an array of name value pairs - build a valid HTTP POST application/x-www-form-urlencoded string.
+ *
+ * @param array $params Name / value pairs.
+ * @return string POST data.
+ */
+ public function build_post_data($params) {
+ $result = [];
+ foreach ($params as $name => $value) {
+ $result[] = str_replace('&', '%26', $name) . '=' . str_replace('&', '%26', $value);
+ }
+ return implode('&', $result);
}
/**
@@ -490,10 +513,10 @@ abstract class oauth2_client extends curl {
*/
public function upgrade_token($code) {
$callbackurl = self::callback_url();
- $params = array('client_id' => $this->clientid,
+ $params = array('code' => $code,
+ 'client_id' => $this->clientid,
'client_secret' => $this->clientsecret,
'grant_type' => 'authorization_code',
- 'code' => $code,
'redirect_uri' => $callbackurl->out(false),
);
@@ -501,7 +524,7 @@ abstract class oauth2_client extends curl {
if ($this->use_http_get()) {
$response = $this->get($this->token_url(), $params);
} else {
- $response = $this->post($this->token_url(), $params);
+ $response = $this->post($this->token_url(), $this->build_post_data($params));
}
if (!$this->info['http_code'] === 200) {
@@ -514,6 +537,10 @@ abstract class oauth2_client extends curl {
return false;
}
+ if (isset($r->refresh_token)) {
+ $this->refreshtoken = $r->refresh_token;
+ }
+
// Store the token an expiry time.
$accesstoken = new stdClass;
$accesstoken->token = $r->access_token;
@@ -552,7 +579,11 @@ abstract class oauth2_client extends curl {
}
}
- return parent::request($murl->out(false), $options);
+ $response = parent::request($murl->out(false), $options);
+
+ $this->resetHeader();
+
+ return $response;
}
/**
@@ -603,6 +634,15 @@ abstract class oauth2_client extends curl {
}
}
+ /**
+ * Get a refresh token!!!
+ *
+ * @return string
+ */
+ public function get_refresh_token() {
+ return $this->refreshtoken;
+ }
+
/**
* Retrieve a token stored.
*
diff --git a/lib/templates/login.mustache b/lib/templates/login.mustache
index e30c537624f..a0545c38573 100644
--- a/lib/templates/login.mustache
+++ b/lib/templates/login.mustache
@@ -148,9 +148,9 @@
{{#identityproviders}}
diff --git a/theme/boost/templates/core/login.mustache b/theme/boost/templates/core/login.mustache
index 3496224ef75..c253f728136 100644
--- a/theme/boost/templates/core/login.mustache
+++ b/theme/boost/templates/core/login.mustache
@@ -178,7 +178,12 @@
{{#identityproviders}}
{{/identityproviders}}
diff --git a/version.php b/version.php
index f8fd9032855..8a5f4a2be68 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
-$version = 2017033100.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2017033100.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.