MDL-83516 tool_mfa: Improve the factors management table
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
issueNumber: MDL-83516
|
||||
notes:
|
||||
tool_mfa:
|
||||
- message: >-
|
||||
The new factor management table uses `plugin_management_table`, so not only the functions that changed, but the file needs to be
|
||||
moved from `admin/tool/mfa/classes/local/admin_setting_managemfa.php` to `admin/tool/mfa/classes/table/admin_setting_managemfa.php`
|
||||
type: improved
|
||||
- message: >-
|
||||
The two language strings in the tool_mfa plugin, namely `inputrequired` and `setuprequired`, are deprecated.
|
||||
type: deprecated
|
||||
- message: >-
|
||||
Introduce the new language string `settings:shortdescription`, which is mandatory for each factor.
|
||||
type: improved
|
||||
@@ -1,276 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
namespace tool_mfa\local;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/ddllib.php');
|
||||
require_once($CFG->libdir.'/xmlize.php');
|
||||
require_once($CFG->libdir.'/messagelib.php');
|
||||
|
||||
/**
|
||||
* Admin setting for MFA.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @author Mikhail Golenkov <[email protected]>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class admin_setting_managemfa extends \admin_setting {
|
||||
|
||||
/**
|
||||
* Calls parent::__construct with specific arguments
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->nosave = true;
|
||||
parent::__construct('mfaui', get_string('mfasettings', 'tool_mfa'), '', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_setting(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns '' and doesn't write anything
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string Always returns ''
|
||||
*/
|
||||
public function write_setting($data): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns XHTML to display Manage MFA admin page.
|
||||
*
|
||||
* @param mixed $data Unused
|
||||
* @param string $query
|
||||
*
|
||||
* @return string highlight
|
||||
* @throws \coding_exception
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public function output_html($data, $query=''): string {
|
||||
global $OUTPUT;
|
||||
|
||||
$return = $OUTPUT->box_start('generalbox');
|
||||
$return .= $this->define_manage_mfa_table();
|
||||
$return .= $OUTPUT->box_end();
|
||||
|
||||
$return .= $OUTPUT->heading(get_string('settings:combinations', 'tool_mfa'), 3);
|
||||
$return .= $OUTPUT->box_start('generalbox');
|
||||
$return .= $this->define_factor_combinations_table();
|
||||
$return .= $OUTPUT->box_end();
|
||||
|
||||
return highlight($query, $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines main table with configurable factors.
|
||||
*
|
||||
* @return string HTML code
|
||||
* @throws \coding_exception
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public function define_manage_mfa_table() {
|
||||
global $OUTPUT;
|
||||
$sesskey = sesskey();
|
||||
|
||||
$txt = get_strings(['enable', 'disable', 'moveup', 'movedown', 'order', 'settings']);
|
||||
$txt->factor = get_string('factor', 'tool_mfa');
|
||||
$txt->weight = get_string('weight', 'tool_mfa');
|
||||
$txt->setup = get_string('setuprequired', 'tool_mfa');
|
||||
$txt->input = get_string('inputrequired', 'tool_mfa');
|
||||
|
||||
$table = new \html_table();
|
||||
$table->id = 'managemfatable';
|
||||
$table->attributes['class'] = 'admintable generaltable';
|
||||
$table->head = [
|
||||
$txt->factor,
|
||||
$txt->enable,
|
||||
$txt->order,
|
||||
$txt->weight,
|
||||
$txt->settings,
|
||||
$txt->setup,
|
||||
$txt->input,
|
||||
];
|
||||
$table->colclasses = ['leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign'];
|
||||
$table->data = [];
|
||||
|
||||
$factors = \tool_mfa\plugininfo\factor::get_factors();
|
||||
$enabledfactors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$order = 1;
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$settingsparams = ['section' => 'factor_'.$factor->name];
|
||||
$settingsurl = new \moodle_url('settings.php', $settingsparams);
|
||||
$settingslinkattrtext = get_string('editfactor', 'tool_mfa', $factor->get_display_name());
|
||||
$settingslinkattr = [
|
||||
'title' => $settingslinkattrtext,
|
||||
'aria-label' => $settingslinkattrtext,
|
||||
];
|
||||
$settingslink = \html_writer::link($settingsurl, $txt->settings, $settingslinkattr);
|
||||
|
||||
if ($factor->is_enabled()) {
|
||||
$hideshowparams = ['action' => 'disable', 'factor' => $factor->name, 'sesskey' => $sesskey];
|
||||
$hideshowurl = new \moodle_url('tool/mfa/index.php', $hideshowparams);
|
||||
$hideshowlink = \html_writer::link($hideshowurl, $OUTPUT->pix_icon('t/hide', $txt->disable));
|
||||
$class = '';
|
||||
|
||||
if ($order > 1) {
|
||||
$upparams = ['action' => 'up', 'factor' => $factor->name, 'sesskey' => $sesskey];
|
||||
$upurl = new \moodle_url('tool/mfa/index.php', $upparams);
|
||||
$uplink = \html_writer::link($upurl, $OUTPUT->pix_icon('t/up', $txt->moveup));
|
||||
} else {
|
||||
$uplink = \html_writer::link('', $uplink = $OUTPUT->spacer(['style' => 'margin-right: .5rem']));
|
||||
}
|
||||
|
||||
if ($order < count($enabledfactors)) {
|
||||
$downparams = ['action' => 'down', 'factor' => $factor->name, 'sesskey' => $sesskey];
|
||||
$downurl = new \moodle_url('tool/mfa/index.php', $downparams);
|
||||
$downlink = \html_writer::link($downurl, $OUTPUT->pix_icon('t/down', $txt->movedown));
|
||||
} else {
|
||||
$downlink = '';
|
||||
}
|
||||
$updownlink = $uplink.$downlink;
|
||||
$order++;
|
||||
} else {
|
||||
$hideshowparams = ['action' => 'enable', 'factor' => $factor->name, 'sesskey' => $sesskey];
|
||||
$hideshowurl = new \moodle_url('tool/mfa/index.php', $hideshowparams);
|
||||
$hideshowlink = \html_writer::link($hideshowurl, $OUTPUT->pix_icon('t/show', $txt->enable));
|
||||
$class = 'dimmed_text';
|
||||
$updownlink = '';
|
||||
}
|
||||
|
||||
$hassetup = $factor->has_setup() ? get_string('yes') : get_string('no');
|
||||
$hasinput = $factor->has_input() ? get_string('yes') : get_string('no');
|
||||
|
||||
$rowarray = [
|
||||
$factor->get_display_name(),
|
||||
$hideshowlink,
|
||||
$updownlink,
|
||||
$factor->get_weight(),
|
||||
$settingslink,
|
||||
$hassetup,
|
||||
$hasinput,
|
||||
];
|
||||
$row = new \html_table_row($rowarray);
|
||||
$row->attributes['class'] = $class;
|
||||
|
||||
$table->data[] = $row;
|
||||
}
|
||||
|
||||
return \html_writer::table($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines supplementary table that shows available combinations of factors enough for successful authentication.
|
||||
*
|
||||
* @return string HTML code
|
||||
*/
|
||||
public function define_factor_combinations_table() {
|
||||
global $OUTPUT;
|
||||
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$combinations = $this->get_factor_combinations($factors, 0, count($factors) - 1);
|
||||
|
||||
if (empty($combinations)) {
|
||||
return $OUTPUT->notification(get_string('error:notenoughfactors', 'tool_mfa'), 'notifyproblem');
|
||||
}
|
||||
|
||||
$txt = get_strings(['combination', 'totalweight'], 'tool_mfa');
|
||||
$table = new \html_table();
|
||||
$table->id = 'managemfatable';
|
||||
$table->attributes['class'] = 'admintable generaltable table table-bordered';
|
||||
$table->head = [$txt->combination, $txt->totalweight];
|
||||
$table->colclasses = ['leftalign', 'centeralign'];
|
||||
$table->data = [];
|
||||
|
||||
$factorstringconnector = get_string('connector', 'tool_mfa');
|
||||
foreach ($combinations as $combination) {
|
||||
$factorstrings = array_map(static function(object_factor_base $factor): string {
|
||||
return $factor->get_summary_condition() . ' <sup>' . $factor->get_weight() . '</sup>';
|
||||
}, $combination['combination']);
|
||||
|
||||
$string = implode(" {$factorstringconnector} ", $factorstrings);
|
||||
$table->data[] = new \html_table_row([$string, $combination['totalweight']]);
|
||||
}
|
||||
|
||||
return \html_writer::table($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive method to get all possible combinations of given factors.
|
||||
* Output is filtered by combination total weight (should be greater than 100).
|
||||
*
|
||||
* @param array $allfactors initial array of factor objects
|
||||
* @param int $start start position in initial array
|
||||
* @param int $end end position in initial array
|
||||
* @param int $totalweight total weight of combination
|
||||
* @param array $combination combination candidate
|
||||
* @param array $result array that includes combination total weight and subarray of factors combination
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_factor_combinations($allfactors, $start = 0, $end = 0,
|
||||
$totalweight = 0, $combination = [], $result = []) {
|
||||
|
||||
if ($totalweight >= 100) {
|
||||
// Ensure this is a valid combination before appending result.
|
||||
$valid = true;
|
||||
foreach ($combination as $factor) {
|
||||
if (!$factor->check_combination($combination)) {
|
||||
$valid = false;
|
||||
}
|
||||
}
|
||||
if ($valid) {
|
||||
$result[] = ['totalweight' => $totalweight, 'combination' => $combination];
|
||||
}
|
||||
return $result;
|
||||
} else if ($start > $end) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$combinationnext = $combination;
|
||||
$combinationnext[] = $allfactors[$start];
|
||||
|
||||
$result = $this->get_factor_combinations(
|
||||
$allfactors,
|
||||
$start + 1,
|
||||
$end,
|
||||
$totalweight + $allfactors[$start]->get_weight(),
|
||||
$combinationnext,
|
||||
$result);
|
||||
|
||||
$result = $this->get_factor_combinations(
|
||||
$allfactors,
|
||||
$start + 1,
|
||||
$end,
|
||||
$totalweight,
|
||||
$combination,
|
||||
$result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -134,6 +134,18 @@ abstract class object_factor_base implements object_factor {
|
||||
return get_string('pluginname', 'factor_'.$this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns factor short description from language string.
|
||||
*
|
||||
* Base class implementation.
|
||||
*
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_short_description(): string {
|
||||
return get_string('settings:shortdescription', 'factor_'.$this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns factor help from language string.
|
||||
*
|
||||
|
||||
@@ -126,6 +126,30 @@ class factor extends \core\plugininfo\base {
|
||||
return $return;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function enable_plugin(string $pluginname, int $enabled): bool {
|
||||
$enabledfactors = array_map(fn($f) => $f->name, self::get_enabled_factors());
|
||||
$currentlyenabled = in_array($pluginname, $enabledfactors);
|
||||
|
||||
// Determine if there's a change in the enabled status.
|
||||
if ($enabled && !$currentlyenabled) {
|
||||
$action = 'enable';
|
||||
} else if (!$enabled && $currentlyenabled) {
|
||||
$action = 'disable';
|
||||
} else {
|
||||
return false; // No change needed.
|
||||
}
|
||||
|
||||
// Execute the configuration and action based on the determined action.
|
||||
\tool_mfa\manager::set_factor_config(['enabled' => $enabled], 'factor_' . $pluginname);
|
||||
\tool_mfa\manager::do_factor_action($pluginname, $action);
|
||||
|
||||
\core\session\manager::gc(); // Remove stale sessions.
|
||||
\core_plugin_manager::reset_caches();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds active factors for a user.
|
||||
* If user is not specified, current user is used.
|
||||
@@ -320,6 +344,16 @@ class factor extends \core\plugininfo\base {
|
||||
return $this->name !== 'nosetup';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function plugintype_supports_disabling(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function plugintype_supports_ordering(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-uninstall hook.
|
||||
*
|
||||
@@ -383,4 +417,42 @@ class factor extends \core\plugininfo\base {
|
||||
|
||||
return $count > 1;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function get_sorted_plugins(bool $enabledonly = false): ?array {
|
||||
$pluginmanager = \core_plugin_manager::instance();
|
||||
$plugins = $pluginmanager->get_plugins_of_type('factor');
|
||||
$orders = self::get_factors();
|
||||
$sortedplugins = [];
|
||||
foreach ($orders as $order) {
|
||||
$sortedplugins[$order->name] = $plugins[$order->name];
|
||||
}
|
||||
|
||||
return $sortedplugins;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public static function change_plugin_order(string $pluginname, int $direction): bool {
|
||||
$activefactors = array_keys(self::get_sorted_plugins(true));
|
||||
$key = array_search($pluginname, $activefactors);
|
||||
|
||||
if ($key === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($direction === self::MOVE_DOWN && $key < (count($activefactors) - 1)) {
|
||||
$action = 'down';
|
||||
} else if ($direction === self::MOVE_UP && $key >= 1) {
|
||||
$action = 'up';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
\tool_mfa\manager::do_factor_action($pluginname, $action);
|
||||
|
||||
\core\session\manager::gc(); // Remove stale sessions.
|
||||
\core_plugin_manager::reset_caches();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
<?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/>.
|
||||
|
||||
namespace tool_mfa\table;
|
||||
|
||||
use stdClass;
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
/**
|
||||
* Admin setting for MFA.
|
||||
*
|
||||
* @package tool_mfa
|
||||
* @copyright Meirza <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class admin_setting_managemfa extends \core_admin\table\plugin_management_table {
|
||||
|
||||
#[\Override]
|
||||
protected function get_plugintype(): string {
|
||||
return 'factor';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function guess_base_url(): void {
|
||||
$this->define_baseurl(
|
||||
new \moodle_url('/admin/settings.php', ['section' => 'managemfa'])
|
||||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function get_action_url(array $params = []): \moodle_url {
|
||||
return new \moodle_url('/admin/tool/mfa/index.php', $params);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function get_column_list(): array {
|
||||
$columns = [
|
||||
'name' => get_string('factor', 'tool_mfa'),
|
||||
];
|
||||
|
||||
if ($this->supports_disabling()) {
|
||||
$columns['enabled'] = get_string('pluginenabled', 'core_plugin');
|
||||
}
|
||||
|
||||
if ($this->supports_ordering()) {
|
||||
$columns['order'] = get_string('order', 'core');
|
||||
}
|
||||
|
||||
$columns['weight'] = get_string('weight', 'tool_mfa');
|
||||
$columns['settings'] = get_string('settings', 'core');
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function col_settings(stdClass $row): string {
|
||||
if ($settingsurl = $row->plugininfo->get_settings_url()) {
|
||||
$factor = $row->plugininfo->get_factor($row->plugininfo->name);
|
||||
return \html_writer::link(
|
||||
url: $settingsurl,
|
||||
text: get_string('settings'),
|
||||
attributes: ["title" => get_string('editfactor', 'tool_mfa', $factor->get_display_name())],
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function wrap_html_finish() {
|
||||
$this->output_factor_combinations_table();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the name & short description column content.
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
protected function col_name(stdClass $row): string {
|
||||
global $OUTPUT;
|
||||
$factor = $row->plugininfo->get_factor($row->plugininfo->name);
|
||||
$params = [
|
||||
'name' => $factor->get_display_name(),
|
||||
'description' => $factor->get_short_description(),
|
||||
];
|
||||
|
||||
return $OUTPUT->render_from_template('core_admin/table/namedesc', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the weight column content.
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
protected function col_weight(stdClass $row): string {
|
||||
$factor = $row->plugininfo->get_factor($row->plugininfo->name);
|
||||
return $factor->get_weight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines supplementary table that shows available combinations of factors enough for successful authentication.
|
||||
*/
|
||||
public function output_factor_combinations_table(): void {
|
||||
global $OUTPUT;
|
||||
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$combinations = $this->get_factor_combinations($factors, 0, count($factors) - 1);
|
||||
|
||||
echo \html_writer::tag('h3', get_string('settings:combinations', 'tool_mfa'));
|
||||
|
||||
if (empty($combinations)) {
|
||||
echo $OUTPUT->notification(get_string('error:notenoughfactors', 'tool_mfa'), 'notifyproblem');
|
||||
return;
|
||||
}
|
||||
|
||||
$txt = get_strings(['combination', 'totalweight'], 'tool_mfa');
|
||||
$table = new \html_table();
|
||||
$table->id = 'mfacombinations';
|
||||
$table->attributes['class'] = 'admintable generaltable table table-bordered';
|
||||
$table->head = [$txt->combination, $txt->totalweight];
|
||||
$table->data = [];
|
||||
|
||||
$factorstringconnector = get_string('connector', 'tool_mfa');
|
||||
foreach ($combinations as $combination) {
|
||||
$factorstrings = array_map(static function(object_factor_base $factor): string {
|
||||
return $factor->get_summary_condition() . ' <sup>' . $factor->get_weight() . '</sup>';
|
||||
}, $combination['combination']);
|
||||
|
||||
$string = implode(" {$factorstringconnector} ", $factorstrings);
|
||||
$table->data[] = new \html_table_row([$string, $combination['totalweight']]);
|
||||
}
|
||||
|
||||
echo \html_writer::table($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive method to get all possible combinations of given factors.
|
||||
* Output is filtered by combination total weight (should be greater than 100).
|
||||
*
|
||||
* @param array $allfactors initial array of factor objects
|
||||
* @param int $start start position in initial array
|
||||
* @param int $end end position in initial array
|
||||
* @param int $totalweight total weight of combination
|
||||
* @param array $combination combination candidate
|
||||
* @param array $result array that includes combination total weight and subarray of factors combination
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_factor_combinations($allfactors, $start = 0, $end = 0,
|
||||
$totalweight = 0, $combination = [], $result = []): array {
|
||||
|
||||
if ($totalweight >= 100) {
|
||||
// Ensure this is a valid combination before appending result.
|
||||
$valid = true;
|
||||
foreach ($combination as $factor) {
|
||||
if (!$factor->check_combination($combination)) {
|
||||
$valid = false;
|
||||
}
|
||||
}
|
||||
if ($valid) {
|
||||
$result[] = ['totalweight' => $totalweight, 'combination' => $combination];
|
||||
}
|
||||
return $result;
|
||||
} else if ($start > $end) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$combinationnext = $combination;
|
||||
$combinationnext[] = $allfactors[$start];
|
||||
|
||||
$result = $this->get_factor_combinations(
|
||||
allfactors: $allfactors,
|
||||
start: $start + 1,
|
||||
end: $end,
|
||||
totalweight: $totalweight + $allfactors[$start]->get_weight(),
|
||||
combination: $combinationnext,
|
||||
result: $result,
|
||||
);
|
||||
|
||||
$result = $this->get_factor_combinations(
|
||||
allfactors: $allfactors,
|
||||
start: $start + 1,
|
||||
end: $end,
|
||||
totalweight: $totalweight,
|
||||
combination: $combination,
|
||||
result: $result,
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -26,5 +26,6 @@
|
||||
$string['info'] = 'This factor allows for NOT being an administrator to count as a factor. Its intended use is to ensure administators require tighter security, so regular users get the weight for free, while admins must use other factors.';
|
||||
$string['pluginname'] = 'Non-administrator';
|
||||
$string['privacy:metadata'] = 'The Non-administrator factor plugin does not store any personal data.';
|
||||
$string['settings:shortdescription'] = 'Require additional authentication factors for administrators.';
|
||||
$string['settings:weight_help'] = 'Weight is given to regular users for this factor, so admins must have more factors than a regular user to pass.';
|
||||
$string['summarycondition'] = 'is not an admin';
|
||||
|
||||
@@ -28,4 +28,5 @@ $string['pluginname'] = 'Authentication type';
|
||||
$string['privacy:metadata'] = 'The Authentication type factor plugin does not store any personal data.';
|
||||
$string['settings:goodauth'] = 'Factor authentication types';
|
||||
$string['settings:goodauth_help'] = 'Select all authentication types to use as a factor for MFA. Any types not selected will not be treated as a FAIL in MFA.';
|
||||
$string['settings:shortdescription'] = 'Allow users to bypass extra authentication steps based on their authentication type.';
|
||||
$string['summarycondition'] = 'has an authentication type of {$a}';
|
||||
|
||||
@@ -30,4 +30,5 @@ $string['settings:adminpasses'] = 'Site admins can pass this factor';
|
||||
$string['settings:adminpasses_help'] = 'By default admins pass all capability checks, including this one which uses \'factor/capability:cannotpassfactor\', which means they will fail this factor.
|
||||
If checked then all site admins will pass this factor if they do not have this capability from another role.
|
||||
If unchecked site admins will fail this factor.';
|
||||
$string['settings:shortdescription'] = 'Specify which users must use other factors to authenticate. Must be combined with other factors.';
|
||||
$string['summarycondition'] = 'does NOT have the factor/capability:cannotpassfactor capability in any role including site administrator.';
|
||||
|
||||
@@ -27,4 +27,5 @@ $string['pluginname'] = 'Cohort';
|
||||
$string['privacy:metadata'] = 'The Cohort factor plugin does not store any personal data.';
|
||||
$string['settings:cohort'] = 'Non-passing cohorts';
|
||||
$string['settings:cohort_help'] = 'Select the cohorts that will not pass this factor. This allows you to force these cohorts to use other factors to authenticate.';
|
||||
$string['settings:shortdescription'] = 'Specify which cohorts of users must use other factors to authenticate. Must be combined with other factors.';
|
||||
$string['summarycondition'] = 'does NOT have any of the following cohorts assigned in any context: {$a}';
|
||||
|
||||
@@ -55,6 +55,7 @@ $string['pluginname'] = 'Email';
|
||||
$string['privacy:metadata'] = 'The Email factor plugin does not store any personal data';
|
||||
$string['settings:duration'] = 'Validity duration';
|
||||
$string['settings:duration_help'] = 'The period of time that the code is valid.';
|
||||
$string['settings:shortdescription'] = 'Require users to enter a code received via email during login.';
|
||||
$string['settings:suspend'] = 'Suspend unauthorised accounts';
|
||||
$string['settings:suspend_help'] = 'Check this to suspend user accounts if an unauthorised email verification is received.';
|
||||
$string['setupfactor'] = 'Set up email';
|
||||
|
||||
@@ -37,5 +37,6 @@ $string['settings:graceperiod'] = 'Grace period';
|
||||
$string['settings:graceperiod_help'] = 'Period of time when users can access the site without configured and enabled factors.';
|
||||
$string['settings:ignorelist'] = 'Ignored factors';
|
||||
$string['settings:ignorelist_help'] = 'Grace period will not give points if there are other factors that users can use to authenticate with multi-factor authentication. Any factors here will not be counted by Grace period when deciding whether to give points. This can allow Grace period to allow authentication if another factor like email, has configuration or system issues.';
|
||||
$string['settings:shortdescription'] = 'Allow users to log in without MFA for a specified period, giving them time to set up authentication factors.';
|
||||
$string['setupfactors'] = 'You are currently in the grace period, and may not have enough factors set up to log in once the grace period expires. Go to {$a->url} to check your authentication status and set up more authentication factors. Your grace period expires in {$a->time}.';
|
||||
$string['summarycondition'] = 'is within grace period';
|
||||
|
||||
@@ -30,4 +30,5 @@ $string['pluginname'] = 'IP range';
|
||||
$string['privacy:metadata'] = 'The IP range factor plugin does not store any personal data.';
|
||||
$string['settings:safeips'] = 'Safe IP ranges';
|
||||
$string['settings:safeips_help'] = 'Enter a list of IP addresses or subnets to be counted as a pass in factor. If empty nobody will pass this factor. {$a->info} {$a->syntax}';
|
||||
$string['settings:shortdescription'] = 'Use IP addresses to automatically verify users\' identity.';
|
||||
$string['summarycondition'] = 'is on a secured network';
|
||||
|
||||
@@ -27,4 +27,5 @@ $string['deleteunusablefactors'] = 'Delete unusable Nosetup factors';
|
||||
$string['info'] = 'This factor passes if the user has no other factors set up.';
|
||||
$string['pluginname'] = 'No other factors';
|
||||
$string['privacy:metadata'] = 'The No other factors plugin does not store any personal data';
|
||||
$string['settings:shortdescription'] = 'Allow users to bypass MFA if they have not set up any other authentication factor.';
|
||||
$string['summarycondition'] = 'has no other factors set up';
|
||||
|
||||
@@ -27,4 +27,5 @@ $string['pluginname'] = 'Role';
|
||||
$string['privacy:metadata'] = 'The Role factor plugin does not store any personal data.';
|
||||
$string['settings:roles'] = 'Non-passing roles';
|
||||
$string['settings:roles_help'] = 'Select the roles that will not pass this factor. This allows you to force these roles to use other factors to authenticate.';
|
||||
$string['settings:shortdescription'] = 'Specify which users must use other factors to authenticate, based on their role. Must be combined with other factors.';
|
||||
$string['summarycondition'] = 'does NOT have any of the following roles assigned in any context: {$a}';
|
||||
|
||||
@@ -56,6 +56,7 @@ $string['settings:heading'] = 'Users will receive an SMS with 6-digit code durin
|
||||
|
||||
Users will need to register their mobile phone number first.';
|
||||
$string['settings:setupdesc'] = '<br><br>To use SMS as an authentication factor, you first need to <a href="{$a}">set up an SMS gateway</a>.';
|
||||
$string['settings:shortdescription'] = 'Require users to enter a code received via SMS during login.';
|
||||
$string['settings:smsgateway'] = 'SMS gateway';
|
||||
$string['settings:smsgateway_help'] = 'Select a gateway from the list, or <a href="{$a}">create a new gateway</a>.';
|
||||
$string['setupfactor'] = 'Set up SMS';
|
||||
|
||||
@@ -31,5 +31,6 @@ $string['settings:expireovernight'] = 'Expire trust overnight';
|
||||
$string['settings:expireovernight_help'] = 'This forces tokens to expire overnight, preventing midday interruptions for users. Instead they will be asked to multi-factor authenticate at the start of a day after expiry.';
|
||||
$string['settings:expiry'] = 'Trust duration';
|
||||
$string['settings:expiry_help'] = 'The duration a device is trusted before requiring a new multi-factor authentication.';
|
||||
$string['settings:shortdescription'] = 'Allow users to bypass authentication on devices marked as trusted. Needs to be combined with other factors.';
|
||||
$string['summarycondition'] = 'the user has previously trusted this device';
|
||||
$string['tokenstoredindevice'] = 'The user with ID {$a->userid} has a multi-factor authentication token stored on their device. <br> Information: {$a->string}.';
|
||||
|
||||
@@ -51,6 +51,7 @@ $string['privacy:metadata'] = 'The Authenticator app factor plugin does not stor
|
||||
$string['replacefactor'] = 'Replace authenticator app';
|
||||
$string['replacefactorconfirmation'] = 'Replace \'{$a}\' authenticator app?';
|
||||
$string['revokefactorconfirmation'] = 'Remove \'{$a}\' authenticator app?';
|
||||
$string['settings:shortdescription'] = 'Require users to enter a code from an authenticator app on their devices during login.';
|
||||
$string['settings:totplink'] = 'Show mobile app setup link';
|
||||
$string['settings:totplink_help'] = 'If enabled the user will see a 3rd setup option with a direct otpauth:// link';
|
||||
$string['settings:window'] = 'TOTP verification window';
|
||||
|
||||
@@ -49,6 +49,7 @@ $string['replacefactorconfirmation'] = 'Replace \'{$a}\' security key?';
|
||||
$string['revokefactorconfirmation'] = 'Remove \'{$a}\' security key?';
|
||||
$string['settings:authenticatortypes'] = 'Types of authenticator';
|
||||
$string['settings:authenticatortypes_help'] = 'Toggle certain types of authenticators';
|
||||
$string['settings:shortdescription'] = 'Require users to use a security key, like a USB or NFC token, or a biometric method, during login.';
|
||||
$string['settings:userverification'] = 'User verification';
|
||||
$string['settings:userverification_help'] = 'Serves to ensure the person authenticating is in fact who they say they are. User verification can take various forms, such as password, PIN, fingerprint, etc.';
|
||||
$string['setupfactor'] = 'Set up security key';
|
||||
|
||||
@@ -36,7 +36,7 @@ $returnurl = get_local_referer(false);
|
||||
$PAGE->set_url('/admin/tool/mfa/index.php');
|
||||
|
||||
$action = optional_param('action', '', PARAM_ALPHANUMEXT);
|
||||
$factor = optional_param('factor', '', PARAM_ALPHANUMEXT);
|
||||
$factor = optional_param('plugin', '', PARAM_ALPHANUMEXT);
|
||||
|
||||
if (empty($factor) || !\tool_mfa\plugininfo\factor::factor_exists($factor)) {
|
||||
throw new moodle_exception('factornotfound', 'tool_mfa', $returnurl, $factor);
|
||||
@@ -48,42 +48,20 @@ if (empty($action) || !in_array($action, \tool_mfa\plugininfo\factor::get_factor
|
||||
|
||||
require_sesskey();
|
||||
|
||||
$enabledfactors = [];
|
||||
foreach (\tool_mfa\plugininfo\factor::get_enabled_factors() as $enabledfactor) {
|
||||
$enabledfactors[] = $enabledfactor->name;
|
||||
}
|
||||
|
||||
$class = \core_plugin_manager::resolve_plugininfo_class('factor');
|
||||
|
||||
switch ($action) {
|
||||
case 'disable':
|
||||
if (in_array($factor, $enabledfactors)) {
|
||||
\tool_mfa\manager::set_factor_config(['enabled' => 0], 'factor_' . $factor);
|
||||
\tool_mfa\manager::do_factor_action($factor, $action);
|
||||
|
||||
\core\session\manager::gc(); // Remove stale sessions.
|
||||
core_plugin_manager::reset_caches();
|
||||
}
|
||||
$class::enable_plugin($factor, 0);
|
||||
break;
|
||||
|
||||
case 'enable':
|
||||
if (!in_array($factor, $enabledfactors)) {
|
||||
\tool_mfa\manager::set_factor_config(['enabled' => 1], 'factor_' . $factor);
|
||||
\tool_mfa\manager::do_factor_action($factor, $action);
|
||||
|
||||
\core\session\manager::gc(); // Remove stale sessions.
|
||||
core_plugin_manager::reset_caches();
|
||||
}
|
||||
$class::enable_plugin($factor, 1);
|
||||
break;
|
||||
|
||||
case 'up':
|
||||
case 'down':
|
||||
\tool_mfa\manager::do_factor_action($factor, $action);
|
||||
|
||||
\core\session\manager::gc(); // Remove stale sessions.
|
||||
core_plugin_manager::reset_caches();
|
||||
$class::change_plugin_order($factor, $class::MOVE_UP);
|
||||
break;
|
||||
|
||||
default:
|
||||
case 'down':
|
||||
$class::change_plugin_order($factor, $class::MOVE_DOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,3 +3,5 @@ createdfromip,tool_mfa
|
||||
lastverified,tool_mfa
|
||||
revoke,tool_mfa
|
||||
setup,tool_mfa
|
||||
inputrequired,tool_mfa
|
||||
setuprequired,tool_mfa
|
||||
|
||||
@@ -71,7 +71,6 @@ $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['lastused'] = 'Last used';
|
||||
$string['locked'] = '{$a} (Unavailable)';
|
||||
@@ -151,7 +150,6 @@ $string['settings:redir_exclusions_help'] = 'Each new line is a relative URL fro
|
||||
$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['setupfactor'] = 'Set up factor';
|
||||
$string['setuprequired'] = 'User setup';
|
||||
$string['state:fail'] = 'Fail';
|
||||
$string['state:locked'] = 'Locked';
|
||||
$string['state:neutral'] = 'Neutral';
|
||||
@@ -180,3 +178,7 @@ $string['createdfromip'] = 'Created from IP';
|
||||
$string['lastverified'] = 'Last verified';
|
||||
$string['revoke'] = 'Revoke';
|
||||
$string['setup'] = 'Setup';
|
||||
|
||||
// Deprecated since Moodle 5.0.
|
||||
$string['inputrequired'] = 'User input';
|
||||
$string['setuprequired'] = 'User setup';
|
||||
|
||||
@@ -32,7 +32,12 @@ if ($hassiteconfig) {
|
||||
new moodle_url('/admin/tool/mfa/reset_factor.php')));
|
||||
|
||||
$settings = new admin_settingpage('managemfa', new lang_string('mfasettings', 'tool_mfa'));
|
||||
$settings->add(new \tool_mfa\local\admin_setting_managemfa());
|
||||
$settings->add(new \core_admin\admin\admin_setting_plugin_manager(
|
||||
plugintype: 'factor',
|
||||
tableclass: \tool_mfa\table\admin_setting_managemfa::class,
|
||||
name: 'managemfatable',
|
||||
visiblename: new lang_string('manageaiproviders', 'core_ai'),
|
||||
));
|
||||
|
||||
$heading = new lang_string('settings:general', 'tool_mfa');
|
||||
$settings->add(new admin_setting_heading('tool_mfa/settings', $heading, ''));
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_mfa;
|
||||
use tool_mfa\tool_mfa_trait;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once(__DIR__ . '/tool_mfa_trait.php');
|
||||
|
||||
/**
|
||||
* Tests for MFA admin settings
|
||||
@@ -28,18 +24,18 @@ require_once(__DIR__ . '/tool_mfa_trait.php');
|
||||
* @author Peter Burnett <[email protected]>
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*
|
||||
* @covers \tool_mfa\table\admin_setting_managemfa
|
||||
*/
|
||||
final class admin_setting_managemfa_test extends \advanced_testcase {
|
||||
|
||||
use tool_mfa_trait;
|
||||
use \tool_mfa\tests\mfa_settings_trait;
|
||||
|
||||
/**
|
||||
* Tests getting the factor combinations
|
||||
*
|
||||
* @covers ::get_factor_combinations
|
||||
*/
|
||||
public function test_get_factor_combinations_default(): void {
|
||||
$namagemfa = new \tool_mfa\local\admin_setting_managemfa();
|
||||
$namagemfa = new \tool_mfa\table\admin_setting_managemfa();
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$combinations = $namagemfa->get_factor_combinations($factors, 0, count($factors) - 1);
|
||||
$this->assertEquals(0, count($factors));
|
||||
@@ -136,12 +132,11 @@ final class admin_setting_managemfa_test extends \advanced_testcase {
|
||||
/**
|
||||
* Tests getting the factor combinations with data provider
|
||||
*
|
||||
* @covers ::get_factor_combinations
|
||||
* @dataProvider get_factor_combinations_provider
|
||||
* @param array $factorset configured factors
|
||||
* @param int $combinationscount expected count of available combinations
|
||||
*/
|
||||
public function test_get_factor_combinations_with_data_provider($factorset, $combinationscount): void {
|
||||
public function test_get_factor_combinations_with_data_provider(array $factorset, int $combinationscount): void {
|
||||
$this->resetAfterTest();
|
||||
$enabledcount = 0;
|
||||
|
||||
@@ -152,7 +147,7 @@ final class admin_setting_managemfa_test extends \advanced_testcase {
|
||||
}
|
||||
}
|
||||
|
||||
$managemfa = new \tool_mfa\local\admin_setting_managemfa();
|
||||
$managemfa = new \tool_mfa\table\admin_setting_managemfa();
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$combinations = $managemfa->get_factor_combinations($factors, 0, count($factors) - 1);
|
||||
|
||||
@@ -171,12 +166,10 @@ final class admin_setting_managemfa_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Tests checking the factor combinations
|
||||
*
|
||||
* @covers ::get_factor_combinations
|
||||
*/
|
||||
public function test_factor_combination_checker(): void {
|
||||
$this->resetAfterTest();
|
||||
$managemfa = new \tool_mfa\local\admin_setting_managemfa();
|
||||
$managemfa = new \tool_mfa\table\admin_setting_managemfa();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
@tool @tool_mfa
|
||||
Feature: Manage factor plugins
|
||||
In order to manage different factors in MFA
|
||||
As an administrator
|
||||
I need to enable/disable or change the order of the factor plugins from MFA management page
|
||||
|
||||
@javascript
|
||||
Scenario: Administrators can manage factor plugins from MFA managements page
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Admin tools > Multi-factor authentication > Manage multi-factor authentication" in site administration
|
||||
# Enable and disable Factor.
|
||||
When I toggle the "Enable Trust this device" admin switch "on"
|
||||
And I should see "Trust this device enabled."
|
||||
And I should see "Disable Trust this device" in the "Trust this device" "table_row"
|
||||
And I reload the page
|
||||
And I should see "Disable Trust this device"
|
||||
And I toggle the "Disable Trust this device" admin switch "off"
|
||||
And I should see "Trust this device disabled."
|
||||
And I should see "Enable Trust this device" in the "Trust this device" "table_row"
|
||||
# Ordering Factors.
|
||||
Then I toggle the "Enable Trust this device" admin switch "on"
|
||||
And I toggle the "Enable Grace period" admin switch "on"
|
||||
And I click on "Move up" "link" in the "Grace period" "table_row"
|
||||
And "Grace period" "table_row" should appear before "Trust this device" "table_row"
|
||||
And I click on "Move down" "link" in the "Grace period" "table_row"
|
||||
And "Grace period" "table_row" should appear after "Trust this device" "table_row"
|
||||
+3
-3
@@ -14,13 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_mfa;
|
||||
namespace tool_mfa\tests;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once(__DIR__ . '/../lib.php');
|
||||
require_once(__DIR__ . '../../../lib.php');
|
||||
|
||||
/**
|
||||
* Trait for testing this plugin
|
||||
@@ -31,7 +31,7 @@ require_once(__DIR__ . '/../lib.php');
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
trait tool_mfa_trait {
|
||||
trait mfa_settings_trait {
|
||||
|
||||
/**
|
||||
* Sets the state of the factor, in particular the weight and whether it is enabled
|
||||
@@ -15,10 +15,6 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_mfa;
|
||||
use tool_mfa\tool_mfa_trait;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once(__DIR__ . '/tool_mfa_trait.php');
|
||||
|
||||
/**
|
||||
* Tests for MFA manager class.
|
||||
@@ -30,7 +26,7 @@ require_once(__DIR__ . '/tool_mfa_trait.php');
|
||||
*/
|
||||
final class manager_test extends \advanced_testcase {
|
||||
|
||||
use tool_mfa_trait;
|
||||
use \tool_mfa\tests\mfa_settings_trait;
|
||||
|
||||
/**
|
||||
* Tests getting the factor total weight
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_mfa;
|
||||
use tool_mfa\tool_mfa_trait;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once(__DIR__ . '/tool_mfa_trait.php');
|
||||
|
||||
/**
|
||||
* Tests for base factor implementation methods.
|
||||
@@ -30,7 +26,7 @@ require_once(__DIR__ . '/tool_mfa_trait.php');
|
||||
*/
|
||||
final class object_factor_base_test extends \advanced_testcase {
|
||||
|
||||
use tool_mfa_trait;
|
||||
use \tool_mfa\tests\mfa_settings_trait;
|
||||
|
||||
/**
|
||||
* Test deleting user's configured factors
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2024100700; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2024121800; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2024100100; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_mfa'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
|
||||
Reference in New Issue
Block a user