diff --git a/admin/tool/oauth2/classes/form/endpoint.php b/admin/tool/oauth2/classes/form/endpoint.php new file mode 100644 index 00000000000..b4bd4be3cda --- /dev/null +++ b/admin/tool/oauth2/classes/form/endpoint.php @@ -0,0 +1,81 @@ +. + +/** + * This file contains the form add/update oauth2 endpoint. + * + * @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 endpoint extends persistent { + + protected static $persistentclass = 'core\\oauth2\\endpoint'; + + protected static $fieldstoremove = array('submitbutton', 'action'); + + /** + * Define the form - called by parent constructor + */ + public function definition() { + global $PAGE; + + $mform = $this->_form; + $endpoint = $this->get_persistent(); + + $mform->addElement('header', 'generalhdr', get_string('general')); + + // Name. + $mform->addElement('text', 'name', get_string('endpointname', 'tool_oauth2'), 'maxlength="255"'); + $mform->addRule('name', null, 'required', null, 'client'); + $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); + $mform->addHelpButton('name', 'endpointname', 'tool_oauth2'); + + // Url. + $mform->addElement('text', 'url', get_string('endpointurl', 'tool_oauth2'), 'maxlength="1024"'); + $mform->addRule('url', null, 'required', null, 'client'); + $mform->addRule('url', get_string('maximumchars', '', 1024), 'maxlength', 1024, 'client'); + $mform->addHelpButton('url', 'endpointurl', 'tool_oauth2'); + + $mform->addElement('hidden', 'action', 'edit'); + $mform->setType('action', PARAM_RAW); + + $mform->addElement('hidden', 'issuerid', $endpoint->get('issuerid')); + $mform->setType('issuerid', PARAM_INT); + $mform->setConstant('issuerid', $this->_customdata['issuerid']); + + $mform->addElement('hidden', 'id', $endpoint->get('id')); + $mform->setType('id', PARAM_INT); + + $this->add_action_buttons(true, get_string('savechanges', 'tool_oauth2')); + } + +} + diff --git a/admin/tool/oauth2/classes/form/issuer.php b/admin/tool/oauth2/classes/form/issuer.php index ce03af63e4a..d16755c32bf 100644 --- a/admin/tool/oauth2/classes/form/issuer.php +++ b/admin/tool/oauth2/classes/form/issuer.php @@ -48,7 +48,7 @@ class issuer extends persistent { global $PAGE; $mform = $this->_form; - $provider = $this->get_persistent(); + $endpoint = $this->get_persistent(); $mform->addElement('header', 'generalhdr', get_string('general')); @@ -77,7 +77,7 @@ class issuer extends persistent { $mform->addHelpButton('baseurl', 'issuerbaseurl', 'tool_oauth2'); // Offline access type - $options = $provider->get_behaviour_list(); + $options = $endpoint->get_behaviour_list(); $mform->addElement('select', 'behaviour', get_string('issuerbehaviour', 'tool_oauth2'), $options); $mform->addHelpButton('behaviour', 'issuerbehaviour', 'tool_oauth2'); @@ -97,7 +97,7 @@ class issuer extends persistent { $mform->addElement('hidden', 'action', 'edit'); $mform->setType('action', PARAM_RAW); - $mform->addElement('hidden', 'id', $provider->get('id')); + $mform->addElement('hidden', 'id', $endpoint->get('id')); $mform->setType('id', PARAM_INT); $this->add_action_buttons(true, get_string('savechanges', 'tool_oauth2')); diff --git a/admin/tool/oauth2/classes/form/user_field_mapping.php b/admin/tool/oauth2/classes/form/user_field_mapping.php new file mode 100644 index 00000000000..e8fa760356e --- /dev/null +++ b/admin/tool/oauth2/classes/form/user_field_mapping.php @@ -0,0 +1,80 @@ +. + +/** + * This file contains the form add/update oauth2 user_field_mapping. + * + * @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 user_field_mapping extends persistent { + + protected static $persistentclass = 'core\\oauth2\\user_field_mapping'; + + protected static $fieldstoremove = array('submitbutton', 'action'); + + /** + * Define the form - called by parent constructor + */ + public function definition() { + global $PAGE; + + $mform = $this->_form; + $userfieldmapping = $this->get_persistent(); + + $mform->addElement('header', 'generalhdr', get_string('general')); + + // External. + $mform->addElement('text', 'externalfield', get_string('userfieldexternalfield', 'tool_oauth2'), 'maxlength="255"'); + $mform->addRule('externalfield', null, 'required', null, 'client'); + $mform->addRule('externalfield', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); + $mform->addHelpButton('externalfield', 'userfieldexternalfield', 'tool_oauth2'); + + // Internal. + $choices = $userfieldmapping->get_internalfield_list(); + $mform->addElement('select', 'internalfield', get_string('userfieldinternalfield', 'tool_oauth2'), $choices); + $mform->addHelpButton('internalfield', 'userfieldinternalfield', 'tool_oauth2'); + + $mform->addElement('hidden', 'action', 'edit'); + $mform->setType('action', PARAM_RAW); + + $mform->addElement('hidden', 'issuerid', $userfieldmapping->get('issuerid')); + $mform->setConstant('issuerid', $this->_customdata['issuerid']); + $mform->setType('issuerid', PARAM_INT); + + $mform->addElement('hidden', 'id', $userfieldmapping->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 index f26ca777f91..0d4514d2bcc 100644 --- a/admin/tool/oauth2/classes/output/renderer.php +++ b/admin/tool/oauth2/classes/output/renderer.php @@ -79,7 +79,7 @@ class renderer extends plugin_renderer_base { $name = $issuer->get('name'); $image = $issuer->get('image'); if ($image) { - $name = ' ' . $name; + $name = ' ' . s($name); } $namecell = new html_table_cell($name); $namecell->header = true; @@ -104,7 +104,11 @@ class renderer extends plugin_renderer_base { 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'); + if ($issuer->get('behaviour') == $issuer::BEHAVIOUR_OPENID_CONNECT) { + $discovered = $OUTPUT->pix_icon('no', get_string('notdiscovered', 'tool_oauth2'), 'tool_oauth2'); + } else { + $discovered = '-'; + } } $discoverystatuscell = new html_table_cell($discovered); @@ -125,22 +129,37 @@ class renderer extends plugin_renderer_base { $systemauthstatuscell = new html_table_cell($systemauth); - // Action links. $links = ''; + // Action 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; + + // Endpoints. + $editendpointsurl = new moodle_url('/admin/tool/oauth2/endpoints.php', ['issuerid' => $issuer->get('id')]); + $str = get_string('editendpoints', 'tool_oauth2'); + $editendpointlink = html_writer::link($editendpointsurl, $OUTPUT->pix_icon('t/viewdetails', $str)); + $links .= ' ' . $editendpointlink; + + // User field mapping. + $edituserfieldmappingsurl = new moodle_url('/admin/tool/oauth2/userfieldmappings.php', ['issuerid' => $issuer->get('id')]); + $str = get_string('edituserfieldmappings', 'tool_oauth2'); + $edituserfieldmappinglink = html_writer::link($edituserfieldmappingsurl, $OUTPUT->pix_icon('t/user', $str)); + $links .= ' ' . $edituserfieldmappinglink; + + // Delete. $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) { + // Move down. $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) { + // Move up. $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'))); @@ -164,4 +183,119 @@ class renderer extends plugin_renderer_base { $table->data = $data; return html_writer::table($table); } + + /** + * This function will render one beautiful table with all the endpoints. + * + * @param \core\oauth2\endpoint[] $endpoints - list of all endpoints. + * @return string HTML to output. + */ + public function endpoints_table($endpoints, $issuerid) { + global $CFG, $OUTPUT; + + $table = new html_table(); + $table->head = [ + get_string('name'), + get_string('url'), + get_string('edit'), + ]; + $table->attributes['class'] = 'admintable generaltable'; + $data = []; + + $index = 0; + + foreach ($endpoints as $endpoint) { + // Name. + $name = $endpoint->get('name'); + $namecell = new html_table_cell(s($name)); + $namecell->header = true; + + // Url + $url = $endpoint->get('url'); + $urlcell = new html_table_cell(s($url)); + + $links = ''; + // Action links. + $editparams = ['issuerid' => $issuerid, 'endpointid' => $endpoint->get('id'), 'action' => 'edit']; + $editurl = new moodle_url('/admin/tool/oauth2/endpoints.php', $editparams); + $editlink = html_writer::link($editurl, $OUTPUT->pix_icon('t/edit', get_string('edit'))); + $links .= ' ' . $editlink; + + // Delete. + $deleteparams = ['issuerid' => $issuerid, 'endpointid' => $endpoint->get('id'), 'action' => 'delete']; + $deleteurl = new moodle_url('/admin/tool/oauth2/endpoints.php', $deleteparams); + $deletelink = html_writer::link($deleteurl, $OUTPUT->pix_icon('t/delete', get_string('delete'))); + $links .= ' ' . $deletelink; + + $editcell = new html_table_cell($links); + + $row = new html_table_row([ + $namecell, + $urlcell, + $editcell, + ]); + + $data[] = $row; + $index++; + } + $table->data = $data; + return html_writer::table($table); + } + + /** + * This function will render one beautiful table with all the user_field_mappings. + * + * @param \core\oauth2\user_field_mapping[] $userfieldmappings - list of all user_field_mappings. + * @return string HTML to output. + */ + public function user_field_mappings_table($userfieldmappings, $issuerid) { + global $CFG, $OUTPUT; + + $table = new html_table(); + $table->head = [ + get_string('userfieldexternalfield', 'tool_oauth2'), + get_string('userfieldinternalfield', 'tool_oauth2'), + get_string('edit'), + ]; + $table->attributes['class'] = 'admintable generaltable'; + $data = []; + + $index = 0; + + foreach ($userfieldmappings as $userfieldmapping) { + // External field + $externalfield = $userfieldmapping->get('externalfield'); + $externalfieldcell = new html_table_cell(s($externalfield)); + + // Internal field + $internalfield = $userfieldmapping->get('internalfield'); + $internalfieldcell = new html_table_cell(s($internalfield)); + + $links = ''; + // Action links. + $editparams = ['issuerid' => $issuerid, 'userfieldmappingid' => $userfieldmapping->get('id'), 'action' => 'edit']; + $editurl = new moodle_url('/admin/tool/oauth2/userfieldmappings.php', $editparams); + $editlink = html_writer::link($editurl, $OUTPUT->pix_icon('t/edit', get_string('edit'))); + $links .= ' ' . $editlink; + + // Delete. + $deleteparams = ['issuerid' => $issuerid, 'userfieldmappingid' => $userfieldmapping->get('id'), 'action' => 'delete']; + $deleteurl = new moodle_url('/admin/tool/oauth2/userfieldmappings.php', $deleteparams); + $deletelink = html_writer::link($deleteurl, $OUTPUT->pix_icon('t/delete', get_string('delete'))); + $links .= ' ' . $deletelink; + + $editcell = new html_table_cell($links); + + $row = new html_table_row([ + $externalfieldcell, + $internalfieldcell, + $editcell, + ]); + + $data[] = $row; + $index++; + } + $table->data = $data; + return html_writer::table($table); + } } diff --git a/admin/tool/oauth2/endpoints.php b/admin/tool/oauth2/endpoints.php new file mode 100644 index 00000000000..cf3a3fe3b0b --- /dev/null +++ b/admin/tool/oauth2/endpoints.php @@ -0,0 +1,128 @@ +. + +/** + * OAuth 2 Endpoing 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/endpoints.php', ['issuerid' => required_param('issuerid', PARAM_INT)]); +$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); +$issuerid = required_param('issuerid', PARAM_INT); +$endpointid = optional_param('endpointid', '', PARAM_INT); +$endpoint = null; +$mform = null; + +$issuer = \core\oauth2\api::get_issuer($issuerid); +if (!$issuer) { + print_error('invaliddata'); +} +$PAGE->navbar->override_active_url(new moodle_url('/admin/tool/oauth2/issuers.php'), true); + +if (!empty($endpointid)) { + $endpoint = \core\oauth2\api::get_endpoint($endpointid); +} + +if ($action == 'edit') { + if ($endpoint) { + $strparams = [ 'issuer' => s($issuer->get('name')), 'endpoint' => s($endpoint->get('name')) ]; + $PAGE->navbar->add(get_string('editendpoint', 'tool_oauth2', $strparams)); + } else { + $PAGE->navbar->add(get_string('createnewendpoint', 'tool_oauth2', s($issuer->get('name')))); + } + + $mform = new \tool_oauth2\form\endpoint(null, ['persistent' => $endpoint, 'issuerid' => $issuerid]); +} + +if ($mform && $mform->is_cancelled()) { + redirect(new moodle_url('/admin/tool/oauth2/endpoints.php', ['issuerid' => $issuerid])); +} else if ($action == 'edit') { + + if ($data = $mform->get_data()) { + + try { + if (!empty($data->id)) { + core\oauth2\api::update_endpoint($data); + } else { + core\oauth2\api::create_endpoint($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 ($endpoint) { + $strparams = [ 'issuer' => s($issuer->get('name')), 'endpoint' => s($endpoint->get('name')) ]; + echo $OUTPUT->heading(get_string('editendpoint', 'tool_oauth2', $strparams)); + } else { + echo $OUTPUT->heading(get_string('createnewendpoint', 'tool_oauth2', s($issuer->get('name')))); + } + $mform->display(); + echo $OUTPUT->footer(); + } + +} else if ($action == 'delete') { + + if (!optional_param('confirm', false, PARAM_BOOL)) { + $continueparams = [ + 'action' => 'delete', + 'issuerid' => $issuerid, + 'endpointid' => $endpointid, + 'sesskey' => sesskey(), + 'confirm' => true + ]; + $continueurl = new moodle_url('/admin/tool/oauth2/endpoints.php', $continueparams); + $cancelurl = new moodle_url('/admin/tool/oauth2/endpoints.php'); + echo $OUTPUT->header(); + $strparams = [ 'issuer' => s($issuer->get('name')), 'endpoint' => s($endpoint->get('name')) ]; + echo $OUTPUT->confirm(get_string('deleteendpointconfirm', 'tool_oauth2', $strparams), $continueurl, $cancelurl); + echo $OUTPUT->footer(); + } else { + require_sesskey(); + core\oauth2\api::delete_endpoint($endpointid); + redirect($PAGE->url, get_string('endpointdeleted', 'tool_oauth2'), null, \core\output\notification::NOTIFY_SUCCESS); + } + +} else { + echo $OUTPUT->header(); + echo $OUTPUT->heading(get_string('endpointsforissuer', 'tool_oauth2', s($issuer->get('name')))); + $endpoints = core\oauth2\api::get_endpoints($issuer); + echo $renderer->endpoints_table($endpoints, $issuerid); + + $addurl = new moodle_url('/admin/tool/oauth2/endpoints.php', ['action' => 'edit', 'issuerid' => $issuerid]); + echo $renderer->single_button($addurl, get_string('createnewendpoint', 'tool_oauth2', s($issuer->get('name')))); + echo $OUTPUT->footer(); +} diff --git a/admin/tool/oauth2/issuers.php b/admin/tool/oauth2/issuers.php index d80f91d1e2a..af6c762bcf0 100644 --- a/admin/tool/oauth2/issuers.php +++ b/admin/tool/oauth2/issuers.php @@ -40,12 +40,12 @@ 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); +$issuerid = optional_param('id', '', PARAM_RAW); $issuer = null; $mform = null; -if ($idpid) { - $issuer = \core\oauth2\api::get_issuer($idpid); +if ($issuerid) { + $issuer = \core\oauth2\api::get_issuer($issuerid); if (!$issuer) { print_error('invaliddata'); } @@ -53,7 +53,7 @@ if ($idpid) { if ($action == 'edit') { if ($issuer) { - $PAGE->navbar->add(get_string('editissuer', 'tool_oauth2', $issuer->get('name'))); + $PAGE->navbar->add(get_string('editissuer', 'tool_oauth2', s($issuer->get('name')))); } else { $PAGE->navbar->add(get_string('createnewissuer', 'tool_oauth2')); } @@ -80,7 +80,7 @@ if ($mform && $mform->is_cancelled()) { } else { echo $OUTPUT->header(); if ($issuer) { - echo $OUTPUT->heading(get_string('editissuer', 'tool_oauth2', $issuer->get('name'))); + echo $OUTPUT->heading(get_string('editissuer', 'tool_oauth2', s($issuer->get('name')))); } else { echo $OUTPUT->heading(get_string('createnewissuer', 'tool_oauth2')); } @@ -91,30 +91,30 @@ if ($mform && $mform->is_cancelled()) { } else if ($action == 'delete') { if (!optional_param('confirm', false, PARAM_BOOL)) { - $continueparams = ['action' => 'delete', 'id' => $idpid, 'sesskey' => sesskey(), 'confirm' => true]; + $continueparams = ['action' => 'delete', 'id' => $issuerid, '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->confirm(get_string('deleteconfirm', 'tool_oauth2', s($issuer->get('name'))), $continueurl, $cancelurl); echo $OUTPUT->footer(); } else { require_sesskey(); - core\oauth2\api::delete_issuer($idpid); + core\oauth2\api::delete_issuer($issuerid); 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]; + $continueparams = ['action' => 'auth', 'id' => $issuerid, '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->confirm(get_string('authconfirm', 'tool_oauth2', s($issuer->get('name'))), $continueurl, $cancelurl); echo $OUTPUT->footer(); } else { require_sesskey(); - $params = ['sesskey' => sesskey(), 'id' => $idpid, 'action' => 'auth', 'confirm' => true, 'response' => true]; + $params = ['sesskey' => sesskey(), 'id' => $issuerid, '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 { @@ -123,12 +123,12 @@ if ($mform && $mform->is_cancelled()) { } } else if ($action == 'moveup') { require_sesskey(); - core\oauth2\api::move_up_issuer($idpid); + core\oauth2\api::move_up_issuer($issuerid); redirect($PAGE->url); } else if ($action == 'movedown') { require_sesskey(); - core\oauth2\api::move_down_issuer($idpid); + core\oauth2\api::move_down_issuer($issuerid); redirect($PAGE->url); } else { diff --git a/admin/tool/oauth2/lang/en/tool_oauth2.php b/admin/tool/oauth2/lang/en/tool_oauth2.php index 73bce13bed4..46dc3d8e47b 100644 --- a/admin/tool/oauth2/lang/en/tool_oauth2.php +++ b/admin/tool/oauth2/lang/en/tool_oauth2.php @@ -22,8 +22,17 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['pluginname'] = 'Open ID Connect configuration'; +$string['pluginname'] = 'OAuth 2 Services'; $string['editissuer'] = 'Edit identity issuer: {$a}'; +$string['editendpoint'] = 'Edit endpoint: {$a->endpoint} for issuer {$a->issuer}'; +$string['endpointsforissuer'] = 'Endpoints for issuer: {$a}'; +$string['edituserfieldmapping'] = 'Edit user field mapping for issuer {$a}'; +$string['userfieldmappingsforissuer'] = 'User field mappings for issuer: {$a}'; +$string['issuers'] = 'Issuers'; +$string['endpointname'] = 'Name'; +$string['endpointname_help'] = 'Key used to search for this endpoint. Must end with "_endpoint".'; +$string['endpointurl'] = 'Url'; +$string['endpointurl_help'] = 'URL for this endpoint. Must use https:// protocol.'; $string['issuername'] = 'Name'; $string['issuername_help'] = 'Name of the identity issuer. May be displayed on login page.'; $string['issuerimage'] = 'Logo URL'; @@ -47,6 +56,8 @@ $string['configuredstatus'] = 'Configured'; $string['discoverystatus'] = 'Discovery'; $string['systemauthstatus'] = 'System account connected'; $string['configured'] = 'Configured'; +$string['editendpoints'] = 'Configure endpoints'; +$string['edituserfieldmappings'] = 'Configure user field mappings'; $string['notconfigured'] = 'Not configured'; $string['discovered'] = 'Service discovery successful'; $string['notdiscovered'] = 'Service discovery not successful'; @@ -55,9 +66,19 @@ $string['notloginissuer'] = 'Do not allow login'; $string['systemaccountconnected'] = 'System account connected'; $string['systemaccountnotconnected'] = 'System account not connected'; $string['createnewissuer'] = 'Create new identity issuer'; +$string['createnewendpoint'] = 'Create new endpoint for issuer "{$a}"'; +$string['createnewuserfieldmapping'] = 'Create new user field mapping for issuer "{$a}"'; $string['deleteconfirm'] = 'Are you sure you want to delete the identity issuer "{$a}"? Any plugins relying on this issuer will stop working.'; +$string['deleteendpointconfirm'] = 'Are you sure you want to delete the endpoint "{$a->endpoint}" for issuer "{$a->issuer}"? Any plugins relying on this endpoint will stop working.'; +$string['deleteuserfieldmappingconfirm'] = 'Are you sure you want to delete the user field mapping for issuer "{$a}"?'; $string['issuerdeleted'] = 'Identity issuer deleted'; +$string['endpointdeleted'] = 'Endpoint deleted'; +$string['userfieldmappingdeleted'] = 'User field mapping 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'; +$string['userfieldexternalfield'] = 'External field name'; +$string['userfieldexternalfield_help'] = 'Name of the field provided by the external OAuth system.'; +$string['userfieldinternalfield'] = 'Internal field name'; +$string['userfieldinternalfield_help'] = 'Name of the Moodle user field that should be mapped from the external field.'; diff --git a/admin/tool/oauth2/userfieldmappings.php b/admin/tool/oauth2/userfieldmappings.php new file mode 100644 index 00000000000..1bf277d9069 --- /dev/null +++ b/admin/tool/oauth2/userfieldmappings.php @@ -0,0 +1,126 @@ +. + +/** + * OAuth 2 Endpoing 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/userfieldmappings.php', ['issuerid' => required_param('issuerid', PARAM_INT)]); +$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); +$issuerid = required_param('issuerid', PARAM_INT); +$userfieldmappingid = optional_param('userfieldmappingid', '', PARAM_INT); +$userfieldmapping = null; +$mform = null; + +$issuer = \core\oauth2\api::get_issuer($issuerid); +if (!$issuer) { + print_error('invaliddata'); +} +$PAGE->navbar->override_active_url(new moodle_url('/admin/tool/oauth2/issuers.php'), true); + +if (!empty($userfieldmappingid)) { + $userfieldmapping = \core\oauth2\api::get_user_field_mapping($userfieldmappingid); +} + +if ($action == 'edit') { + if ($userfieldmapping) { + $PAGE->navbar->add(get_string('edituserfieldmapping', 'tool_oauth2', s($issuer->get('name')))); + } else { + $PAGE->navbar->add(get_string('createnewuserfieldmapping', 'tool_oauth2', s($issuer->get('name')))); + } + + $mform = new \tool_oauth2\form\user_field_mapping(null, ['persistent' => $userfieldmapping, 'issuerid' => $issuerid]); +} + +if ($mform && $mform->is_cancelled()) { + redirect(new moodle_url('/admin/tool/oauth2/userfieldmappings.php', ['issuerid' => $issuerid])); +} else if ($action == 'edit') { + + if ($data = $mform->get_data()) { + + try { + if (!empty($data->id)) { + core\oauth2\api::update_user_field_mapping($data); + } else { + core\oauth2\api::create_user_field_mapping($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('edituserfieldmapping', 'tool_oauth2', s($issuer->get('name')))); + } else { + echo $OUTPUT->heading(get_string('createnewuserfieldmapping', 'tool_oauth2', s($issuer->get('name')))); + } + $mform->display(); + echo $OUTPUT->footer(); + } + +} else if ($action == 'delete') { + + if (!optional_param('confirm', false, PARAM_BOOL)) { + $continueparams = [ + 'action' => 'delete', + 'issuerid' => $issuerid, + 'userfieldmappingid' => $userfieldmappingid, + 'sesskey' => sesskey(), + 'confirm' => true + ]; + $continueurl = new moodle_url('/admin/tool/oauth2/userfieldmappings.php', $continueparams); + $cancelurl = new moodle_url('/admin/tool/oauth2/userfieldmappings.php'); + echo $OUTPUT->header(); + $str = get_string('deleteuserfieldmappingconfirm', 'tool_oauth2', s($issuer->get('name'))); + echo $OUTPUT->confirm($str, $continueurl, $cancelurl); + echo $OUTPUT->footer(); + } else { + require_sesskey(); + core\oauth2\api::delete_user_field_mapping($userfieldmappingid); + redirect($PAGE->url, get_string('userfieldmappingdeleted', 'tool_oauth2'), null, \core\output\notification::NOTIFY_SUCCESS); + } + +} else { + echo $OUTPUT->header(); + echo $OUTPUT->heading(get_string('userfieldmappingsforissuer', 'tool_oauth2', s($issuer->get('name')))); + $userfieldmappings = core\oauth2\api::get_user_field_mappings($issuer); + echo $renderer->user_field_mappings_table($userfieldmappings, $issuerid); + + $addurl = new moodle_url('/admin/tool/oauth2/userfieldmappings.php', ['action' => 'edit', 'issuerid' => $issuerid]); + echo $renderer->single_button($addurl, get_string('createnewuserfieldmapping', 'tool_oauth2', s($issuer->get('name')))); + echo $OUTPUT->footer(); +} diff --git a/auth/oauth2/classes/auth.php b/auth/oauth2/classes/auth.php index 4bf2cc9fb68..545a162f2dd 100644 --- a/auth/oauth2/classes/auth.php +++ b/auth/oauth2/classes/auth.php @@ -318,6 +318,11 @@ class auth extends \auth_plugin_base { $SESSION->loginerrormsg = $errormsg; redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php')); } + if (empty($userinfo['username'])) { + $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'])); diff --git a/lib/classes/oauth2/api.php b/lib/classes/oauth2/api.php index 00230d1620e..5fc61507344 100644 --- a/lib/classes/oauth2/api.php +++ b/lib/classes/oauth2/api.php @@ -68,6 +68,7 @@ class api { $endpoint = new endpoint(0, $record); $endpoint->create(); + // Microsoft is a custom setup. $record = (object) [ 'name' => 'Microsoft', 'image' => 'https://www.microsoft.com/favicon.ico', @@ -81,29 +82,43 @@ class api { $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' + $endpoints = [ + 'authorization_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + 'token_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + 'userinfo_endpoint' => 'https://graph.microsoft.com/v1.0/me/', + 'userpicture_endpoint' => 'https://graph.microsoft.com/v1.0/me/photo/$value', ]; - $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(); + foreach ($endpoints as $name => $url) { + $record = (object) [ + 'issuerid' => $issuer->get('id'), + 'name' => $name, + 'url' => $url + ]; + $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' + // Create the field mappings. + $mapping = [ + 'givenName' => 'firstname', + 'surname' => 'lastname', + 'mail' => 'email', + 'userPrincipalName' => 'username', + 'displayName' => 'alternatename', + 'officeLocation' => 'address', + 'mobilePhone' => 'phone', + 'preferredLanguage' => 'lang' ]; - $endpoint = new endpoint(0, $record); - $endpoint->create(); + foreach ($mapping as $external => $internal) { + $record = (object) [ + 'issuerid' => $issuer->get('id'), + 'externalfield' => $external, + 'internalfield' => $internal + ]; + $userfieldmapping = new user_field_mapping(0, $record); + $userfieldmapping->create(); + } return issuer::count_records(); } @@ -115,6 +130,14 @@ class api { return new issuer($id); } + public static function get_endpoint($id) { + return new endpoint($id); + } + + public static function get_user_field_mapping($id) { + return new user_field_mapping($id); + } + public static function get_system_account(issuer $issuer) { return system_account::get_record(['issuerid' => $issuer->get('id')]); } @@ -132,11 +155,13 @@ class api { } public static function get_endpoints(issuer $issuer) { - require_capability('moodle/site:config', context_system::instance()); - return endpoint::get_records(['issuerid' => $issuer->get('id')]); } + public static function get_user_field_mappings(issuer $issuer) { + return user_field_mapping::get_records(['issuerid' => $issuer->get('id')]); + } + protected static function guess_image($issuer) { if (empty($issuer->get('image'))) { $baseurl = parse_url($issuer->get('discoveryurl')); @@ -202,6 +227,32 @@ class api { } } + // We got to here - must be a decent OpenID connect service. Add the default user field mapping list. + + // Create the field mappings. + $mapping = [ + 'given_name' => 'firstname', + 'middle_name' => 'middlename', + 'family_name' => 'lastname', + 'email' => 'email', + 'sub' => 'username', + 'website' => 'url', + 'nickname' => 'alternatename', + 'picture' => 'picture', + 'address' => 'address', + 'phone' => 'phone', + 'locale' => 'lang' + ]; + foreach ($mapping as $external => $internal) { + $record = (object) [ + 'issuerid' => $issuer->get('id'), + 'externalfield' => $external, + 'internalfield' => $internal + ]; + $userfieldmapping = new user_field_mapping(0, $record); + $userfieldmapping->create(); + } + return endpoint::count_records(['issuerid' => $issuer->get('id')]); } @@ -215,6 +266,7 @@ class api { // Perform service discovery. self::discover_endpoints($issuer); self::guess_image($issuer); + return $issuer; } public static function create_issuer($data) { @@ -227,6 +279,45 @@ class api { // Perform service discovery. self::discover_endpoints($issuer); self::guess_image($issuer); + return $issuer; + } + + public static function update_endpoint($data) { + require_capability('moodle/site:config', context_system::instance()); + $endpoint = new endpoint(0, $data); + + // Will throw exceptions on validation failures. + $endpoint->update(); + + return $endpoint; + } + + public static function create_endpoint($data) { + require_capability('moodle/site:config', context_system::instance()); + $endpoint = new endpoint(0, $data); + + // Will throw exceptions on validation failures. + $endpoint->create(); + return $endpoint; + } + + public static function update_user_field_mapping($data) { + require_capability('moodle/site:config', context_system::instance()); + $userfieldmapping = new user_field_mapping(0, $data); + + // Will throw exceptions on validation failures. + $userfieldmapping->update(); + + return $userfieldmapping; + } + + public static function create_user_field_mapping($data) { + require_capability('moodle/site:config', context_system::instance()); + $userfieldmapping = new user_field_mapping(0, $data); + + // Will throw exceptions on validation failures. + $userfieldmapping->create(); + return $userfieldmapping; } /** @@ -307,7 +398,23 @@ class api { } // Will throw exceptions on validation failures. - $issuer->delete(); + return $issuer->delete(); + } + + public static function delete_endpoint($id) { + require_capability('moodle/site:config', context_system::instance()); + $endpoint = new endpoint($id); + + // Will throw exceptions on validation failures. + return $endpoint->delete(); + } + + public static function delete_user_field_mapping($id) { + require_capability('moodle/site:config', context_system::instance()); + $userfieldmapping = new user_field_mapping($id); + + // Will throw exceptions on validation failures. + return $userfieldmapping->delete(); } public static function connect_system_account($issuer, $returnurl) { @@ -324,6 +431,10 @@ class api { $client->log_out(); } + if (optional_param('error', '', PARAM_RAW)) { + return false; + } + if (!$client->is_logged_in()) { redirect($client->get_login_url()); } diff --git a/lib/classes/oauth2/client.php b/lib/classes/oauth2/client.php index 7410e64fad5..9b3b579107a 100644 --- a/lib/classes/oauth2/client.php +++ b/lib/classes/oauth2/client.php @@ -30,6 +30,7 @@ require_once($CFG->libdir . '/filelib.php'); use moodle_url; use curl; +use stdClass; /** * Configurable oauth2 client class where the urls come from DB. @@ -83,35 +84,98 @@ abstract class client extends \oauth2_client { return $this->issuer->get_endpoint_url('authorization'); } - public function get_issuer() { + /** + * Get the oauth2 issuer for this client. + * + * @return \core\oauth2\issuer Issuer + */ + public function get_issuer() { return $this->issuer; } + /** + * Override to append additional params to a authentication request. + * + * @return array (name value pairs). + */ public function get_additional_login_parameters() { - if ($this->issuer->get('behaviour') == issuer::BEHAVIOUR_OPENID_CONNECT) { - return ['access_type' => 'offline', 'prompt' => 'consent']; - } return []; } + /** + * Override to change the scopes requested with an authentiction request. + * + * @return string + */ protected function get_login_scopes() { return 'openid profile email'; } /** * Returns the token url for OAuth 2.0 request + * + * We are overriding the parent function so we get this from the configured endpoint. + * * @return string the auth url */ protected function token_url() { return $this->issuer->get_endpoint_url('token'); } + /** + * We want a unique key for each issuer / and a different key for system vs user oauth. + * + * @return string The unique key for the session value. + */ protected function get_tokenname() { - $name = static::class; + $name = 'oauth2-state-' . $this->issuer->get('id'); if ($this->system) { $name .= '-system'; } return $name; } + protected function get_userinfo_mapping() { + $fields = user_field_mapping::get_records(['issuerid' => $this->issuer->get('id')]); + + $map = []; + foreach ($fields as $field) { + $map[$field->get('externalfield')] = $field->get('internalfield'); + } + return $map; + } + + 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; + } + + $map = $this->get_userinfo_mapping(); + + $user = new stdClass(); + foreach ($map as $openidproperty => $moodleproperty) { + if (!empty($userinfo->$openidproperty)) { + $user->$moodleproperty = $userinfo->$openidproperty; + } + } + + if (!empty($user->picture)) { + $user->picture = download_file_content($user->picture, null, null, false, 10, 10, true, null, false); + } else { + $pictureurl = $this->issuer->get_endpoint_url('userpicture'); + if (!empty($pictureurl)) { + $user->picture = $this->get($pictureurl); + } + } + + return (array)$user; + } } diff --git a/lib/classes/oauth2/client_microsoft.php b/lib/classes/oauth2/client_microsoft.php index 3d0bcd617e7..7059c4d9e6a 100644 --- a/lib/classes/oauth2/client_microsoft.php +++ b/lib/classes/oauth2/client_microsoft.php @@ -26,6 +26,7 @@ namespace core\oauth2; defined('MOODLE_INTERNAL') || die(); use moodle_url; +use stdClass; /** * We have to call directly to the graph APIs because the Microsoft Open ID Connect API is @@ -36,19 +37,13 @@ use moodle_url; */ 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'; + if ($this->system) { + return 'openid profile email user.read offline_access'; + } else { + 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_openid_connect.php b/lib/classes/oauth2/client_openid_connect.php index 4ff9837f0cb..805102c0f8d 100644 --- a/lib/classes/oauth2/client_openid_connect.php +++ b/lib/classes/oauth2/client_openid_connect.php @@ -37,62 +37,10 @@ use Exception; */ 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 index fe3a0122e90..5916455a172 100644 --- a/lib/classes/oauth2/endpoint.php +++ b/lib/classes/oauth2/endpoint.php @@ -26,6 +26,7 @@ namespace core\oauth2; defined('MOODLE_INTERNAL') || die(); use core\persistent; +use lang_string; /** * Class for loading/storing oauth2 endpoints from the DB @@ -55,4 +56,11 @@ class endpoint extends persistent { ) ); } + + protected function validate_url($value) { + if (strpos($value, 'https://') !== 0) { + return new lang_string('sslonlyaccess', 'error'); + } + return true; + } } diff --git a/lib/classes/oauth2/user_field_mapping.php b/lib/classes/oauth2/user_field_mapping.php new file mode 100644 index 00000000000..1ccf26b0e9e --- /dev/null +++ b/lib/classes/oauth2/user_field_mapping.php @@ -0,0 +1,78 @@ +. + +/** + * 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 user field mappings from the DB + * + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_field_mapping extends persistent { + + const TABLE = 'oauth2_user_field_mapping'; + + private static $userfields = [ + 'firstname', + 'middlename', + 'lastname', + 'email', + 'username', + 'idnumber', + 'url', + 'alternatename', + 'picture', + 'address', + 'phone', + 'lang' + ]; + + /** + * Return the definition of the properties of this model. + * + * @return array + */ + protected static function define_properties() { + return array( + 'issuerid' => array( + 'type' => PARAM_INT + ), + 'externalfield' => array( + 'type' => PARAM_ALPHANUMEXT, + ), + 'internalfield' => array( + 'type' => PARAM_ALPHANUMEXT, + 'choices' => self::$userfields, + ) + ); + } + + public function get_internalfield_list() { + return array_combine(self::$userfields, self::$userfields); + } +} diff --git a/lib/db/install.xml b/lib/db/install.xml index 0233dedcf37..ba926fd74af 100755 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -3513,5 +3513,22 @@ + + + + + + + + + + + + + + + + +
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 189c3338734..5666423c38c 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2699,10 +2699,39 @@ function xmldb_main_upgrade($oldversion) { if ($oldversion < 2017033100.01) { - \core\oauth2\api::install_default_issuers(); + // Define table oauth2_user_field_mapping to be created. + $table = new xmldb_table('oauth2_user_field_mapping'); + + // Adding fields to table oauth2_user_field_mapping. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('timecreated', 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('externalfield', XMLDB_TYPE_CHAR, '64', null, XMLDB_NOTNULL, null, null); + $table->add_field('internalfield', XMLDB_TYPE_CHAR, '64', null, XMLDB_NOTNULL, null, null); + + // Adding keys to table oauth2_user_field_mapping. + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('issuerkey', XMLDB_KEY_FOREIGN, array('issuerid'), 'oauth2_issuer', array('id')); + $table->add_key('uniqexternal', XMLDB_KEY_UNIQUE, array('issuerid', 'externalfield')); + $table->add_key('uniqinternal', XMLDB_KEY_UNIQUE, array('issuerid', 'internalfield')); + + // Conditionally launch create table for oauth2_user_field_mapping. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + // Main savepoint reached. upgrade_main_savepoint(true, 2017033100.01); } + if ($oldversion < 2017033100.02) { + + \core\oauth2\api::install_default_issuers(); + // Main savepoint reached. + upgrade_main_savepoint(true, 2017033100.02); + } + return true; } diff --git a/lib/filelib.php b/lib/filelib.php index bdc90bd114b..e326406e13b 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'; diff --git a/lib/oauthlib.php b/lib/oauthlib.php index 9efb6d100f6..7260defeb96 100644 --- a/lib/oauthlib.php +++ b/lib/oauthlib.php @@ -441,6 +441,15 @@ abstract class oauth2_client extends curl { // We have a token so we are logged in. if (isset($this->accesstoken->token)) { + // Check that the access token has all the requested scopes. + $scopecheck = ' ' . $this->accesstoken->scope . ' '; + + $requiredscopes = explode(' ', $this->scope); + foreach ($requiredscopes as $requiredscope) { + if (strpos($scopecheck, ' ' . $requiredscope . ' ') === false) { + return false; + } + } return true; } @@ -533,6 +542,10 @@ abstract class oauth2_client extends curl { $r = json_decode($response); + if (!empty($r->error)) { + throw new moodle_exception($r->error . ' ' . $r->error_description); + } + if (!isset($r->access_token)) { return false; } @@ -548,6 +561,12 @@ abstract class oauth2_client extends curl { // Expires 10 seconds before actual expiry. $accesstoken->expires = (time() + ($r->expires_in - 10)); } + if (isset($r->scope)) { + $accesstoken->scope = $r->scope; + } else { + $accesstoken->scope = $this->scope; + } + // Also add the scopes. $this->store_token($accesstoken); return true; diff --git a/theme/boost/templates/core/login.mustache b/theme/boost/templates/core/login.mustache index c253f728136..90fb24c96c5 100644 --- a/theme/boost/templates/core/login.mustache +++ b/theme/boost/templates/core/login.mustache @@ -178,7 +178,7 @@