Merge branch 'master_MDL-78534' of https://github.com/mattporritt/moodle
This commit is contained in:
@@ -1,10 +1,3 @@
|
||||
/**
|
||||
* Module to autosubmit the verification code element when it reaches 6 characters.
|
||||
*
|
||||
* @module tool_mfa/autosubmit_verification_code
|
||||
* @copyright 2020 Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("tool_mfa/autosubmit_verification_code",[],(function(){return{init:function(){document.querySelector("#id_verificationcode").addEventListener("keyup",(function(){6==this.value.length&&this.closest("form").submit()}))}}}));
|
||||
define("tool_mfa/autosubmit_verification_code",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;_exports.init=()=>{const codeInput=document.querySelector("#id_verificationcode"),codeForm=codeInput.closest("form"),submitButton=codeForm.querySelector("#id_submitbutton");codeInput.addEventListener("keyup",(function(){this.value.length>=6&&codeForm.submit()})),codeInput.disabled&&(submitButton.disabled=!0)}}));
|
||||
|
||||
//# sourceMappingURL=autosubmit_verification_code.min.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"autosubmit_verification_code.min.js","sources":["../src/autosubmit_verification_code.js"],"sourcesContent":["\n// 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 * Module to autosubmit the verification code element when it reaches 6 characters.\n *\n * @module tool_mfa/autosubmit_verification_code\n * @copyright 2020 Peter Burnett <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([], function() {\n return {\n init: function() {\n document.querySelector(\"#id_verificationcode\").addEventListener('keyup', function() {\n if (this.value.length == 6) {\n // Submits the closes form (parent).\n this.closest(\"form\").submit();\n }\n });\n }\n };\n});\n"],"names":["define","init","document","querySelector","addEventListener","this","value","length","closest","submit"],"mappings":";;;;;;;AAwBAA,+CAAO,IAAI,iBACA,CACHC,KAAM,WACFC,SAASC,cAAc,wBAAwBC,iBAAiB,SAAS,WAC5C,GAArBC,KAAKC,MAAMC,aAENC,QAAQ,QAAQC"}
|
||||
{"version":3,"file":"autosubmit_verification_code.min.js","sources":["../src/autosubmit_verification_code.js"],"sourcesContent":["\n// 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 * Module to autosubmit the verification code element when it reaches 6 characters.\n *\n * @module tool_mfa/autosubmit_verification_code\n * @copyright 2020 Peter Burnett <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const init = () => {\n const codeInput = document.querySelector(\"#id_verificationcode\");\n const codeForm = codeInput.closest(\"form\");\n const submitButton = codeForm.querySelector(\"#id_submitbutton\");\n\n // Event listener for code input field.\n codeInput.addEventListener('keyup', function() {\n if (this.value.length >= 6) {\n // Submits the closes form (parent).\n codeForm.submit();\n }\n });\n\n // Disable the submit button if the input field is disabled.\n // This occurs if there are no more attempts left for the factor.\n if (codeInput.disabled) {\n submitButton.disabled = true;\n }\n};\n"],"names":["codeInput","document","querySelector","codeForm","closest","submitButton","addEventListener","this","value","length","submit","disabled"],"mappings":"0KAwBoB,WACVA,UAAYC,SAASC,cAAc,wBACnCC,SAAWH,UAAUI,QAAQ,QAC7BC,aAAeF,SAASD,cAAc,oBAG5CF,UAAUM,iBAAiB,SAAS,WAC5BC,KAAKC,MAAMC,QAAU,GAErBN,SAASO,YAMbV,UAAUW,WACVN,aAAaM,UAAW"}
|
||||
@@ -22,15 +22,22 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define([], function() {
|
||||
return {
|
||||
init: function() {
|
||||
document.querySelector("#id_verificationcode").addEventListener('keyup', function() {
|
||||
if (this.value.length == 6) {
|
||||
// Submits the closes form (parent).
|
||||
this.closest("form").submit();
|
||||
}
|
||||
});
|
||||
export const init = () => {
|
||||
const codeInput = document.querySelector("#id_verificationcode");
|
||||
const codeForm = codeInput.closest("form");
|
||||
const submitButton = codeForm.querySelector("#id_submitbutton");
|
||||
|
||||
// Event listener for code input field.
|
||||
codeInput.addEventListener('keyup', function() {
|
||||
if (this.value.length >= 6) {
|
||||
// Submits the closes form (parent).
|
||||
codeForm.submit();
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Disable the submit button if the input field is disabled.
|
||||
// This occurs if there are no more attempts left for the factor.
|
||||
if (codeInput.disabled) {
|
||||
submitButton.disabled = true;
|
||||
}
|
||||
};
|
||||
|
||||
+32
-25
@@ -27,18 +27,20 @@ require_once($CFG->dirroot . '/admin/tool/mfa/lib.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
|
||||
use tool_mfa\local\form\login_form;
|
||||
use tool_mfa\manager;
|
||||
use tool_mfa\plugininfo\factor;
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$context = context_user::instance($USER->id);
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url('/admin/tool/mfa/auth.php');
|
||||
$PAGE->set_pagelayout('secure');
|
||||
$PAGE->set_pagelayout('login');
|
||||
$PAGE->blocks->show_only_fake_blocks();
|
||||
$pagetitle = $SITE->shortname.': '.get_string('mfa', 'tool_mfa');
|
||||
$PAGE->set_title($pagetitle);
|
||||
|
||||
// The only page action allowed here is a logout if it was requested.
|
||||
// Logout if it was requested.
|
||||
$logout = optional_param('logout', false, PARAM_BOOL);
|
||||
if ($logout) {
|
||||
if (!empty($SESSION->wantsurl)) {
|
||||
@@ -49,62 +51,67 @@ if ($logout) {
|
||||
$wantsurl = new \moodle_url($CFG->wwwroot);
|
||||
}
|
||||
|
||||
\tool_mfa\manager::mfa_logout();
|
||||
manager::mfa_logout();
|
||||
redirect($wantsurl);
|
||||
}
|
||||
|
||||
$currenturl = new moodle_url('/admin/tool/mfa/auth.php');
|
||||
|
||||
// Perform state check.
|
||||
\tool_mfa\manager::resolve_mfa_status();
|
||||
manager::resolve_mfa_status();
|
||||
|
||||
// We have a valid landing here, before doing any actions, clear any redir loop progress.
|
||||
\tool_mfa\manager::clear_redirect_counter();
|
||||
manager::clear_redirect_counter();
|
||||
|
||||
// If a specific factor was requested, use it.
|
||||
$pickedname = optional_param('factorname', false, PARAM_ALPHA);
|
||||
$pickedfactor = factor::get_factor($pickedname);
|
||||
$formfactor = optional_param('factor', false, PARAM_ALPHA);
|
||||
|
||||
if ($pickedfactor && $pickedfactor->has_input() && $pickedfactor->get_state() == factor::STATE_UNKNOWN) {
|
||||
$factor = $pickedfactor;
|
||||
} else if ($formfactor) {
|
||||
// Check if a factor was supplied by the form, such as for a form submission.
|
||||
$factor = factor::get_factor($formfactor);
|
||||
} else {
|
||||
// Else, get the next factor that requires input.
|
||||
$factor = factor::get_next_user_login_factor();
|
||||
}
|
||||
|
||||
$factor = \tool_mfa\plugininfo\factor::get_next_user_factor();
|
||||
// If ok, perform form actions for input factor.
|
||||
$form = new login_form($currenturl, ['factor' => $factor]);
|
||||
$form = new login_form($currenturl, ['factor' => $factor], 'post', '', ['class' => 'ignoredirty']);
|
||||
if ($form->is_submitted()) {
|
||||
if (!$form->is_validated() && !$form->is_cancelled()) {
|
||||
// Increment the fail counter for the factor,
|
||||
// And let the factor handle locking logic.
|
||||
$factor->increment_lock_counter();
|
||||
\tool_mfa\manager::resolve_mfa_status(false);
|
||||
manager::resolve_mfa_status(false);
|
||||
} else {
|
||||
// Set state from user actions.
|
||||
if ($form->is_cancelled()) {
|
||||
$factor->process_cancel_action();
|
||||
// Move to next factor.
|
||||
\tool_mfa\manager::resolve_mfa_status(true);
|
||||
manager::resolve_mfa_status(true);
|
||||
} else {
|
||||
if ($data = $form->get_data()) {
|
||||
// Validation has passed, so before processing, lets action the global form submissions as well.
|
||||
$form->globalmanager->submit($data);
|
||||
|
||||
// Did user submit something that causes a fail state?
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
\tool_mfa\manager::resolve_mfa_status(true);
|
||||
if ($factor->get_state() == factor::STATE_FAIL) {
|
||||
manager::resolve_mfa_status(true);
|
||||
}
|
||||
|
||||
$factor->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
|
||||
$factor->set_state(factor::STATE_PASS);
|
||||
// Move to next factor.
|
||||
\tool_mfa\manager::resolve_mfa_status(true);
|
||||
manager::resolve_mfa_status(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_mfa');
|
||||
echo $OUTPUT->header();
|
||||
|
||||
\tool_mfa\manager::display_debug_notification();
|
||||
|
||||
echo $OUTPUT->heading(get_string('pluginname', 'factor_'.$factor->name));
|
||||
// Check if a notification is required for factor lockouts.
|
||||
$remattempts = $factor->get_remaining_attempts();
|
||||
if ($remattempts < get_config('tool_mfa', 'lockout')) {
|
||||
echo $OUTPUT->notification(get_string('lockoutnotification', 'tool_mfa', $remattempts), 'notifyerror');
|
||||
}
|
||||
$form->display();
|
||||
|
||||
echo $renderer->guide_link();
|
||||
manager::display_debug_notification();
|
||||
echo $renderer->verification_form($factor, $form);
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -153,7 +153,7 @@ interface object_factor {
|
||||
* Marks factor record as revoked.
|
||||
* If factorid is not provided, revoke all instances of factor.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @param int|null $factorid
|
||||
* @return bool
|
||||
*/
|
||||
public function revoke_user_factor(?int $factorid = null): bool;
|
||||
@@ -162,7 +162,7 @@ interface object_factor {
|
||||
* When validation code is correct - update lastverified field for given factor.
|
||||
* If factor id is not provided, update all factor entries for user.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @param int|null $factorid
|
||||
* @return bool|\dml_exception
|
||||
*/
|
||||
public function update_lastverified(?int $factorid = null): bool|\dml_exception;
|
||||
|
||||
@@ -41,6 +41,9 @@ abstract class object_factor_base implements object_factor {
|
||||
*/
|
||||
protected $secretmanager;
|
||||
|
||||
/** @var string Factor icon */
|
||||
protected $icon = 'fa-lock';
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
@@ -276,8 +279,9 @@ abstract class object_factor_base implements object_factor {
|
||||
* Marks factor record as revoked.
|
||||
* If factorid is not provided, revoke all instances of factor.
|
||||
*
|
||||
* @param int $factorid
|
||||
* @param int|null $factorid
|
||||
* @return bool
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function revoke_user_factor(?int $factorid = null): bool {
|
||||
@@ -309,7 +313,8 @@ abstract class object_factor_base implements object_factor {
|
||||
/**
|
||||
* When validation code is correct - update lastverified field for given factor.
|
||||
* If factor id is not provided, update all factor entries for user.
|
||||
* @param int $factorid
|
||||
*
|
||||
* @param int|null $factorid
|
||||
* @return bool|\dml_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
@@ -404,7 +409,7 @@ abstract class object_factor_base implements object_factor {
|
||||
* Implementation for factors that require input.
|
||||
* Should be overridden in child classes with no input.
|
||||
*
|
||||
* @param mixed $state the state constant to set
|
||||
* @param string $state the state constant to set.
|
||||
* @return bool
|
||||
*/
|
||||
public function set_state(string $state): bool {
|
||||
@@ -551,9 +556,6 @@ abstract class object_factor_base implements object_factor {
|
||||
$lockthreshold = get_config('tool_mfa', 'lockout');
|
||||
if ($this->lockcounter >= $lockthreshold) {
|
||||
$this->set_state(\tool_mfa\plugininfo\factor::STATE_LOCKED);
|
||||
|
||||
// Lastly output a notification showing the user the factor is locked.
|
||||
\core\notification::error(get_string('factorlocked', 'tool_mfa', $this->get_display_name()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,4 +623,23 @@ abstract class object_factor_base implements object_factor {
|
||||
public function global_submit(object $data): void {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon associated with this factor.
|
||||
*
|
||||
* @return string the icon name.
|
||||
*/
|
||||
public function get_icon(): string {
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login description associated with this factor.
|
||||
* Override for factors that have a user input.
|
||||
*
|
||||
* @return string The login option.
|
||||
*/
|
||||
public function get_login_desc(): string {
|
||||
return get_string('logindesc', 'factor_'.$this->name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ class login_form extends \moodleform {
|
||||
$mform = $this->_form;
|
||||
$factor = $this->_customdata['factor'];
|
||||
$mform = $factor->login_form_definition($mform);
|
||||
// Add a hidden field with the factor name so it is always available.
|
||||
$factorname = $mform->addElement('hidden', 'factor', $factor->name);
|
||||
$factorname->setType(PARAM_ALPHAEXT);
|
||||
$this->globalmanager->definition($mform);
|
||||
}
|
||||
|
||||
@@ -86,7 +89,6 @@ class login_form extends \moodleform {
|
||||
|
||||
$buttonarray = [];
|
||||
$buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('loginsubmit', 'factor_' . $factor->name));
|
||||
$buttonarray[] = &$mform->createElement('cancel', '', get_string('loginskip', 'factor_' . $factor->name));
|
||||
$mform->addGroup($buttonarray, 'buttonar', '', [' '], false);
|
||||
$mform->closeHeaderBefore('buttonar');
|
||||
}
|
||||
@@ -110,4 +112,45 @@ class login_form extends \moodleform {
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns error corresponding to validated element.
|
||||
*
|
||||
* @param string $elementname Name of form element to check.
|
||||
* @return string|null Error message corresponding to the validated element.
|
||||
*/
|
||||
public function get_element_error(string $elementname): ?string {
|
||||
return $this->_form->getElementError($elementname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an error message for a form element.
|
||||
*
|
||||
* @param string $elementname Name of form element to set error for.
|
||||
* @param string $error Error message, if empty then removes the current error message.
|
||||
* @return void
|
||||
*/
|
||||
public function set_element_error(string $elementname, string $error): void {
|
||||
$this->_form->setElementError($elementname, $error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a form element.
|
||||
*
|
||||
* @param string $elementname Name of form element to freeze.
|
||||
* @return void
|
||||
*/
|
||||
public function freeze(string $elementname): void {
|
||||
$this->_form->freeze($elementname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the form element exists.
|
||||
*
|
||||
* @param string $elementname Name of form element to check.
|
||||
* @return bool
|
||||
*/
|
||||
public function element_exists(string $elementname): bool {
|
||||
return $this->_form->elementExists($elementname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ class verification_field extends \MoodleQuickForm_text {
|
||||
$attributes['autocomplete'] = 'one-time-code';
|
||||
$attributes['inputmode'] = 'numeric';
|
||||
$attributes['pattern'] = '[0-9]*';
|
||||
$attributes['class'] = 'tool-mfa-verification-code';
|
||||
$attributes['class'] = 'tool-mfa-verification-code font-weight-bold';
|
||||
$attributes['maxlength'] = 6;
|
||||
|
||||
// If we aren't on the auth page, this might be part of a larger form such as for setup.
|
||||
// We shouldn't autofocus here, as it probably isn't the only element, or main target.
|
||||
@@ -62,11 +63,7 @@ class verification_field extends \MoodleQuickForm_text {
|
||||
// If we are on the auth page, load JS for element.
|
||||
$this->appendjs = false;
|
||||
if ($auth) {
|
||||
if ($PAGE->pagelayout === 'secure') {
|
||||
$this->appendjs = true;
|
||||
} else {
|
||||
$PAGE->requires->js_call_amd('tool_mfa/autosubmit_verification_code', 'init', []);
|
||||
}
|
||||
$PAGE->requires->js_call_amd('tool_mfa/autosubmit_verification_code', 'init', []);
|
||||
}
|
||||
|
||||
// Force element name to match JS.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
namespace tool_mfa;
|
||||
|
||||
use dml_exception;
|
||||
use tool_mfa\plugininfo\factor;
|
||||
|
||||
/**
|
||||
* MFA management class.
|
||||
@@ -69,8 +70,8 @@ class manager {
|
||||
'text-right',
|
||||
'text-center',
|
||||
];
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$userfactors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$factors = factor::get_enabled_factors();
|
||||
$userfactors = factor::get_active_user_factor_types();
|
||||
$runningtotal = 0;
|
||||
$weighttoggle = false;
|
||||
|
||||
@@ -79,13 +80,13 @@ class manager {
|
||||
$name = get_string('pluginname', $namespace);
|
||||
|
||||
// If factor is unknown, pending from here.
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_UNKNOWN) {
|
||||
if ($factor->get_state() == factor::STATE_UNKNOWN) {
|
||||
$weighttoggle = true;
|
||||
}
|
||||
|
||||
// Stop adding weight if 100 achieved.
|
||||
if (!$weighttoggle) {
|
||||
$achieved = $factor->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS ? $factor->get_weight() : 0;
|
||||
$achieved = $factor->get_state() == factor::STATE_PASS ? $factor->get_weight() : 0;
|
||||
$achieved = '+'.$achieved;
|
||||
$runningtotal += $achieved;
|
||||
} else {
|
||||
@@ -148,10 +149,10 @@ class manager {
|
||||
*/
|
||||
public static function get_total_weight(): int {
|
||||
$totalweight = 0;
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$factors = factor::get_active_user_factor_types();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
if ($factor->get_state() == factor::STATE_PASS) {
|
||||
$totalweight += $factor->get_weight();
|
||||
}
|
||||
}
|
||||
@@ -227,12 +228,12 @@ class manager {
|
||||
global $SESSION;
|
||||
|
||||
// Check for any instant fail states.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$factors = factor::get_active_user_factor_types();
|
||||
foreach ($factors as $factor) {
|
||||
$factor->load_locked_state();
|
||||
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_FAIL;
|
||||
if ($factor->get_state() == factor::STATE_FAIL) {
|
||||
return factor::STATE_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,25 +241,24 @@ class manager {
|
||||
self::passed_enough_factors());
|
||||
|
||||
// Check next factor for instant fail (fallback).
|
||||
if (\tool_mfa\plugininfo\factor::get_next_user_factor()->get_state() ==
|
||||
\tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
if (factor::get_next_user_login_factor()->get_state() == factor::STATE_FAIL) {
|
||||
// We need to handle a special case here, where someone reached the fallback,
|
||||
// If they were able to modify their state on the error page, such as passing iprange,
|
||||
// We must return pass.
|
||||
if ($passcondition) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
return factor::STATE_PASS;
|
||||
}
|
||||
|
||||
return \tool_mfa\plugininfo\factor::STATE_FAIL;
|
||||
return factor::STATE_FAIL;
|
||||
}
|
||||
|
||||
// Now check for general passing state. If found, ensure that session var is set.
|
||||
if ($passcondition) {
|
||||
return \tool_mfa\plugininfo\factor::STATE_PASS;
|
||||
return factor::STATE_PASS;
|
||||
}
|
||||
|
||||
// Else return neutral state.
|
||||
return \tool_mfa\plugininfo\factor::STATE_NEUTRAL;
|
||||
return factor::STATE_NEUTRAL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,7 +272,7 @@ class manager {
|
||||
global $SESSION;
|
||||
|
||||
$state = self::get_status();
|
||||
if ($state == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
if ($state == factor::STATE_PASS) {
|
||||
self::set_pass_state();
|
||||
// Check if user even had to reach auth page.
|
||||
if (isset($SESSION->tool_mfa_has_been_redirected)) {
|
||||
@@ -287,7 +287,7 @@ class manager {
|
||||
// Don't touch anything, let user be on their way.
|
||||
return;
|
||||
}
|
||||
} else if ($state == \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
} else if ($state == factor::STATE_FAIL) {
|
||||
self::cannot_login();
|
||||
} else if ($shouldreload) {
|
||||
// Set a session variable to track whether user is where they want to be.
|
||||
@@ -305,9 +305,9 @@ class manager {
|
||||
public static function passed_enough_factors(): bool {
|
||||
|
||||
// Check for any instant fail states.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$factors = factor::get_active_user_factor_types();
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_FAIL) {
|
||||
if ($factor->get_state() == factor::STATE_FAIL) {
|
||||
self::mfa_logout();
|
||||
}
|
||||
}
|
||||
@@ -353,17 +353,17 @@ class manager {
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
// Fire post pass state factor actions.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$factors = factor::get_active_user_factor_types();
|
||||
foreach ($factors as $factor) {
|
||||
$factor->post_pass_state();
|
||||
// Also set the states for this session to neutral if they were locked.
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_LOCKED) {
|
||||
$factor->set_state(\tool_mfa\plugininfo\factor::STATE_NEUTRAL);
|
||||
if ($factor->get_state() == factor::STATE_LOCKED) {
|
||||
$factor->set_state(factor::STATE_NEUTRAL);
|
||||
}
|
||||
}
|
||||
|
||||
// Output notifications if any factors were reset for this user.
|
||||
$enabledfactors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$enabledfactors = factor::get_enabled_factors();
|
||||
foreach ($enabledfactors as $factor) {
|
||||
$pref = 'tool_mfa_reset_' . $factor->name;
|
||||
$factorpref = get_user_preferences($pref, false);
|
||||
@@ -377,7 +377,7 @@ class manager {
|
||||
}
|
||||
|
||||
// Also check for a global reset.
|
||||
// TODO: Delete this in a ferw months, the reset all preference is no longer set.
|
||||
// TODO: Delete this in a few months, the reset all preference is no longer set.
|
||||
$allfactor = get_user_preferences('tool_mfa_reset_all', false);
|
||||
if ($allfactor) {
|
||||
$url = new \moodle_url('/admin/tool/mfa/user_preferences.php');
|
||||
@@ -563,7 +563,7 @@ class manager {
|
||||
* @return array
|
||||
*/
|
||||
public static function get_no_redirect_urls(): array {
|
||||
$factors = \tool_mfa\plugininfo\factor::get_factors();
|
||||
$factors = factor::get_factors();
|
||||
$urls = [
|
||||
new \moodle_url('/login/logout.php'),
|
||||
new \moodle_url('/admin/tool/mfa/guide.php'),
|
||||
@@ -723,7 +723,7 @@ class manager {
|
||||
return false;
|
||||
}
|
||||
|
||||
$enabledfactors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$enabledfactors = factor::get_enabled_factors();
|
||||
if (count($enabledfactors) == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -791,13 +791,13 @@ class manager {
|
||||
global $USER;
|
||||
|
||||
// Get all active factors.
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$factors = factor::get_enabled_factors();
|
||||
|
||||
// Check if there are enough factors that a user can ONLY pass, if so, don't display the menu.
|
||||
$weight = 0;
|
||||
foreach ($factors as $factor) {
|
||||
$states = $factor->possible_states($USER);
|
||||
if (count($states) == 1 && reset($states) == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
if (count($states) == 1 && reset($states) == factor::STATE_PASS) {
|
||||
$weight += $factor->get_weight();
|
||||
if ($weight >= 100) {
|
||||
return false;
|
||||
@@ -808,7 +808,7 @@ class manager {
|
||||
// Now if there is a factor that can be setup, that may return a pass state for the user, display menu.
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->has_setup()) {
|
||||
if (in_array(\tool_mfa\plugininfo\factor::STATE_PASS, $factor->possible_states($USER))) {
|
||||
if (in_array(factor::STATE_PASS, $factor->possible_states($USER))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -820,19 +820,21 @@ class manager {
|
||||
/**
|
||||
* Gets current user weight, up until first unknown factor.
|
||||
*
|
||||
* @return int
|
||||
* @return int $totalweight Total weight of all factors.
|
||||
*/
|
||||
public static function get_cumulative_weight(): int {
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$factors = factor::get_active_user_factor_types();
|
||||
// Factor order is important here, so sort the factors by state.
|
||||
$sortedfactors = factor::sort_factors_by_state($factors, factor::STATE_PASS);
|
||||
$totalweight = 0;
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
foreach ($sortedfactors as $factor) {
|
||||
if ($factor->get_state() == factor::STATE_PASS) {
|
||||
$totalweight += $factor->get_weight();
|
||||
// If over 100, break. Dont care about >100.
|
||||
// If over 100, break. Don't care about >100.
|
||||
if ($totalweight >= 100) {
|
||||
break;
|
||||
}
|
||||
} else if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_UNKNOWN) {
|
||||
} else if ($factor->get_state() == factor::STATE_UNKNOWN) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -846,7 +848,7 @@ class manager {
|
||||
* @return bool true if factor is pending.
|
||||
*/
|
||||
public static function check_factor_pending(string $factorname): bool {
|
||||
$factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types();
|
||||
$factors = factor::get_active_user_factor_types();
|
||||
// Setup vars.
|
||||
$pending = [];
|
||||
$totalweight = 0;
|
||||
@@ -859,7 +861,7 @@ class manager {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($factor->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS) {
|
||||
if ($factor->get_state() == factor::STATE_PASS) {
|
||||
$totalweight += $factor->get_weight();
|
||||
if ($totalweight >= 100) {
|
||||
$weighttoggle = true;
|
||||
|
||||
@@ -14,6 +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\output;
|
||||
|
||||
use tool_mfa\local\factor\object_factor;
|
||||
use tool_mfa\local\form\login_form;
|
||||
use \html_writer;
|
||||
use tool_mfa\plugininfo\factor;
|
||||
|
||||
/**
|
||||
* MFA renderer.
|
||||
*
|
||||
@@ -22,10 +29,10 @@
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_mfa_renderer extends plugin_renderer_base {
|
||||
class renderer extends \plugin_renderer_base {
|
||||
|
||||
/**
|
||||
* Returns the state of the factor as a badge
|
||||
* Returns the state of the factor as a badge.
|
||||
*
|
||||
* @param string $state
|
||||
* @return string
|
||||
@@ -33,36 +40,36 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
public function get_state_badge(string $state): string {
|
||||
|
||||
switch ($state) {
|
||||
case \tool_mfa\plugininfo\factor::STATE_PASS:
|
||||
return \html_writer::tag('span', get_string('state:pass', 'tool_mfa'), ['class' => 'badge badge-success']);
|
||||
case factor::STATE_PASS:
|
||||
return html_writer::tag('span', get_string('state:pass', 'tool_mfa'), ['class' => 'badge badge-success']);
|
||||
|
||||
case \tool_mfa\plugininfo\factor::STATE_FAIL:
|
||||
return \html_writer::tag('span', get_string('state:fail', 'tool_mfa'), ['class' => 'badge badge-danger']);
|
||||
case factor::STATE_FAIL:
|
||||
return html_writer::tag('span', get_string('state:fail', 'tool_mfa'), ['class' => 'badge badge-danger']);
|
||||
|
||||
case \tool_mfa\plugininfo\factor::STATE_NEUTRAL:
|
||||
return \html_writer::tag('span', get_string('state:neutral', 'tool_mfa'), ['class' => 'badge badge-warning']);
|
||||
case factor::STATE_NEUTRAL:
|
||||
return html_writer::tag('span', get_string('state:neutral', 'tool_mfa'), ['class' => 'badge badge-warning']);
|
||||
|
||||
case \tool_mfa\plugininfo\factor::STATE_UNKNOWN:
|
||||
return \html_writer::tag('span', get_string('state:unknown', 'tool_mfa'),
|
||||
case factor::STATE_UNKNOWN:
|
||||
return html_writer::tag('span', get_string('state:unknown', 'tool_mfa'),
|
||||
['class' => 'badge badge-secondary']);
|
||||
|
||||
case \tool_mfa\plugininfo\factor::STATE_LOCKED:
|
||||
return \html_writer::tag('span', get_string('state:locked', 'tool_mfa'), ['class' => 'badge badge-error']);
|
||||
case factor::STATE_LOCKED:
|
||||
return html_writer::tag('span', get_string('state:locked', 'tool_mfa'), ['class' => 'badge badge-error']);
|
||||
|
||||
default:
|
||||
return \html_writer::tag('span', get_string('pending', 'tool_mfa'), ['class' => 'badge badge-secondary']);
|
||||
return html_writer::tag('span', get_string('pending', 'tool_mfa'), ['class' => 'badge badge-secondary']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of factors which a user can add
|
||||
* Returns a list of factors which a user can add.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function available_factors(): string {
|
||||
$html = $this->output->heading(get_string('preferences:availablefactors', 'tool_mfa'), 2);
|
||||
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$factors = factor::get_enabled_factors();
|
||||
foreach ($factors as $factor) {
|
||||
// TODO is_configured / is_ready.
|
||||
if (!$factor->has_setup() || !$factor->show_setup_buttons()) {
|
||||
@@ -144,7 +151,7 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
];
|
||||
$table->data = [];
|
||||
|
||||
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
|
||||
$factors = factor::get_enabled_factors();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$userfactors = $factor->get_active_user_factors($USER);
|
||||
@@ -227,14 +234,14 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
$linktext = \html_writer::link($supportpage, $supportpage);
|
||||
$notification .= $linktext;
|
||||
}
|
||||
$return = $this->output->notification($notification, 'notifyerror');
|
||||
$return = $this->output->notification($notification, 'notifyerror', false);
|
||||
|
||||
// Logout button.
|
||||
$url = new \moodle_url('/admin/tool/mfa/auth.php', ['logout' => 1]);
|
||||
$btn = new \single_button($url, get_string('logout'), 'post', \single_button::BUTTON_PRIMARY);
|
||||
$return .= $this->render($btn);
|
||||
|
||||
$return .= $this->guide_link();
|
||||
$return .= $this->get_support_link();
|
||||
|
||||
return $return;
|
||||
}
|
||||
@@ -248,7 +255,7 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
public function factors_in_use_table(int $lookback): string {
|
||||
global $DB;
|
||||
|
||||
$factors = \tool_mfa\plugininfo\factor::get_factors();
|
||||
$factors = factor::get_factors();
|
||||
|
||||
// Setup 2 arrays, one with internal names, one pretty.
|
||||
$columns = [''];
|
||||
@@ -383,7 +390,7 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
public function factors_locked_table(): string {
|
||||
global $DB;
|
||||
|
||||
$factors = \tool_mfa\plugininfo\factor::get_factors();
|
||||
$factors = factor::get_factors();
|
||||
|
||||
$table = new \html_table();
|
||||
|
||||
@@ -414,9 +421,9 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
$lockedusers = $DB->count_records_sql($sql, [$factor->name, $locklevel]);
|
||||
$enabled = $factor->is_enabled() ? \html_writer::tag('b', get_string('yes')) : get_string('no');
|
||||
|
||||
$actions = \html_writer::link( new moodle_url($this->page->url,
|
||||
$actions = \html_writer::link( new \moodle_url($this->page->url,
|
||||
['reset' => $factor->name, 'sesskey' => sesskey()]), get_string('performbulk', 'tool_mfa'));
|
||||
$lockedusers = \html_writer::link(new moodle_url($this->page->url, ['view' => $factor->name]), $lockedusers);
|
||||
$lockedusers = \html_writer::link(new \moodle_url($this->page->url, ['view' => $factor->name]), $lockedusers);
|
||||
|
||||
$table->data[] = [
|
||||
$factor->get_display_name(),
|
||||
@@ -432,13 +439,13 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
/**
|
||||
* Displays a table of all users with a locked instance of the given factor.
|
||||
*
|
||||
* @param object $factor the factor class
|
||||
* @param object_factor $factor the factor class
|
||||
* @return string the HTML for the table
|
||||
*/
|
||||
public function factor_locked_users_table(object $factor): string {
|
||||
public function factor_locked_users_table(object_factor $factor): string {
|
||||
global $DB;
|
||||
|
||||
$table = new html_table();
|
||||
$table = new \html_table();
|
||||
$table->attributes['class'] = 'generaltable table table-bordered w-auto';
|
||||
$table->attributes['style'] = 'width: auto; min-width: 50%';
|
||||
$table->head = [
|
||||
@@ -470,25 +477,25 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
|
||||
foreach ($records as $record) {
|
||||
// Construct profile link.
|
||||
$proflink = \html_writer::link(new moodle_url('/user/profile.php',
|
||||
$proflink = \html_writer::link(new \moodle_url('/user/profile.php',
|
||||
['id' => $record->id]), fullname($record));
|
||||
|
||||
// IP link.
|
||||
$creatediplink = \html_writer::link(new moodle_url('/iplookup/index.php',
|
||||
$creatediplink = \html_writer::link(new \moodle_url('/iplookup/index.php',
|
||||
['ip' => $record->createdfromip]), $record->createdfromip);
|
||||
$lastiplink = \html_writer::link(new moodle_url('/iplookup/index.php',
|
||||
$lastiplink = \html_writer::link(new \moodle_url('/iplookup/index.php',
|
||||
['ip' => $record->lastip]), $record->lastip);
|
||||
|
||||
// Deep link to logs.
|
||||
$logicon = $this->pix_icon('i/report', get_string('userlogs', 'tool_mfa'));
|
||||
$actions = \html_writer::link(new moodle_url('/report/log/index.php', [
|
||||
$actions = \html_writer::link(new \moodle_url('/report/log/index.php', [
|
||||
'id' => 1, // Site.
|
||||
'user' => $record->id,
|
||||
]), $logicon);
|
||||
|
||||
$action = new confirm_action(get_string('resetfactorconfirm', 'tool_mfa', fullname($record)));
|
||||
$action = new \confirm_action(get_string('resetfactorconfirm', 'tool_mfa', fullname($record)));
|
||||
$actions .= $this->action_link(
|
||||
new moodle_url($this->page->url, ['reset' => $factor->name, 'id' => $record->id, 'sesskey' => sesskey()]),
|
||||
new \moodle_url($this->page->url, ['reset' => $factor->name, 'id' => $record->id, 'sesskey' => sesskey()]),
|
||||
$this->pix_icon('t/delete', get_string('resetconfirm', 'tool_mfa')),
|
||||
$action
|
||||
);
|
||||
@@ -507,17 +514,20 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a html section render of the guide link template
|
||||
* Returns a rendered support link.
|
||||
* If the MFA guidance page is enabled, this is returned.
|
||||
* Otherwise, the site support link is returned.
|
||||
* If neither support link is configured, an empty string is returned.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function guide_link(): string {
|
||||
if (!get_config('tool_mfa', 'guidance')) {
|
||||
return '';
|
||||
public function get_support_link(): string {
|
||||
// Try the guidance page link first.
|
||||
if (get_config('tool_mfa', 'guidance')) {
|
||||
return $this->render_from_template('tool_mfa/guide_link', []);
|
||||
} else {
|
||||
return $this->output->supportemail([], true);
|
||||
}
|
||||
$html = $this->heading(get_string('needhelp', 'tool_mfa'), 3);
|
||||
$html .= $this->render_from_template('tool_mfa/guide_link', []);
|
||||
return $this->notification($html, 'info');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,17 +535,17 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
*
|
||||
* In certain situations, includes a script element which adds autosubmission behaviour.
|
||||
*
|
||||
* @param HTML_QuickForm_element $element element
|
||||
* @param mixed $element element
|
||||
* @param bool $required if input is required field
|
||||
* @param bool $advanced if input is an advanced field
|
||||
* @param string|null $error error message to display
|
||||
* @param bool $ingroup True if this element is rendered as part of a group
|
||||
* @return mixed string|bool
|
||||
*/
|
||||
public function mform_element(HTML_QuickForm_element $element, bool $required,
|
||||
public function mform_element(mixed $element, bool $required,
|
||||
bool $advanced, string|null $error, bool $ingroup): string|bool {
|
||||
$script = null;
|
||||
if ($element instanceof tool_mfa\local\form\verification_field) {
|
||||
if ($element instanceof \tool_mfa\local\form\verification_field) {
|
||||
if ($this->page->pagelayout === 'secure') {
|
||||
$script = $element->secure_js();
|
||||
}
|
||||
@@ -549,4 +559,85 @@ class tool_mfa_renderer extends plugin_renderer_base {
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the verification form.
|
||||
*
|
||||
* @param object_factor $factor The factor to render the form for.
|
||||
* @param login_form $form The login form object.
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public function verification_form(object_factor $factor, login_form $form): string {
|
||||
$allloginfactors = factor::get_all_user_login_factors();
|
||||
$additionalfactors = [];
|
||||
$disabledfactors = [];
|
||||
$displaycount = 0;
|
||||
$disablefactor = false;
|
||||
|
||||
foreach ($allloginfactors as $loginfactor) {
|
||||
if ($loginfactor->name != $factor->name) {
|
||||
$additionalfactor = [
|
||||
'name' => $loginfactor->name,
|
||||
'icon' => $loginfactor->get_icon(),
|
||||
'loginoption' => get_string('loginoption', 'factor_' . $loginfactor->name),
|
||||
];
|
||||
// We mark the factor as disabled if it is locked.
|
||||
// We store the disabled factors in a separate array so that they can be displayed at the bottom of the template.
|
||||
if ($loginfactor->get_state() == factor::STATE_LOCKED) {
|
||||
$additionalfactor['loginoption'] = get_string('locked', 'tool_mfa', $additionalfactor['loginoption']);
|
||||
$additionalfactor['disable'] = true;
|
||||
$disabledfactors[] = $additionalfactor;
|
||||
} else {
|
||||
$additionalfactors[] = $additionalfactor;
|
||||
}
|
||||
$displaycount++;
|
||||
}
|
||||
}
|
||||
|
||||
// We merge the additional factors placing the disabled ones last.
|
||||
$alladitionalfactors = array_merge($additionalfactors, $disabledfactors);
|
||||
$hasadditionalfactors = $displaycount > 0;
|
||||
$authurl = new \moodle_url('/admin/tool/mfa/auth.php');
|
||||
|
||||
// Set the form to better display vertically.
|
||||
$form->set_display_vertical();
|
||||
|
||||
// Check if we need to display a remaining attempts message.
|
||||
$remattempts = $factor->get_remaining_attempts();
|
||||
$verificationerror = $form->get_element_error('verificationcode');
|
||||
if ($remattempts < get_config('tool_mfa', 'lockout') && !empty($verificationerror)) {
|
||||
// Update the validation error for the code form field to include the remaining attempts.
|
||||
$remattemptsstr = get_string('lockoutnotification', 'tool_mfa', $factor->get_remaining_attempts());
|
||||
$updatederror = $verificationerror . ' ' . $remattemptsstr;
|
||||
$form->set_element_error('verificationcode', $updatederror);
|
||||
}
|
||||
|
||||
// If all attempts for this factor have been used, disable the form.
|
||||
// This forces the user to choose another factor or cancel their login.
|
||||
if ($remattempts <= 0) {
|
||||
$disablefactor = true;
|
||||
$form->freeze('verificationcode');
|
||||
|
||||
// Handle the trust factor if present.
|
||||
if ($form->element_exists('factor_token_trust')) {
|
||||
$form->freeze('factor_token_trust');
|
||||
}
|
||||
}
|
||||
|
||||
$context = [
|
||||
'logintitle' => get_string('logintitle', 'factor_'.$factor->name),
|
||||
'logindesc' => $factor->get_login_desc(),
|
||||
'factoricon' => $factor->get_icon(),
|
||||
'form' => $form->render(),
|
||||
'hasadditionalfactors' => $hasadditionalfactors,
|
||||
'additionalfactors' => $alladitionalfactors,
|
||||
'authurl' => $authurl->out(),
|
||||
'supportlink' => $this->get_support_link(),
|
||||
'disablefactor' => $disablefactor
|
||||
];
|
||||
return $this->render_from_template('tool_mfa/verification_form', $context);
|
||||
}
|
||||
}
|
||||
@@ -127,32 +127,18 @@ class factor extends \core\plugininfo\base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds active factors for current user.
|
||||
* Finds active factors for a user.
|
||||
* If user is not specified, current user is used.
|
||||
*
|
||||
* @param mixed $user user object or null.
|
||||
* @return array of factor objects.
|
||||
*/
|
||||
public static function get_active_user_factor_types(): array {
|
||||
public static function get_active_user_factor_types(mixed $user = null): array {
|
||||
global $USER;
|
||||
$return = [];
|
||||
$factors = self::get_enabled_factors();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
$userfactors = $factor->get_active_user_factors($USER);
|
||||
if (count($userfactors) > 0) {
|
||||
$return[] = $factor;
|
||||
}
|
||||
if (is_null($user)) {
|
||||
$user = $USER;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds active factors for given user.
|
||||
*
|
||||
* @param stdClass $user the user to get types for.
|
||||
* @return array of factor objects.
|
||||
*/
|
||||
public static function get_active_other_user_factor_types(stdClass $user): array {
|
||||
$return = [];
|
||||
$factors = self::get_enabled_factors();
|
||||
|
||||
@@ -168,10 +154,11 @@ class factor extends \core\plugininfo\base {
|
||||
|
||||
/**
|
||||
* Returns next factor to authenticate user.
|
||||
* Only returns factors that require user input.
|
||||
*
|
||||
* @return mixed factor object the next factor to be authenticated or false.
|
||||
*/
|
||||
public static function get_next_user_factor(): object {
|
||||
public static function get_next_user_login_factor(): mixed {
|
||||
$factors = self::get_active_user_factor_types();
|
||||
|
||||
foreach ($factors as $factor) {
|
||||
@@ -187,6 +174,23 @@ class factor extends \core\plugininfo\base {
|
||||
return new \tool_mfa\local\factor\fallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all factors that require user input.
|
||||
*
|
||||
* @return array of factor objects.
|
||||
*/
|
||||
public static function get_all_user_login_factors(): array {
|
||||
$factors = self::get_active_user_factor_types();
|
||||
$loginfactors = [];
|
||||
foreach ($factors as $factor) {
|
||||
if ($factor->has_input()) {
|
||||
$loginfactors[] = $factor;
|
||||
}
|
||||
|
||||
}
|
||||
return $loginfactors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of available actions with factor.
|
||||
*
|
||||
@@ -336,4 +340,30 @@ class factor extends \core\plugininfo\base {
|
||||
|
||||
parent::uninstall_cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts factors by state.
|
||||
*
|
||||
* @param array $factors The factors to sort.
|
||||
* @param string $state The state to sort by.
|
||||
* @return array $factors The sorted factors.
|
||||
*/
|
||||
public static function sort_factors_by_state(array $factors, string $state): array {
|
||||
usort($factors, function ($a, $b) use ($state) {
|
||||
$statea = $a->get_state();
|
||||
$stateb = $b->get_state();
|
||||
|
||||
if ($statea === $state && $stateb !== $state) {
|
||||
return -1; // A comes before B.
|
||||
}
|
||||
|
||||
if ($stateb === $state && $statea !== $state) {
|
||||
return 1; // B comes before A.
|
||||
}
|
||||
|
||||
return 0; // They are the same, keep current order.
|
||||
});
|
||||
|
||||
return $factors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,15 +30,17 @@ use tool_mfa\local\factor\object_factor_base;
|
||||
*/
|
||||
class factor extends object_factor_base {
|
||||
|
||||
/** @var string Factor icon */
|
||||
protected $icon = 'fa-envelope';
|
||||
|
||||
/**
|
||||
* E-Mail Factor implementation.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform
|
||||
* @return object $mform
|
||||
* @return \MoodleQuickForm $mform
|
||||
*/
|
||||
public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
|
||||
$mform->addElement('text', 'verificationcode', get_string('verificationcode', 'factor_email'));
|
||||
$mform->addElement(new \tool_mfa\local\form\verification_field());
|
||||
$mform->setType('verificationcode', PARAM_ALPHANUM);
|
||||
return $mform;
|
||||
}
|
||||
@@ -290,4 +292,47 @@ class factor extends object_factor_base {
|
||||
\tool_mfa\plugininfo\factor::STATE_UNKNOWN,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obscure an email address by replacing all but the first and last character of the local part with a dot.
|
||||
* So the users full email isn't displayed during login.
|
||||
*
|
||||
* @param string $email The email address to obfuscate.
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
protected function obfuscate_email(string $email): string {
|
||||
// Split the email address at the '@' symbol.
|
||||
$parts = explode('@', $email);
|
||||
|
||||
if (count($parts) != 2) {
|
||||
throw new \coding_exception('Invalid email format');
|
||||
}
|
||||
|
||||
$local = $parts[0];
|
||||
$domain = $parts[1];
|
||||
|
||||
// Obfuscate all but the first and last character of the local part.
|
||||
$length = strlen($local);
|
||||
$middledot = "\u{00B7}";
|
||||
if ($length > 2) {
|
||||
$local = $local[0] . str_repeat($middledot, $length - 2) . $local[$length - 1];
|
||||
}
|
||||
|
||||
// Put the email address back together and return it.
|
||||
return $local . '@' . $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login description associated with this factor.
|
||||
* Override for factors that have a user input.
|
||||
*
|
||||
* @return string The login option.
|
||||
*/
|
||||
public function get_login_desc(): string {
|
||||
global $USER;
|
||||
$email = $this->obfuscate_email($USER->email);
|
||||
|
||||
return get_string('logindesc', 'factor_' . $this->name, $email);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,14 @@ $string['email:uadescription'] = 'Browser identity for this request:';
|
||||
$string['email:validity'] = 'The code can only be used once and is valid for {$a}.';
|
||||
$string['error:badcode'] = 'Code was not found. This may be an old link, a new code may have been emailed, or the login attempt with this code was successful.';
|
||||
$string['error:parameters'] = 'Incorrect page parameters.';
|
||||
$string['error:wrongverification'] = 'Incorrect verification code';
|
||||
$string['error:wrongverification'] = 'Wrong code. Try again.';
|
||||
$string['event:unauthemail'] = 'Unauthorised email received';
|
||||
$string['info'] = '<p>Built-in factor. Uses e-mail address mentioned in user profile for sending verification codes</p>';
|
||||
$string['logindesc'] = 'We\'ve just sent a 6-digit code to your email: {$a}';
|
||||
$string['loginoption'] = 'Have a code emailed to you';
|
||||
$string['loginskip'] = "I didn't receive a code";
|
||||
$string['loginsubmit'] = 'Verify code';
|
||||
$string['loginsubmit'] = 'Continue';
|
||||
$string['logintitle'] = "Verify it's you by email";
|
||||
$string['pluginname'] = 'E-Mail Factor';
|
||||
$string['privacy:metadata'] = 'The E-Mail Factor plugin does not store any personal data';
|
||||
$string['settings:duration'] = 'Validity duration';
|
||||
|
||||
@@ -62,6 +62,10 @@ class factor extends object_factor_base {
|
||||
/** @var string */
|
||||
const TOTP_INVALID = 'invalid';
|
||||
|
||||
/** @var string Factor icon */
|
||||
protected $icon = 'fa-mobile-screen';
|
||||
|
||||
|
||||
/**
|
||||
* Generates TOTP URI for given secret key.
|
||||
* Uses site name, hostname and user name to make GA account look like:
|
||||
@@ -243,7 +247,6 @@ class factor extends object_factor_base {
|
||||
$mform->disable_form_change_checker();
|
||||
$mform->addElement(new \tool_mfa\local\form\verification_field());
|
||||
$mform->setType('verificationcode', PARAM_ALPHANUM);
|
||||
$mform->addHelpButton('verificationcode', 'verificationcode', 'factor_totp');
|
||||
|
||||
return $mform;
|
||||
}
|
||||
|
||||
@@ -41,12 +41,13 @@ eg. <ul><li><a href="https://authy.com/download/">Twilio Authy</a></li>
|
||||
<li><a href="https://www.microsoft.com/en-us/account/authenticator#getapp">Microsoft Authenticator</a></li>
|
||||
<li>Google Authenticator for <a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8" target="_blank">iOS</a> or <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" target="_blank">Android</a></li></ul>
|
||||
<p>Note: Please ensure your device time and date has been set to "Auto" or "Network provided".</p>';
|
||||
$string['logindesc'] = 'Use the authenticator app in your mobile device to generate a code.';
|
||||
$string['loginoption'] = 'Use Authenticator application';
|
||||
$string['loginskip'] = 'I don\'t have my device';
|
||||
$string['loginsubmit'] = 'Verify code';
|
||||
$string['loginsubmit'] = 'Continue';
|
||||
$string['logintitle'] = 'Verify it\'s you by mobile app';
|
||||
$string['pluginname'] = 'Authenticator app';
|
||||
$string['privacy:metadata'] = 'The TOTP factor plugin does not store any personal data';
|
||||
$string['settings:secretlength'] = 'TOTP secret key length';
|
||||
$string['settings:secretlength_help'] = 'Generated TOTP secret key string length';
|
||||
$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';
|
||||
@@ -55,7 +56,6 @@ $string['settings:window_help'] = 'How long each code is valid for. You can set
|
||||
$string['setupfactor'] = 'TOTP authenticator setup';
|
||||
$string['setupfactor:account'] = 'Account:';
|
||||
$string['setupfactor:enter'] = 'Enter details manually:';
|
||||
$string['setupfactor:enter_help'] = 'When manually adding the secret code, set the account name in the app to something that will help to identify this code to the platform, such as the site name. Ensure the selected mode is time-based.';
|
||||
$string['setupfactor:key'] = 'Secret key: ';
|
||||
$string['setupfactor:link'] = '<b> OR </b> open mobile app:';
|
||||
$string['setupfactor:link_help'] = 'If you are on a mobile device and already have an authenticator app installed this link may work. Note that using TOTP on the same device as you login on can weaken the benefits of MFA.';
|
||||
|
||||
@@ -43,6 +43,9 @@ class factor extends object_factor_base {
|
||||
/** @var string User verification setting */
|
||||
private $userverification;
|
||||
|
||||
/** @var string Factor icon */
|
||||
protected $icon = 'fa-hand-pointer';
|
||||
|
||||
/**
|
||||
* Create webauthn server.
|
||||
*
|
||||
@@ -154,11 +157,12 @@ class factor extends object_factor_base {
|
||||
public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
|
||||
global $PAGE, $USER, $SESSION;
|
||||
|
||||
$mform->addElement('html', get_string('loginexplanation', 'factor_webauthn'));
|
||||
|
||||
$mform->addElement('hidden', 'response_input', '', ['id' => 'id_response_input']);
|
||||
$mform->setType('response_input', PARAM_RAW);
|
||||
|
||||
// Required to attach verification errors, so they can be displayed to the user.
|
||||
$mform->addElement('static', 'verificationcode', '', '');
|
||||
|
||||
$ids = [];
|
||||
|
||||
$authenticators = $this->get_active_user_factors($USER);
|
||||
@@ -248,7 +252,7 @@ class factor extends object_factor_base {
|
||||
$mform->addRule('webauthn_name', get_string('required'), 'required', null, 'client');
|
||||
|
||||
$registerbtn = \html_writer::tag('btn', get_string('register', 'factor_webauthn'), [
|
||||
'class' => 'btn btn-secondary',
|
||||
'class' => 'btn btn-primary',
|
||||
'type' => 'button',
|
||||
'id' => 'factor_webauthn-register'
|
||||
]);
|
||||
|
||||
@@ -30,12 +30,13 @@ $string['authenticator:internal'] = 'Internal';
|
||||
$string['authenticator:nfc'] = 'NFC';
|
||||
$string['authenticator:usb'] = 'USB';
|
||||
$string['authenticatorname'] = 'Security key name';
|
||||
$string['authenticatortypelimitation'] = 'Please note that you can only use security keys of one of these types: {$a}.<br>Registering other security keys is possible, but you cannot use them during login.';
|
||||
$string['error'] = 'Failed to authenticate';
|
||||
$string['info'] = '<p>Use a security key</p>';
|
||||
$string['loginexplanation'] = 'Your account settings require that you authenticate with your security key in addition to your password.';
|
||||
$string['logindesc'] = 'Click continue to use your authenticator token or security key.';
|
||||
$string['loginoption'] = 'Use authenticator token';
|
||||
$string['loginskip'] = 'I don\'t have my security key';
|
||||
$string['loginsubmit'] = 'Verify security key';
|
||||
$string['loginsubmit'] = 'Continue';
|
||||
$string['logintitle'] = 'Verify it\'s you by authenticator token';
|
||||
$string['pluginname'] = 'Security Key';
|
||||
$string['privacy:metadata'] = 'The WebAuthn factor plugin does not store any personal data';
|
||||
$string['register'] = 'Register authenticator';
|
||||
|
||||
@@ -32,7 +32,6 @@ $string['combination'] = 'Combination';
|
||||
$string['connector'] = 'AND';
|
||||
$string['created'] = 'Created';
|
||||
$string['createdfromip'] = 'Created from IP';
|
||||
$string['debugmode:currentweight'] = 'Current weight: {$a}';
|
||||
$string['debugmode:heading'] = 'Debug mode';
|
||||
$string['devicename'] = 'Device';
|
||||
$string['email:subject'] = 'Unable to login to {$a}';
|
||||
@@ -41,7 +40,6 @@ $string['error:actionnotfound'] = 'Action \'{$a}\' not supported';
|
||||
$string['error:directaccess'] = 'This page shouldn\'t be accessed directly';
|
||||
$string['error:factornotenabled'] = 'MFA Factor \'{$a}\' not enabled';
|
||||
$string['error:factornotfound'] = 'MFA Factor \'{$a}\' not found';
|
||||
$string['error:home'] = 'Click here to return home.';
|
||||
$string['error:notenoughfactors'] = 'Unable to authenticate';
|
||||
$string['error:reauth'] = 'We couldn\'t confirm your identity sufficiently to meet this sites 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 in each step. <br> 3) Steps were skipped - please reload this page or try logging in again.';
|
||||
@@ -59,7 +57,6 @@ $string['event:userpassedmfa'] = 'Verification passed';
|
||||
$string['event:userrevokedfactor'] = 'Factor revocation';
|
||||
$string['event:usersetupfactor'] = 'Factor setup';
|
||||
$string['factor'] = 'Factor';
|
||||
$string['factorlocked'] = 'Factor \'{$a}\' has been locked due to exceeded failed attempts.';
|
||||
$string['factorreport'] = 'All factor report';
|
||||
$string['factorreset'] = 'Your MFA \'{$a->factor}\' has been reset by a site administrator. You may need to set up this factor again. {$a->url}';
|
||||
$string['factorresetall'] = 'All your MFA factors have been reset by a site administrator. You may need to set up these factors again. {$a}';
|
||||
@@ -67,15 +64,14 @@ $string['factorrevoked'] = 'Factor \'{$a}\' successfully revoked.';
|
||||
$string['factorsetup'] = 'Factor \'{$a}\' successfully setup.';
|
||||
$string['fallback'] = 'Fallback factor';
|
||||
$string['fallback_info'] = 'This factor is a fallback if no other factors are configured. This factor will always fail.';
|
||||
$string['gotourl'] = 'Go to your original URL: ';
|
||||
$string['guidance'] = 'MFA user guide';
|
||||
$string['inputrequired'] = 'User input';
|
||||
$string['ipatcreation'] = 'IP address when factor created';
|
||||
$string['lastverified'] = 'Last verified';
|
||||
$string['locked'] = '{$a} (Unavailable)';
|
||||
$string['lockedusersforallfactors'] = 'Locked users: All factors';
|
||||
$string['lockedusersforfactor'] = 'Locked users: {$a}';
|
||||
$string['lockoutnotification'] = 'You have {$a} verification attempts remaining for this factor.';
|
||||
$string['lookbackperiod'] = 'Showing MFA information from {$a} onwards.';
|
||||
$string['lockoutnotification'] = 'You have {$a} attempts left.';
|
||||
$string['mfa'] = 'MFA';
|
||||
$string['mfa:mfaaccess'] = 'Interact with MFA';
|
||||
$string['mfareports'] = 'MFA reports';
|
||||
@@ -161,7 +157,9 @@ $string['userempty'] = 'User cannot be empty.';
|
||||
$string['userlogs'] = 'User logs';
|
||||
$string['usernotfound'] = 'Unable to locate user.';
|
||||
$string['usersauthedinperiod'] = 'Logged in';
|
||||
$string['verificationcode'] = 'Enter verification code for confirmation';
|
||||
$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_help'] = 'The verification code provided by the current authentication factor.';
|
||||
$string['viewlockedusers'] = 'View locked users';
|
||||
$string['verifyalt'] = 'Try another way to verify:';
|
||||
$string['weight'] = 'Weight';
|
||||
|
||||
@@ -1,8 +1,40 @@
|
||||
input.tool-mfa-verification-code,
|
||||
.tool-mfa-verification-code input {
|
||||
/* Some elements must be important to override form element*/
|
||||
font-size: 1.25em;
|
||||
letter-spacing: 1.05em;
|
||||
font-size: 1.5em !important; /* stylelint-disable-line declaration-no-important */
|
||||
font-family: monospace;
|
||||
width: 11.5em;
|
||||
}
|
||||
text-align: center;
|
||||
letter-spacing: 1.0em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tool-mfa-factor-choose,
|
||||
.tool-mfa-factor-choose:hover {
|
||||
background-color: #fff;
|
||||
border-color: #3584c9;
|
||||
border-width: 1px;
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.tool-mfa-factor-choose:hover,
|
||||
.tool-mfa-factor-choose:hover .tool-mfa-factor-choose-text {
|
||||
text-decoration: underline;
|
||||
color: #094478;
|
||||
background-color: #cfe2f2;
|
||||
}
|
||||
|
||||
.mfa-verify-form input[type="submit"] {
|
||||
height: 50px;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.mfa-verify-form .invalid-feedback {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mfa-verify-form .form-group.fitem,
|
||||
.mfa-verify-form .form-group.fitem > span,
|
||||
.mfa-verify-form .form-group.fitem > span > input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
{
|
||||
}
|
||||
}}
|
||||
<div class='my-2'>
|
||||
<h4>
|
||||
<a href='{{config.wwwroot}}/admin/tool/mfa/guide.php'> {{#str}} guidance, tool_mfa {{/str}} </a>
|
||||
</h4>
|
||||
</div>
|
||||
<a href='{{config.wwwroot}}/admin/tool/mfa/guide.php'>
|
||||
{{#str}} guidance, tool_mfa {{/str}}<i class="icon fa fa-external-link fa-fw ml-1" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
{{!
|
||||
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/verification_form
|
||||
|
||||
Template to display the MFA verification form and options.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"logintitle": "We've just sent a 6-digit code to your email",
|
||||
"logindesc": "Use the Google Authenticator app to generate a code.",
|
||||
"factoricon": "fa-envelope",
|
||||
"form": "<form id=\"mfa-verify-form\" method=\"POST\" action=\"https:\/\/webserver\/admin\/tool\/mfa\/auth.php\"></form>",
|
||||
"hasadditionalfactors": true,
|
||||
"additionalfactors": [
|
||||
{
|
||||
"name": "webauthn",
|
||||
"icon": "fa-hand-pointer",
|
||||
"loginoption": "Use authenticator token"
|
||||
}
|
||||
],
|
||||
"authurl": "https:\/\/webserver\/admin\/tool\/mfa\/auth.php",
|
||||
"supportlink": "<a href=\"https:\/\/webserver\/user\/contactsitesupport.php\">Contact site support<\/a>",
|
||||
"disablefactor": false
|
||||
}
|
||||
}}
|
||||
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2>{{#str}} verification, tool_mfa {{/str}}</h2>
|
||||
<p>{{#str}} verification_desc, tool_mfa {{/str}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4 border-bottom"></div>
|
||||
<div class="d-flex align-items-start pb-2 {{#disablefactor}}text-muted{{/disablefactor}}">
|
||||
<div class="pt-1" style="flex: 0 0 auto;">
|
||||
<i class="fa {{factoricon}} fa-fw fa-2x" title="{{logintitle}}" role="img" aria-label="{{logintitle}}"></i>
|
||||
</div>
|
||||
<div style="flex: 1 1 auto; padding-left: 15px;" class="text-wrap">
|
||||
<strong>{{logintitle}}</strong><br>
|
||||
{{logindesc}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mfa-verify-form">
|
||||
{{{form}}}
|
||||
</div>
|
||||
{{#hasadditionalfactors}}
|
||||
<div class="mb-6 mt-4 border-bottom"></div>
|
||||
<p class="font-weight-bold mb-1">{{#str}} verifyalt, tool_mfa {{/str}}</p>
|
||||
{{/hasadditionalfactors}}
|
||||
{{#additionalfactors}}
|
||||
<form id="{{uniqid}}-nextfactor" method="POST" action="{{{authurl}}}">
|
||||
<input type="hidden" id="{{uniqid}}-factorname" name="factorname" value="{{name}}">
|
||||
<button class="btn-link text-decoration-none tool-mfa-factor-choose d-flex align-items-center p-1 mb-2 {{#disable}}disabled{{/disable}}"
|
||||
type="submit">
|
||||
<span class="icon-no-margin icon-size-3 d-flex p-1 mr-2">
|
||||
<i class="fa {{icon}} fa-fw fa-2x"
|
||||
title="{{logintitle}}"
|
||||
role="img"
|
||||
aria-label="{{logintitle}}"></i>
|
||||
</span>
|
||||
<span class="tool-mfa-factor-choose-text mfa-login-option">{{loginoption}}</span>
|
||||
</button>
|
||||
</form>
|
||||
{{/additionalfactors}}
|
||||
<div class="form-container text-center mt-3">
|
||||
<div class="d-flex justify-content-center">
|
||||
<form id="cancelmfa" method="POST" action="{{{authurl}}}">
|
||||
<input type="hidden" id="{{uniqid}}-logout" name="logout" value="true">
|
||||
<input type="submit" class="btn btn-secondary"
|
||||
name="cancelmfa" id="cancelmfa_button"
|
||||
value="Cancel login" data-initial-value="Cancel login">
|
||||
</form>
|
||||
</div>
|
||||
{{#supportlink}}
|
||||
<div class="tool-mfa-site-support mt-4">
|
||||
Need help? {{{supportlink}}}
|
||||
</div>
|
||||
{{/supportlink}}
|
||||
</div>
|
||||
|
||||
@@ -29,14 +29,14 @@ class plugininfo_factor_test extends \advanced_testcase {
|
||||
/**
|
||||
* Tests getting next user factor
|
||||
*
|
||||
* @covers ::get_next_user_factor
|
||||
* @covers ::get_next_user_login_factor
|
||||
* @covers ::setup_user_factor
|
||||
* @covers ::get_enabled_factors
|
||||
* @covers ::is_enabled
|
||||
* @covers ::has_setup
|
||||
* @covers ::get_active_user_factor_types
|
||||
*/
|
||||
public function test_get_next_user_factor() {
|
||||
public function test_get_next_user_login_factor() {
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
@@ -45,7 +45,7 @@ class plugininfo_factor_test extends \advanced_testcase {
|
||||
$this->setUser($user);
|
||||
|
||||
// Test that with no enabled factors, fallback is returned.
|
||||
$this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_factor()->name);
|
||||
$this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_login_factor()->name);
|
||||
|
||||
// Setup enabled totp factor for user.
|
||||
set_config('enabled', 1, 'factor_totp');
|
||||
@@ -57,11 +57,11 @@ class plugininfo_factor_test extends \advanced_testcase {
|
||||
$this->assertNotEmpty($totpfactor->setup_user_factor((object) $totpdata));
|
||||
|
||||
// Test that factor now appears (from STATE_UNKNOWN).
|
||||
$this->assertEquals('totp', \tool_mfa\plugininfo\factor::get_next_user_factor()->name);
|
||||
$this->assertEquals('totp', \tool_mfa\plugininfo\factor::get_next_user_login_factor()->name);
|
||||
|
||||
// Now pass this factor, check for fallback.
|
||||
$totpfactor->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
|
||||
$this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_factor()->name);
|
||||
$this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_login_factor()->name);
|
||||
|
||||
// Add in a no-input factor.
|
||||
set_config('enabled', 1, 'factor_auth');
|
||||
@@ -73,6 +73,6 @@ class plugininfo_factor_test extends \advanced_testcase {
|
||||
|
||||
// Check that the next factor is still the fallback factor.
|
||||
$this->assertEquals(2, count(\tool_mfa\plugininfo\factor::get_active_user_factor_types()));
|
||||
$this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_factor()->name);
|
||||
$this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_login_factor()->name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ if (!empty($action)) {
|
||||
echo $OUTPUT->active_factors();
|
||||
echo $OUTPUT->available_factors();
|
||||
|
||||
echo $OUTPUT->guide_link();
|
||||
$renderer = $PAGE->get_renderer('tool_mfa');
|
||||
echo $renderer->get_support_link();
|
||||
|
||||
\tool_mfa\manager::display_debug_notification();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user