mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 21:49:15 +01:00
MDL-79920 tool_mfa: Improve MFA management for users
Includes the ability to replace/update a factor. Major changes to classes and strings were performed to allow for these improvements.
This commit is contained in:
parent
b2ed67eb43
commit
34bc9a2e9f
@ -25,7 +25,6 @@
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
use tool_mfa\local\form\setup_factor_form;
|
||||
use tool_mfa\local\form\revoke_factor_form;
|
||||
|
||||
require_login(null, false);
|
||||
if (isguestuser()) {
|
||||
@ -63,7 +62,6 @@ $context = context_user::instance($USER->id);
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url('/admin/tool/mfa/action.php');
|
||||
$PAGE->set_pagelayout('standard');
|
||||
$PAGE->set_title(get_string($action.'factor', 'tool_mfa'));
|
||||
$PAGE->set_cacheable(false);
|
||||
|
||||
if ($node = $PAGE->settingsnav->find('usercurrentsettings', null)) {
|
||||
@ -77,7 +75,8 @@ switch ($action) {
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
$PAGE->navbar->add(get_string('setupfactor', 'factor_'.$factor));
|
||||
$PAGE->set_title(get_string('setupfactor', 'tool_mfa'));
|
||||
$PAGE->navbar->add($factorobject->get_setup_string());
|
||||
$OUTPUT = $PAGE->get_renderer('tool_mfa');
|
||||
$form = new setup_factor_form($currenturl, ['factorname' => $factor]);
|
||||
|
||||
@ -106,37 +105,35 @@ switch ($action) {
|
||||
|
||||
break;
|
||||
|
||||
case 'revoke':
|
||||
// Ensure sesskey is valid.
|
||||
require_sesskey();
|
||||
|
||||
if (!$factorobject || !$factorobject->has_revoke()) {
|
||||
throw new moodle_exception('error:revoke', 'tool_mfa', $returnurl);
|
||||
case 'replace':
|
||||
// Replace works much the same as setup.
|
||||
if (!$factorobject || !$factorobject->has_replace()) {
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
$PAGE->navbar->add(get_string('action:revoke', 'factor_'.$factor));
|
||||
$PAGE->set_title(get_string('replacefactor', 'tool_mfa'));
|
||||
$PAGE->navbar->add($factorobject->get_setup_string());
|
||||
$OUTPUT = $PAGE->get_renderer('tool_mfa');
|
||||
|
||||
$revokeparams = [
|
||||
'factorname' => $factorobject->get_display_name(),
|
||||
'devicename' => $factorobject->get_label($factorid),
|
||||
];
|
||||
$form = new revoke_factor_form($currenturl, $revokeparams);
|
||||
// Use setup factor form, but pass in additional id for replacement.
|
||||
$form = new setup_factor_form($currenturl, ['factorname' => $factor, 'replaceid' => $factorid]);
|
||||
|
||||
if ($form->is_submitted()) {
|
||||
$form->is_validated();
|
||||
|
||||
if ($form->is_cancelled()) {
|
||||
$factorobject->setup_factor_form_is_cancelled($factorid);
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
if ($form->get_data()) {
|
||||
if ($factorobject->revoke_user_factor($factorid)) {
|
||||
$finalurl = new moodle_url($returnurl, ['action' => 'revoked', 'factorid' => $factorid]);
|
||||
if ($data = $form->get_data()) {
|
||||
$record = $factorobject->replace_user_factor($data, $factorid);
|
||||
if (!empty($record)) {
|
||||
$factorobject->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
|
||||
$finalurl = new moodle_url($returnurl, ['action' => 'setup', 'factorid' => $record->id]);
|
||||
redirect($finalurl);
|
||||
}
|
||||
|
||||
throw new moodle_exception('error:revoke', 'tool_mfa', $returnurl);
|
||||
throw new moodle_exception('error:setupfactor', 'tool_mfa', $returnurl);
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,6 +142,38 @@ switch ($action) {
|
||||
|
||||
break;
|
||||
|
||||
case 'revoke':
|
||||
// Ensure sesskey is valid.
|
||||
require_sesskey();
|
||||
$PAGE->set_title(get_string('revokefactor', 'tool_mfa'));
|
||||
|
||||
if (!$factorobject || !$factorobject->has_revoke()) {
|
||||
throw new moodle_exception('error:revoke', 'tool_mfa', $returnurl);
|
||||
}
|
||||
|
||||
if ($factorobject->revoke_user_factor($factorid)) {
|
||||
$finalurl = new moodle_url($returnurl, ['action' => 'revoked', 'factorid' => $factorid]);
|
||||
redirect($finalurl);
|
||||
}
|
||||
|
||||
throw new moodle_exception('error:revoke', 'tool_mfa', $returnurl);
|
||||
|
||||
break;
|
||||
|
||||
case 'manage':
|
||||
|
||||
$PAGE->set_title(get_string('managefactor', 'tool_mfa'));
|
||||
$PAGE->navbar->add(get_string('action:manage', 'factor_'.$factor));
|
||||
$OUTPUT = $PAGE->get_renderer('tool_mfa');
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('managefactor', 'factor_' . $factorobject->name));
|
||||
echo $OUTPUT->active_factors($factor);
|
||||
echo $OUTPUT->single_button($returnurl, get_string('back'));
|
||||
// JS for modal confirming replace and revoke actions.
|
||||
$PAGE->requires->js_call_amd('tool_mfa/confirmation_modal', 'init', [$context->id]);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
10
admin/tool/mfa/amd/build/confirmation_modal.min.js
vendored
Normal file
10
admin/tool/mfa/amd/build/confirmation_modal.min.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
define("tool_mfa/confirmation_modal",["exports","core/modal_events","core/modal_save_cancel","core/notification","core/str","core/url","core/fragment"],(function(_exports,_modal_events,_modal_save_cancel,_notification,_str,_url,_fragment){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* Modal for confirming factor actions.
|
||||
*
|
||||
* @module tool_mfa/confirmation_modal
|
||||
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal_events=_interopRequireDefault(_modal_events),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_notification=_interopRequireDefault(_notification),_url=_interopRequireDefault(_url),_fragment=_interopRequireDefault(_fragment);const SELECTORS_ACTION=".mfa-action-button";_exports.init=contextId=>{registerEventListeners(contextId)};const registerEventListeners=contextId=>{document.addEventListener("click",(e=>{const action=e.target.closest(SELECTORS_ACTION);action&&buildModal(action,contextId).catch(_notification.default.exception)}))},buildModal=async(element,contextId)=>{const data={action:element.getAttribute("data-action"),factor:element.getAttribute("data-factor"),factorid:element.getAttribute("data-factorid"),devicename:element.getAttribute("data-devicename"),actionurl:_url.default.relativeUrl("/admin/tool/mfa/action.php")};"revoke"===data.action?(data.title=await(0,_str.getString)("revokefactorconfirmation","factor_"+data.factor,data.devicename),data.buttontext=await(0,_str.getString)("yesremove","tool_mfa")):"replace"===data.action&&(data.title=await(0,_str.getString)("replacefactorconfirmation","factor_"+data.factor,data.devicename),data.buttontext=await(0,_str.getString)("yesreplace","tool_mfa"));const modal=await _modal_save_cancel.default.create({title:data.title,body:_fragment.default.loadFragment("tool_mfa","factor_action_confirmation_form",contextId,data),show:!0,buttons:{save:data.buttontext,cancel:(0,_str.getString)("cancel","moodle")}});modal.getRoot().on(_modal_events.default.save,(()=>{modal.getRoot().find("form").submit()}))}}));
|
||||
|
||||
//# sourceMappingURL=confirmation_modal.min.js.map
|
1
admin/tool/mfa/amd/build/confirmation_modal.min.js.map
Normal file
1
admin/tool/mfa/amd/build/confirmation_modal.min.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"confirmation_modal.min.js","sources":["../src/confirmation_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Modal for confirming factor actions.\n *\n * @module tool_mfa/confirmation_modal\n * @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalEvents from 'core/modal_events';\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport Notification from 'core/notification';\nimport {getString} from 'core/str';\nimport Url from 'core/url';\nimport Fragment from 'core/fragment';\n\nconst SELECTORS = {\n ACTION: '.mfa-action-button',\n};\n\n/**\n * Entrypoint of the js.\n *\n * @method init\n * @param {Number} contextId Context ID of the user.\n */\nexport const init = (contextId) => {\n registerEventListeners(contextId);\n};\n\n/**\n * Register event listeners.\n *\n * @method registerEventListeners\n * @param {Number} contextId Context ID of the user.\n */\nconst registerEventListeners = (contextId) => {\n document.addEventListener('click', (e) => {\n const action = e.target.closest(SELECTORS.ACTION);\n if (action) {\n buildModal(action, contextId).catch(Notification.exception);\n }\n });\n};\n\n/**\n * Build the modal with the provided data.\n *\n * @method buildModal\n * @param {object} element The button element.\n * @param {Number} contextId Context ID of the user.\n */\nconst buildModal = async(element, contextId) => {\n\n // Prepare data for modal.\n const data = {\n action: element.getAttribute('data-action'),\n factor: element.getAttribute('data-factor'),\n factorid: element.getAttribute('data-factorid'),\n devicename: element.getAttribute('data-devicename'),\n actionurl: Url.relativeUrl('/admin/tool/mfa/action.php'),\n };\n\n // Customise modal depending on action being performed.\n if (data.action === 'revoke') {\n data.title = await getString('revokefactorconfirmation', 'factor_' + data.factor, data.devicename);\n data.buttontext = await getString('yesremove', 'tool_mfa');\n\n } else if (data.action === 'replace') {\n data.title = await getString('replacefactorconfirmation', 'factor_' + data.factor, data.devicename);\n data.buttontext = await getString('yesreplace', 'tool_mfa');\n }\n\n const modal = await ModalSaveCancel.create({\n title: data.title,\n body: Fragment.loadFragment('tool_mfa', 'factor_action_confirmation_form', contextId, data),\n show: true,\n buttons: {\n 'save': data.buttontext,\n 'cancel': getString('cancel', 'moodle'),\n },\n });\n\n modal.getRoot().on(ModalEvents.save, () => {\n modal.getRoot().find('form').submit();\n });\n\n};\n"],"names":["SELECTORS","contextId","registerEventListeners","document","addEventListener","e","action","target","closest","buildModal","catch","Notification","exception","async","element","data","getAttribute","factor","factorid","devicename","actionurl","Url","relativeUrl","title","buttontext","modal","ModalSaveCancel","create","body","Fragment","loadFragment","show","buttons","getRoot","on","ModalEvents","save","find","submit"],"mappings":";;;;;;;4UA8BMA,iBACM,mCASSC,YACjBC,uBAAuBD,kBASrBC,uBAA0BD,YAC5BE,SAASC,iBAAiB,SAAUC,UAC1BC,OAASD,EAAEE,OAAOC,QAAQR,kBAC5BM,QACAG,WAAWH,OAAQL,WAAWS,MAAMC,sBAAaC,eAYvDH,WAAaI,MAAMC,QAASb,mBAGxBc,KAAO,CACTT,OAAQQ,QAAQE,aAAa,eAC7BC,OAAQH,QAAQE,aAAa,eAC7BE,SAAUJ,QAAQE,aAAa,iBAC/BG,WAAYL,QAAQE,aAAa,mBACjCI,UAAWC,aAAIC,YAAY,+BAIX,WAAhBP,KAAKT,QACLS,KAAKQ,YAAc,kBAAU,2BAA4B,UAAYR,KAAKE,OAAQF,KAAKI,YACvFJ,KAAKS,iBAAmB,kBAAU,YAAa,aAExB,YAAhBT,KAAKT,SACZS,KAAKQ,YAAc,kBAAU,4BAA6B,UAAYR,KAAKE,OAAQF,KAAKI,YACxFJ,KAAKS,iBAAmB,kBAAU,aAAc,mBAG9CC,YAAcC,2BAAgBC,OAAO,CACvCJ,MAAOR,KAAKQ,MACZK,KAAMC,kBAASC,aAAa,WAAY,kCAAmC7B,UAAWc,MACtFgB,MAAM,EACNC,QAAS,MACGjB,KAAKS,mBACH,kBAAU,SAAU,aAItCC,MAAMQ,UAAUC,GAAGC,sBAAYC,MAAM,KACjCX,MAAMQ,UAAUI,KAAK,QAAQC"}
|
102
admin/tool/mfa/amd/src/confirmation_modal.js
Normal file
102
admin/tool/mfa/amd/src/confirmation_modal.js
Normal file
@ -0,0 +1,102 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Modal for confirming factor actions.
|
||||
*
|
||||
* @module tool_mfa/confirmation_modal
|
||||
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import ModalEvents from 'core/modal_events';
|
||||
import ModalSaveCancel from 'core/modal_save_cancel';
|
||||
import Notification from 'core/notification';
|
||||
import {getString} from 'core/str';
|
||||
import Url from 'core/url';
|
||||
import Fragment from 'core/fragment';
|
||||
|
||||
const SELECTORS = {
|
||||
ACTION: '.mfa-action-button',
|
||||
};
|
||||
|
||||
/**
|
||||
* Entrypoint of the js.
|
||||
*
|
||||
* @method init
|
||||
* @param {Number} contextId Context ID of the user.
|
||||
*/
|
||||
export const init = (contextId) => {
|
||||
registerEventListeners(contextId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*
|
||||
* @method registerEventListeners
|
||||
* @param {Number} contextId Context ID of the user.
|
||||
*/
|
||||
const registerEventListeners = (contextId) => {
|
||||
document.addEventListener('click', (e) => {
|
||||
const action = e.target.closest(SELECTORS.ACTION);
|
||||
if (action) {
|
||||
buildModal(action, contextId).catch(Notification.exception);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the modal with the provided data.
|
||||
*
|
||||
* @method buildModal
|
||||
* @param {object} element The button element.
|
||||
* @param {Number} contextId Context ID of the user.
|
||||
*/
|
||||
const buildModal = async(element, contextId) => {
|
||||
|
||||
// Prepare data for modal.
|
||||
const data = {
|
||||
action: element.getAttribute('data-action'),
|
||||
factor: element.getAttribute('data-factor'),
|
||||
factorid: element.getAttribute('data-factorid'),
|
||||
devicename: element.getAttribute('data-devicename'),
|
||||
actionurl: Url.relativeUrl('/admin/tool/mfa/action.php'),
|
||||
};
|
||||
|
||||
// Customise modal depending on action being performed.
|
||||
if (data.action === 'revoke') {
|
||||
data.title = await getString('revokefactorconfirmation', 'factor_' + data.factor, data.devicename);
|
||||
data.buttontext = await getString('yesremove', 'tool_mfa');
|
||||
|
||||
} else if (data.action === 'replace') {
|
||||
data.title = await getString('replacefactorconfirmation', 'factor_' + data.factor, data.devicename);
|
||||
data.buttontext = await getString('yesreplace', 'tool_mfa');
|
||||
}
|
||||
|
||||
const modal = await ModalSaveCancel.create({
|
||||
title: data.title,
|
||||
body: Fragment.loadFragment('tool_mfa', 'factor_action_confirmation_form', contextId, data),
|
||||
show: true,
|
||||
buttons: {
|
||||
'save': data.buttontext,
|
||||
'cancel': getString('cancel', 'moodle'),
|
||||
},
|
||||
});
|
||||
|
||||
modal.getRoot().on(ModalEvents.save, () => {
|
||||
modal.getRoot().find('form').submit();
|
||||
});
|
||||
|
||||
};
|
@ -146,6 +146,19 @@ abstract class object_factor_base implements object_factor {
|
||||
return get_string('info', 'factor_'.$this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns factor help from language string when there is factor management available.
|
||||
*
|
||||
* Base class implementation.
|
||||
*
|
||||
* @param int $factorid The factor we want manage info for.
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_manage_info(int $factorid): string {
|
||||
return get_string('manageinfo', 'factor_'.$this->name, $this->get_label($factorid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines setup_factor form definition page for particular factor.
|
||||
*
|
||||
@ -218,6 +231,20 @@ abstract class object_factor_base implements object_factor {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a given factor and adds it to user's active factors list.
|
||||
* Returns the new factor if it has been successfully replaced.
|
||||
*
|
||||
* Dummy implementation. Should be overridden in child class.
|
||||
*
|
||||
* @param stdClass $data The new factor data.
|
||||
* @param int $id The id of the factor to replace.
|
||||
* @return stdClass|null the record if created, or null.
|
||||
*/
|
||||
public function replace_user_factor(stdClass $data, int $id): stdClass|null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all user factors of given type (both active and revoked).
|
||||
*
|
||||
@ -332,6 +359,18 @@ abstract class object_factor_base implements object_factor {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if factor class has factor records that can be replaced.
|
||||
*
|
||||
* Override in child class if necessary.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_replace(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When validation code is correct - update lastverified field for given factor.
|
||||
* If factor id is not provided, update all factor entries for user.
|
||||
@ -539,6 +578,15 @@ abstract class object_factor_base implements object_factor {
|
||||
return get_string('setupfactor', 'tool_mfa');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string for manage button on preferences page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_manage_string(): string {
|
||||
return get_string('managefactor', 'tool_mfa');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all instances of factor for a user.
|
||||
*
|
||||
|
@ -21,29 +21,36 @@ defined('MOODLE_INTERNAL') || die();
|
||||
require_once($CFG->libdir . "/formslib.php");
|
||||
|
||||
/**
|
||||
* Revoke factor form
|
||||
* Factor action confirmation form.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <golenkovm@gmail.com>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class revoke_factor_form extends \moodleform {
|
||||
class factor_action_confirmation_form extends \moodleform {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see moodleform::definition()
|
||||
* Form definition.
|
||||
*/
|
||||
public function definition(): void {
|
||||
global $OUTPUT;
|
||||
$mform = $this->_form;
|
||||
$factorname = $this->_customdata['factorname'];
|
||||
$factor = $this->_customdata['factor'];
|
||||
$devicename = $this->_customdata['devicename'];
|
||||
$factorid = $this->_customdata['factorid'];
|
||||
$action = $this->_customdata['action'];
|
||||
|
||||
$mform->addElement('html', $OUTPUT->heading(get_string('areyousure', 'tool_mfa'), 4));
|
||||
$mform->addElement('html', $OUTPUT->heading(get_string('factor', 'tool_mfa').': '.$factorname, 5));
|
||||
$mform->addElement('html', $OUTPUT->heading(get_string('devicename', 'tool_mfa').': '.$devicename, 5));
|
||||
$mform->addElement('html', get_string('confirmation' . $action, 'tool_mfa', $devicename));
|
||||
|
||||
$this->add_action_buttons(true, get_string('revoke', 'tool_mfa'));
|
||||
$mform->setType('factorid', PARAM_INT);
|
||||
$mform->addElement('hidden', 'factorid', $factorid);
|
||||
|
||||
$mform->setType('factor', PARAM_TEXT);
|
||||
$mform->addElement('hidden', 'factor', $factor);
|
||||
|
||||
$mform->setType('action', PARAM_TEXT);
|
||||
$mform->addElement('hidden', 'action', $action);
|
||||
|
||||
$mform->addElement('hidden', 'sesskey', sesskey());
|
||||
}
|
||||
}
|
@ -36,12 +36,17 @@ class setup_factor_form extends \moodleform {
|
||||
*/
|
||||
public function definition(): void {
|
||||
$mform = $this->_form;
|
||||
// Indicate a factor id that will be replaced with this setup.
|
||||
$replaceid = $this->_customdata['replaceid'] ?? null;
|
||||
if (!empty($replaceid)) {
|
||||
$mform->addelement('hidden', 'replaceid', $replaceid);
|
||||
$mform->setType('replaceid', PARAM_INT);
|
||||
}
|
||||
|
||||
$factorname = $this->_customdata['factorname'];
|
||||
$factor = \tool_mfa\plugininfo\factor::get_factor($factorname);
|
||||
$mform = $factor->setup_factor_form_definition($mform);
|
||||
$this->xss_whitelist_static_form_elements($mform);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,18 +69,78 @@ class renderer extends \plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function available_factors(): string {
|
||||
$html = $this->output->heading(get_string('preferences:availablefactors', 'tool_mfa'), 2);
|
||||
|
||||
global $USER;
|
||||
$factors = factor::get_enabled_factors();
|
||||
$data = [];
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
// TODO is_configured / is_ready.
|
||||
if (!$factor->has_setup() || !$factor->show_setup_buttons()) {
|
||||
|
||||
// Allow all factors with setup and button.
|
||||
// Make an exception for email factor as this is currently set up by admins only and required on this list.
|
||||
if ((!$factor->has_setup() || !$factor->show_setup_buttons()) && !$factor instanceof \factor_email\factor) {
|
||||
continue;
|
||||
}
|
||||
$html .= $this->setup_factor($factor);
|
||||
|
||||
$userfactors = $factor->get_active_user_factors($USER);
|
||||
$active = !empty($userfactors) ?? false;
|
||||
$button = null;
|
||||
$icon = $factor->get_icon();
|
||||
$params = [
|
||||
'action' => 'setup',
|
||||
'factor' => $factor->name,
|
||||
'sesskey' => sesskey(),
|
||||
];
|
||||
|
||||
if (!$active) {
|
||||
// Not active yet and requires set up.
|
||||
$info = $factor->get_info();
|
||||
|
||||
if ($factor->show_setup_buttons()) {
|
||||
$params['action'] = 'setup';
|
||||
$button = new \single_button(
|
||||
url: new \moodle_url('action.php', $params),
|
||||
label: $factor->get_setup_string(),
|
||||
method: 'post',
|
||||
type: \single_button::BUTTON_PRIMARY,
|
||||
attributes: [
|
||||
'aria-label' => get_string('setupfactor', 'factor_' . $factor->name),
|
||||
],
|
||||
);
|
||||
$button = $button->export_for_template($this->output);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Active and can be managed.
|
||||
$factorid = reset($userfactors)->id;
|
||||
$info = $factor->get_manage_info($factorid);
|
||||
|
||||
if ($factor->show_setup_buttons()) {
|
||||
$params['action'] = 'manage';
|
||||
$button = new \single_button(
|
||||
url: new \moodle_url('action.php', $params),
|
||||
label: $factor->get_manage_string(),
|
||||
method: 'post',
|
||||
type: \single_button::BUTTON_PRIMARY,
|
||||
attributes: [
|
||||
'aria-label' => get_string('managefactor', 'factor_' . $factor->name),
|
||||
],
|
||||
);
|
||||
$button = $button->export_for_template($this->output);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare data for template.
|
||||
$data['factors'][] = [
|
||||
'active' => $active,
|
||||
'label' => $factor->get_display_name(),
|
||||
'name' => $factor->name,
|
||||
'info' => $info,
|
||||
'icon' => $icon,
|
||||
'button' => $button,
|
||||
];
|
||||
}
|
||||
|
||||
return $html;
|
||||
return $this->render_from_template('tool_mfa/mfa_selector', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,8 +148,12 @@ class renderer extends \plugin_renderer_base {
|
||||
*
|
||||
* @param object $factor object of the factor class
|
||||
* @return string
|
||||
* @deprecated since Moodle 4.4
|
||||
* @todo Final deprecation in Moodle 4.8 MDL-80995
|
||||
*/
|
||||
public function setup_factor(object $factor): string {
|
||||
debugging('The method setup_factor() has been deprecated. The HTML derived from this method is no longer needed.
|
||||
Similar HTML is now achieved as part of available_factors().', DEBUG_DEVELOPER);
|
||||
$html = '';
|
||||
|
||||
$html .= html_writer::start_tag('div', ['class' => 'card']);
|
||||
@ -109,53 +173,56 @@ class renderer extends \plugin_renderer_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines section with active user's factors.
|
||||
* Show a table displaying a users active factors.
|
||||
*
|
||||
* @param string|null $filterfactor The factor name to filter on.
|
||||
* @return string $html
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function active_factors(): string {
|
||||
public function active_factors(string $filterfactor = null): string {
|
||||
global $USER, $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/iplookup/lib.php');
|
||||
|
||||
$html = $this->output->heading(get_string('preferences:activefactors', 'tool_mfa'), 2);
|
||||
$html = '';
|
||||
|
||||
$headers = get_strings([
|
||||
'factor',
|
||||
'devicename',
|
||||
'created',
|
||||
'createdfromip',
|
||||
'lastverified',
|
||||
'revoke',
|
||||
'added',
|
||||
'lastused',
|
||||
'replace',
|
||||
'remove',
|
||||
], 'tool_mfa');
|
||||
|
||||
$table = new \html_table();
|
||||
$table->id = 'active_factors';
|
||||
$table->attributes['class'] = 'generaltable table table-bordered';
|
||||
$table->head = [
|
||||
$headers->factor,
|
||||
$headers->devicename,
|
||||
$headers->created,
|
||||
$headers->createdfromip,
|
||||
$headers->lastverified,
|
||||
$headers->revoke,
|
||||
$headers->added,
|
||||
$headers->lastused,
|
||||
$headers->replace,
|
||||
$headers->remove,
|
||||
];
|
||||
$table->colclasses = [
|
||||
'leftalign',
|
||||
'leftalign',
|
||||
'centeralign',
|
||||
'centeralign',
|
||||
'centeralign',
|
||||
'centeralign',
|
||||
'centeralign',
|
||||
'centeralign',
|
||||
'text-left',
|
||||
'text-left',
|
||||
'text-left',
|
||||
'text-center',
|
||||
'text-center',
|
||||
];
|
||||
$table->data = [];
|
||||
|
||||
$factors = factor::get_enabled_factors();
|
||||
$hasmorethanone = factor::user_has_more_than_one_active_factors();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
|
||||
// Filter results to match the specified factor.
|
||||
if (!empty($filterfactor) && $factor->name !== $filterfactor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$userfactors = $factor->get_active_user_factors($USER);
|
||||
|
||||
if (!$factor->has_setup()) {
|
||||
@ -163,15 +230,39 @@ class renderer extends \plugin_renderer_base {
|
||||
}
|
||||
|
||||
foreach ($userfactors as $userfactor) {
|
||||
if ($factor->has_revoke()) {
|
||||
$revokeparams = [
|
||||
'action' => 'revoke', 'factor' => $factor->name,
|
||||
'factorid' => $userfactor->id, 'sesskey' => sesskey(),
|
||||
|
||||
// Revoke option.
|
||||
if ($factor->has_revoke() && $hasmorethanone) {
|
||||
$content = $headers->remove;
|
||||
$attributes = [
|
||||
'data-action' => 'revoke',
|
||||
'data-factor' => $factor->name,
|
||||
'data-factorid' => $userfactor->id,
|
||||
'data-factorname' => $factor->get_display_name(),
|
||||
'data-devicename' => $userfactor->label,
|
||||
'aria-label' => get_string('revokefactor', 'tool_mfa'),
|
||||
'class' => 'btn btn-primary mfa-action-button',
|
||||
];
|
||||
$revokeurl = new \moodle_url('action.php', $revokeparams);
|
||||
$revokelink = \html_writer::link($revokeurl, $headers->revoke);
|
||||
$revokebutton = \html_writer::tag('button', $content, $attributes);
|
||||
} else {
|
||||
$revokelink = '';
|
||||
$revokebutton = get_string('statusna');
|
||||
}
|
||||
|
||||
// Replace option.
|
||||
if ($factor->has_replace()) {
|
||||
$content = $headers->replace;
|
||||
$attributes = [
|
||||
'data-action' => 'replace',
|
||||
'data-factor' => $factor->name,
|
||||
'data-factorid' => $userfactor->id,
|
||||
'data-factorname' => $factor->get_display_name(),
|
||||
'data-devicename' => $userfactor->label,
|
||||
'aria-label' => get_string('replacefactor', 'tool_mfa'),
|
||||
'class' => 'btn btn-primary mfa-action-button',
|
||||
];
|
||||
$replacebutton = \html_writer::tag('button', $content, $attributes);
|
||||
} else {
|
||||
$replacebutton = get_string('statusna');
|
||||
}
|
||||
|
||||
$timecreated = $userfactor->timecreated == '-' ? '-'
|
||||
@ -185,17 +276,12 @@ class renderer extends \plugin_renderer_base {
|
||||
$lastverified .= get_string('ago', 'core_message', format_time(time() - $userfactor->lastverified));
|
||||
}
|
||||
|
||||
$info = iplookup_find_location($userfactor->createdfromip);
|
||||
$ip = $userfactor->createdfromip;
|
||||
$ip .= '<br>' . $info['country'] . ' - ' . $info['city'];
|
||||
|
||||
$row = new \html_table_row([
|
||||
$factor->get_display_name(),
|
||||
$userfactor->label,
|
||||
$timecreated,
|
||||
$ip,
|
||||
$lastverified,
|
||||
$revokelink,
|
||||
$replacebutton,
|
||||
$revokebutton,
|
||||
]);
|
||||
$table->data[] = $row;
|
||||
}
|
||||
|
@ -205,6 +205,8 @@ class factor extends \core\plugininfo\base {
|
||||
$actions[] = 'disable';
|
||||
$actions[] = 'up';
|
||||
$actions[] = 'down';
|
||||
$actions[] = 'manage';
|
||||
$actions[] = 'replace';
|
||||
|
||||
return $actions;
|
||||
}
|
||||
@ -366,4 +368,19 @@ class factor extends \core\plugininfo\base {
|
||||
|
||||
return $factors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has more than one active factor.
|
||||
*
|
||||
* @return bool Returns true if there are more than one.
|
||||
*/
|
||||
public static function user_has_more_than_one_active_factors(): bool {
|
||||
$factors = self::get_active_user_factor_types();
|
||||
$count = count(array_filter($factors, function($factor) {
|
||||
// Include only user factors that can be set.
|
||||
return $factor->has_input();
|
||||
}));
|
||||
|
||||
return $count > 1;
|
||||
}
|
||||
}
|
||||
|
30
admin/tool/mfa/db/renamedclasses.php
Normal file
30
admin/tool/mfa/db/renamedclasses.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file contains mappings for classes that have been renamed.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @copyright 2024 David Woloszyn <david.woloszyn@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$renamedclasses = [
|
||||
// Since Moodle 4.4.
|
||||
'tool_mfa\\local\\form\\revoke_factor_form' => 'tool_mfa\\local\\form\\factor_action_confirmation_form',
|
||||
];
|
4
admin/tool/mfa/lang/en/deprecated.txt
Normal file
4
admin/tool/mfa/lang/en/deprecated.txt
Normal file
@ -0,0 +1,4 @@
|
||||
created,tool_mfa
|
||||
lastverified,tool_mfa
|
||||
revoke,tool_mfa
|
||||
createdfromip,tool_mfa
|
@ -26,18 +26,21 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['achievedweight'] = 'Achieved weight';
|
||||
$string['added'] = 'Added';
|
||||
$string['alltime'] = 'All time';
|
||||
$string['areyousure'] = 'Are you sure you want to revoke factor?';
|
||||
$string['areyousure'] = 'Are you sure you want to remove this factor?';
|
||||
$string['cancellogin'] = 'Cancel login';
|
||||
$string['combination'] = 'Combination';
|
||||
$string['confirmationreplace'] = 'You will be immediately required to set up another \'{$a}\'. Please make sure you are ready to complete the setup process.';
|
||||
$string['confirmationrevoke'] = 'You will no longer be able to use \'{$a}\' to log in to this site.';
|
||||
$string['connector'] = 'AND';
|
||||
$string['created'] = 'Created';
|
||||
$string['createdfromip'] = 'Created from IP';
|
||||
$string['debugmode:heading'] = 'Debug mode';
|
||||
$string['devicename'] = 'Device';
|
||||
$string['entercode'] = 'Enter code';
|
||||
$string['email:subject'] = 'Unable to log in to {$a}';
|
||||
$string['enablefactor'] = 'Enable factor';
|
||||
$string['error:actionnotfound'] = 'Action \'{$a}\' not supported';
|
||||
$string['error:couldnotreplace'] = 'Could not replace this factor.';
|
||||
$string['error:directaccess'] = 'This page shouldn\'t be accessed directly';
|
||||
$string['error:factornotenabled'] = 'Multi-factor authentication factor \'{$a}\' not enabled';
|
||||
$string['error:factornotfound'] = 'Multi-factor authentication factor \'{$a}\' not found';
|
||||
@ -45,7 +48,7 @@ $string['error:isguestuser'] = 'Guests are not allowed here.';
|
||||
$string['error:notenoughfactors'] = 'Unable to authenticate';
|
||||
$string['error:reauth'] = 'We couldn\'t confirm your identity sufficiently to meet the site authentication security policy.<br>This may be due to: <br> 1) Steps being locked - please wait a few minutes and try again.
|
||||
<br> 2) Steps being failed - please double check the details for each step. <br> 3) Steps were skipped - please reload this page or try logging in again.';
|
||||
$string['error:revoke'] = 'Can\'t revoke factor';
|
||||
$string['error:revoke'] = 'Can\'t remove factor';
|
||||
$string['error:setupfactor'] = 'Can\'t set up factor';
|
||||
$string['error:support'] = 'If you are still unable to log in, or believe you are seeing this in error, please email:';
|
||||
$string['error:wrongfactorid'] = 'Factor ID \'{$a}\' is incorrect';
|
||||
@ -58,22 +61,25 @@ $string['event:userpassedmfa'] = 'Verification passed';
|
||||
$string['event:userrevokedfactor'] = 'Factor revocation';
|
||||
$string['event:usersetupfactor'] = 'Factor setup';
|
||||
$string['factor'] = 'Factor';
|
||||
$string['factorreplace'] = 'Factor \'{$a}\' successfully replaced.';
|
||||
$string['factorreport'] = 'All factor report';
|
||||
$string['factorreset'] = 'Your multi-factor authentication \'{$a->factor}\' has been reset by a site administrator. You may need to set up this factor again. {$a->url}';
|
||||
$string['factorresetall'] = 'All your multi-factor authentication factors have been reset by a site administrator. You may need to set up these factors again. {$a}';
|
||||
$string['factorrevoked'] = 'Factor \'{$a}\' successfully revoked.';
|
||||
$string['factorsetup'] = 'Factor \'{$a}\' successfully set up.';
|
||||
$string['factorrevoked'] = '\'{$a}\' successfully removed.';
|
||||
$string['factorsetup'] = '\'{$a}\' successfully set up.';
|
||||
$string['fallback'] = 'Fallback factor';
|
||||
$string['fallback_info'] = 'This factor is a fallback if no other factors are configured. This factor will always fail.';
|
||||
$string['guidance'] = 'Multi-factor authentication user guide';
|
||||
$string['inputrequired'] = 'User input';
|
||||
$string['ipatcreation'] = 'IP address when factor created';
|
||||
$string['lastverified'] = 'Last verified';
|
||||
$string['lastused'] = 'Last used';
|
||||
$string['locked'] = '{$a} (Unavailable)';
|
||||
$string['lockedusersforallfactors'] = 'Locked users: All factors';
|
||||
$string['lockedusersforfactor'] = 'Locked users: {$a}';
|
||||
$string['lockoutnotification'] = 'You have {$a} attempts left.';
|
||||
$string['managefactor'] = 'Manage factor';
|
||||
$string['mfa'] = 'Multi-factor authentication';
|
||||
$string['mfa:intro'] = 'Easily set up and manage multi-factor authentication.';
|
||||
$string['mfa:mfaaccess'] = 'Interact with MFA';
|
||||
$string['mfareports'] = 'MFA reports';
|
||||
$string['mfasettings'] = 'Manage multi-factor authentication';
|
||||
@ -108,6 +114,9 @@ $string['privacy:metadata:tool_mfa_secrets:secret'] = 'The secret security code.
|
||||
$string['privacy:metadata:tool_mfa_secrets:sessionid'] = 'The session ID this secret is associated with.';
|
||||
$string['privacy:metadata:tool_mfa_secrets:userid'] = 'The user this secret is associated with.';
|
||||
$string['redirecterrordetected'] = 'Unsupported redirect detected, script execution terminated. Redirection error occured between MFA and {$a}.';
|
||||
$string['remove'] = 'Remove';
|
||||
$string['replace'] = 'Replace';
|
||||
$string['replacefactor'] = 'Replace factor';
|
||||
$string['resetconfirm'] = 'Reset user factor';
|
||||
$string['resetfactor'] = 'Reset user authentication factors';
|
||||
$string['resetfactorconfirm'] = 'Are you sure you wish to reset this factor for {$a}?';
|
||||
@ -115,8 +124,7 @@ $string['resetfactorplaceholder'] = 'Username or email';
|
||||
$string['resetsuccess'] = 'Factor \'{$a->factor}\' successfully reset for user \'{$a->username}\'.';
|
||||
$string['resetsuccessbulk'] = 'Factor \'{$a}\' successfully reset for provided users.';
|
||||
$string['resetuser'] = 'User:';
|
||||
$string['revoke'] = 'Revoke';
|
||||
$string['revokefactor'] = 'Revoke factor';
|
||||
$string['revokefactor'] = 'Remove factor';
|
||||
$string['selectfactor'] = 'Select factor to reset:';
|
||||
$string['selectperiod'] = 'Select a lookback period for the report:';
|
||||
$string['settings:combinations'] = 'Summary of good conditions for login';
|
||||
@ -141,8 +149,7 @@ $string['settings:redir_exclusions'] = 'URLS which should not redirect the MFA c
|
||||
$string['settings:redir_exclusions_help'] = 'Each new line is a relative URL from the siteroot for which the MFA check will not redirect from';
|
||||
$string['settings:weight'] = 'Factor weight';
|
||||
$string['settings:weight_help'] = 'The weight of this factor if passed. A user needs at least 100 points to log in.';
|
||||
$string['setup'] = 'Setup';
|
||||
$string['setupfactor'] = 'Setup factor';
|
||||
$string['setupfactor'] = 'Set up factor';
|
||||
$string['setuprequired'] = 'User setup';
|
||||
$string['state:fail'] = 'Fail';
|
||||
$string['state:locked'] = 'Locked';
|
||||
@ -159,7 +166,15 @@ $string['usernotfound'] = 'Unable to locate user.';
|
||||
$string['usersauthedinperiod'] = 'Logged in';
|
||||
$string['verification'] = '2-step verification';
|
||||
$string['verification_desc'] = 'To keep your account safe, we need to check that this is really you.';
|
||||
$string['verificationcode'] = 'Enter code';
|
||||
$string['verificationcode'] = 'Verification code';
|
||||
$string['verificationcode_help'] = 'The verification code provided by the current authentication factor.';
|
||||
$string['verifyalt'] = 'Try another way to verify:';
|
||||
$string['weight'] = 'Weight';
|
||||
$string['yesremove'] = 'Yes, remove';
|
||||
$string['yesreplace'] = 'Yes, replace';
|
||||
|
||||
// Deprecated since Moodle 4.4.
|
||||
$string['created'] = 'Created';
|
||||
$string['lastverified'] = 'Last verified';
|
||||
$string['revoke'] = 'Revoke';
|
||||
$string['createdfromip'] = 'Created from IP';
|
||||
|
@ -138,3 +138,33 @@ function tool_mfa_pluginfile(stdClass $course, stdClass $cm, context $context, s
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fragment to confirm a factor action using the confirmation form.
|
||||
*
|
||||
* @param array $args Arguments to the form.
|
||||
* @return null|string The rendered form.
|
||||
*/
|
||||
function tool_mfa_output_fragment_factor_action_confirmation_form($args) {
|
||||
// Check args are not empty.
|
||||
foreach ($args as $key => $arg) {
|
||||
if (empty($arg)) {
|
||||
throw new \moodle_exception('missingparam', 'error', '', $key);
|
||||
}
|
||||
}
|
||||
|
||||
$customdata = [
|
||||
'action' => $args['action'],
|
||||
'factor' => $args['factor'],
|
||||
'factorid' => $args['factorid'],
|
||||
'devicename' => $args['devicename'],
|
||||
];
|
||||
// Indicate we are performing a replacement by include the replace id.
|
||||
if ($args['action'] === 'replace') {
|
||||
$customdata['replaceid'] = $args['factorid'];
|
||||
}
|
||||
|
||||
$mform = new tool_mfa\local\form\factor_action_confirmation_form($args['actionurl'], $customdata);
|
||||
|
||||
return $mform->render();
|
||||
}
|
||||
|
78
admin/tool/mfa/templates/mfa_card.mustache
Normal file
78
admin/tool/mfa/templates/mfa_card.mustache
Normal file
@ -0,0 +1,78 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template tool_mfa/mfa_card
|
||||
|
||||
This template renders the singular card for an authentication factor.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"label": "SMS Mobile phone",
|
||||
"name": "sms",
|
||||
"info": "Have a verification number sent to the mobile number you choose.",
|
||||
"icon": "fa-commenting-o",
|
||||
"active": true,
|
||||
"button": {
|
||||
"id": "single_button123",
|
||||
"method": "post",
|
||||
"action": "action.php",
|
||||
"label": "Setup",
|
||||
"params": [
|
||||
{
|
||||
"name": "sesskey",
|
||||
"value": "123XYZ"
|
||||
},
|
||||
{
|
||||
"name": "factor",
|
||||
"value": "sms"
|
||||
},
|
||||
{
|
||||
"name": "action",
|
||||
"value": "setup"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}}
|
||||
<div class="card dashboard-card {{#active}}active{{/active}}" role="listitem" id="factor-card-{{name}}" aria-labelledby="factor-name-{{name}} {{#active}}active-factor-{{name}}{{/active}}">
|
||||
<div class="card-img p-3">
|
||||
<div class="icon-circle {{^active}}reversed{{/active}}">
|
||||
<i class="icon iconsize-big fa {{icon}}" role="presentation"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-1">
|
||||
<h3 class="h5" id="factor-name-{{name}}">{{label}}</h3>
|
||||
{{{info}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-end justify-content-between p-3">
|
||||
{{#active}}
|
||||
<div class="d-flex">
|
||||
<strong><span class="text-success" id="active-factor-{{name}}">{{#str}}active, moodle{{/str}}</span></strong>
|
||||
</div>
|
||||
{{/active}}
|
||||
{{#button}}
|
||||
<div class="d-flex ml-auto">
|
||||
{{>core/single_button}}
|
||||
</div>
|
||||
{{/button}}
|
||||
</div>
|
||||
</div>
|
||||
|
88
admin/tool/mfa/templates/mfa_selector.mustache
Normal file
88
admin/tool/mfa/templates/mfa_selector.mustache
Normal file
@ -0,0 +1,88 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template tool_mfa/mfa_selector
|
||||
|
||||
This template renders the cards view for displaying available authentications to a user.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"factors": [
|
||||
{
|
||||
"label": "SMS Mobile phone",
|
||||
"name": "sms",
|
||||
"info": "Have a verification number sent to the mobile number you choose.",
|
||||
"icon": "fa-commenting-o",
|
||||
"active": true,
|
||||
"button": {
|
||||
"id": "single_button123",
|
||||
"method": "post",
|
||||
"action": "action.php",
|
||||
"label": "Setup",
|
||||
"params": [
|
||||
{
|
||||
"name": "sesskey",
|
||||
"value": "123XYZ"
|
||||
},
|
||||
{
|
||||
"name": "factor",
|
||||
"value": "sms"
|
||||
},
|
||||
{
|
||||
"name": "action",
|
||||
"value": "manage"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Authenticator app",
|
||||
"name": "totp",
|
||||
"info": "Use an authenticator app on your mobile phone.",
|
||||
"icon": "fa-mobile-screen",
|
||||
"active": false,
|
||||
"button": {
|
||||
"id": "single_button456",
|
||||
"method": "post",
|
||||
"action": "action.php",
|
||||
"label": "Setup",
|
||||
"params": [
|
||||
{
|
||||
"name": "sesskey",
|
||||
"value": "123XYZ"
|
||||
},
|
||||
{
|
||||
"name": "factor",
|
||||
"value": "totp"
|
||||
},
|
||||
{
|
||||
"name": "action",
|
||||
"value": "setup"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
<h2>{{#str}}mfa, tool_mfa{{/str}}</h2>
|
||||
<p>{{#str}}mfa:intro, tool_mfa{{/str}}</p>
|
||||
<div class="card-deck dashboard-card-deck" id="mfalist" data-region="card-deck" role="list">
|
||||
{{#factors}}
|
||||
{{>tool_mfa/mfa_card}}
|
||||
{{/factors}}
|
||||
</div>
|
7
admin/tool/mfa/upgrade.txt
Normal file
7
admin/tool/mfa/upgrade.txt
Normal file
@ -0,0 +1,7 @@
|
||||
This files describes API changes for code that uses MFA.
|
||||
|
||||
=== 4.4 ===
|
||||
* The method tool_mfa\output\renderer::setup_factor() has been deprecated. The HTML derived from this method is no longer needed.
|
||||
Similar HTML is now achieved as part of available_factors() from MFA renderer.
|
||||
* The class tool_mfa\local\form\revoke_factor_form is renamed to factor_action_confirmation_form to better suit the other actions
|
||||
it is performing (replace and revoke).
|
@ -58,7 +58,6 @@ if (!empty($action)) {
|
||||
}
|
||||
}
|
||||
|
||||
echo $OUTPUT->active_factors();
|
||||
echo $OUTPUT->available_factors();
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_mfa');
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2023100900; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2024030402; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2023100400; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_mfa'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
|
Loading…
x
Reference in New Issue
Block a user