\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([], function() {\n return {\n recursiveBase64StrToArrayBuffer: function(obj) {\n let prefix = '=?BINARY?B?';\n let suffix = '?=';\n if (typeof obj === 'object') {\n for (let key in obj) {\n let isString = true;\n if (typeof obj[key] !== 'string') {\n this.recursiveBase64StrToArrayBuffer(obj[key]);\n isString = false;\n }\n\n let str = obj[key];\n if (isString && str.substring(0, prefix.length) === prefix &&\n str.substring(str.length - suffix.length) === suffix) {\n str = str.substring(prefix.length, str.length - suffix.length);\n\n const binaryString = window.atob(str);\n let len = binaryString.length;\n let bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n obj[key] = bytes.buffer;\n }\n }\n }\n },\n arrayBufferToBase64: function(buffer) {\n let binary = '';\n let bytes = new Uint8Array(buffer);\n let len = bytes.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return window.btoa(binary);\n },\n };\n});\n"],"names":["define","recursiveBase64StrToArrayBuffer","obj","key","isString","str","substring","length","binaryString","window","atob","len","bytes","Uint8Array","i","charCodeAt","buffer","arrayBufferToBase64","binary","byteLength","String","fromCharCode","btoa"],"mappings":";;;;;;;;AAwBAA,+BAAO,IAAI,iBACA,CACHC,gCAAiC,SAASC,QAGnB,iBAARA,QACF,IAAIC,OAAOD,IAAK,KACbE,UAAW,EACS,iBAAbF,IAAIC,YACNF,gCAAgCC,IAAIC,MACzCC,UAAW,OAGXC,IAAMH,IAAIC,QACVC,UAXC,gBAWWC,IAAIC,UAAU,EAXzB,cAWmCC,SAVnC,OAWAF,IAAIC,UAAUD,IAAIE,OAXlB,KAWkCA,QAAoB,CACvDF,IAAMA,IAAIC,UAbT,cAa0BC,OAAQF,IAAIE,OAZtC,KAYsDA,cAEjDC,aAAeC,OAAOC,KAAKL,SAC7BM,IAAMH,aAAaD,OACnBK,MAAQ,IAAIC,WAAWF,SACtB,IAAIG,EAAI,EAAGA,EAAIH,IAAKG,IACrBF,MAAME,GAAKN,aAAaO,WAAWD,GAEvCZ,IAAIC,KAAOS,MAAMI,UAKjCC,oBAAqB,SAASD,YACtBE,OAAS,GACTN,MAAQ,IAAIC,WAAWG,QACvBL,IAAMC,MAAMO,eACX,IAAIL,EAAI,EAAGA,EAAIH,IAAKG,IACrBI,QAAUE,OAAOC,aAAaT,MAAME,WAEjCL,OAAOa,KAAKJ"}
\ No newline at end of file
diff --git a/admin/tool/mfa/factor/webauthn/amd/src/login.js b/admin/tool/mfa/factor/webauthn/amd/src/login.js
index 741f16d19af..0d853deb65e 100644
--- a/admin/tool/mfa/factor/webauthn/amd/src/login.js
+++ b/admin/tool/mfa/factor/webauthn/amd/src/login.js
@@ -25,34 +25,39 @@
define(['factor_webauthn/utils'], function(utils) {
return {
init: function(getArgs) {
- document.getElementById('id_submitbutton').addEventListener('click', async function(e) {
- e.preventDefault();
- if (!navigator.credentials || !navigator.credentials.create) {
- throw new Error('Browser not supported.');
- }
+ const idSubmitButton = document.getElementById('id_submitbutton');
+ if (idSubmitButton) {
+ idSubmitButton.addEventListener('click', async function(e) {
+ e.preventDefault();
+ if (!navigator.credentials || !navigator.credentials.create) {
+ throw new Error('Browser not supported.');
+ }
- getArgs = JSON.parse(getArgs);
+ getArgs = JSON.parse(getArgs);
- if (getArgs.success === false) {
- throw new Error(getArgs.msg || 'unknown error occured');
- }
+ if (getArgs.success === false) {
+ throw new Error(getArgs.msg || 'unknown error occured');
+ }
- utils.recursiveBase64StrToArrayBuffer(getArgs);
+ utils.recursiveBase64StrToArrayBuffer(getArgs);
- const cred = await navigator.credentials.get(getArgs);
+ const cred = await navigator.credentials.get(getArgs);
- const authenticatorAttestationResponse = {
- id: cred.rawId ? utils.arrayBufferToBase64(cred.rawId) : null,
- clientDataJSON: cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,
- authenticatorData:
- cred.response.authenticatorData ? utils.arrayBufferToBase64(cred.response.authenticatorData) : null,
- signature: cred.response.signature ? utils.arrayBufferToBase64(cred.response.signature) : null,
- userHandle: cred.response.userHandle ? utils.arrayBufferToBase64(cred.response.userHandle) : null
- };
+ const authenticatorAttestationResponse = {
+ id: cred.rawId ? utils.arrayBufferToBase64(cred.rawId) : null,
+ clientDataJSON:
+ cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,
+ authenticatorData:
+ cred.response.authenticatorData ? utils.arrayBufferToBase64(cred.response.authenticatorData) : null,
+ signature: cred.response.signature ? utils.arrayBufferToBase64(cred.response.signature) : null,
+ userHandle: cred.response.userHandle ? utils.arrayBufferToBase64(cred.response.userHandle) : null
+ };
- document.getElementById('id_response_input').value = JSON.stringify(authenticatorAttestationResponse);
- document.getElementById('id_response_input').form.submit();
- });
+ const responseInput = document.getElementById('id_response_input');
+ responseInput.value = JSON.stringify(authenticatorAttestationResponse);
+ responseInput.form.submit();
+ });
+ }
}
};
});
diff --git a/admin/tool/mfa/factor/webauthn/amd/src/utils.js b/admin/tool/mfa/factor/webauthn/amd/src/utils.js
index 2985fb52bdf..dcf5ab66857 100644
--- a/admin/tool/mfa/factor/webauthn/amd/src/utils.js
+++ b/admin/tool/mfa/factor/webauthn/amd/src/utils.js
@@ -29,21 +29,24 @@ define([], function() {
let suffix = '?=';
if (typeof obj === 'object') {
for (let key in obj) {
- if (typeof obj[key] === 'string') {
- let str = obj[key];
- if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) {
- str = str.substring(prefix.length, str.length - suffix.length);
-
- let binary_string = window.atob(str);
- let len = binary_string.length;
- let bytes = new Uint8Array(len);
- for (let i = 0; i < len; i++) {
- bytes[i] = binary_string.charCodeAt(i);
- }
- obj[key] = bytes.buffer;
- }
- } else {
+ let isString = true;
+ if (typeof obj[key] !== 'string') {
this.recursiveBase64StrToArrayBuffer(obj[key]);
+ isString = false;
+ }
+
+ let str = obj[key];
+ if (isString && str.substring(0, prefix.length) === prefix &&
+ str.substring(str.length - suffix.length) === suffix) {
+ str = str.substring(prefix.length, str.length - suffix.length);
+
+ const binaryString = window.atob(str);
+ let len = binaryString.length;
+ let bytes = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+ obj[key] = bytes.buffer;
}
}
}
diff --git a/admin/tool/mfa/factor/webauthn/classes/factor.php b/admin/tool/mfa/factor/webauthn/classes/factor.php
index 24ae7018d88..3cb823cf32f 100644
--- a/admin/tool/mfa/factor/webauthn/classes/factor.php
+++ b/admin/tool/mfa/factor/webauthn/classes/factor.php
@@ -23,6 +23,7 @@ require_once($CFG->libdir . '/webauthn/src/WebAuthn.php');
use lbuchs\WebAuthn\Binary\ByteBuffer;
use lbuchs\WebAuthn\WebAuthn;
use lbuchs\WebAuthn\WebAuthnException;
+use stdClass;
use tool_mfa\local\factor\object_factor_base;
/**
@@ -63,7 +64,7 @@ class factor extends object_factor_base {
* @param stdClass $user the user to check against.
* @return array
*/
- public function get_all_user_factors($user) {
+ public function get_all_user_factors(stdClass $user): array {
global $DB;
return $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]);
}
@@ -73,7 +74,7 @@ class factor extends object_factor_base {
*
* {@inheritDoc}
*/
- public function has_input() {
+ public function has_input(): bool {
return true;
}
@@ -82,7 +83,7 @@ class factor extends object_factor_base {
*
* {@inheritDoc}
*/
- public function has_revoke() {
+ public function has_revoke(): bool {
return true;
}
@@ -91,7 +92,7 @@ class factor extends object_factor_base {
*
* {@inheritDoc}
*/
- public function has_setup() {
+ public function has_setup(): bool {
return true;
}
@@ -100,16 +101,17 @@ class factor extends object_factor_base {
*
* {@inheritDoc}
*/
- public function show_setup_buttons() {
+ public function show_setup_buttons(): bool {
return true;
}
/**
* WebAuthn factor implementation.
*
- * @param \stdClass $user
+ * @param stdClass $user
+ * @return array
*/
- public function possible_states($user) {
+ public function possible_states(stdClass $user): array {
return [
\tool_mfa\plugininfo\factor::STATE_PASS,
\tool_mfa\plugininfo\factor::STATE_NEUTRAL,
@@ -122,7 +124,7 @@ class factor extends object_factor_base {
*
* {@inheritDoc}
*/
- public function get_state() {
+ public function get_state(): string {
global $USER;
$userfactors = $this->get_active_user_factors($USER);
@@ -136,8 +138,10 @@ class factor extends object_factor_base {
/**
* Gets the string for setup button on preferences page.
+ *
+ * @return string
*/
- public function get_setup_string() {
+ public function get_setup_string(): string {
return get_string('setupfactor', 'factor_webauthn');
}
@@ -145,11 +149,13 @@ class factor extends object_factor_base {
* WebAuthn Factor implementation.
*
* @param \MoodleQuickForm $mform
- * @return object $mform
+ * @return \MoodleQuickForm $mform
*/
- public function login_form_definition($mform) {
+ 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);
@@ -184,7 +190,7 @@ class factor extends object_factor_base {
* @param array $data
* @return array
*/
- public function login_form_validation($data) {
+ public function login_form_validation(array $data): array {
global $USER, $SESSION;
$errors = [];
@@ -234,7 +240,7 @@ class factor extends object_factor_base {
* @param \MoodleQuickForm $mform
* @return object $mform
*/
- public function setup_factor_form_definition($mform) {
+ public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm {
global $PAGE, $USER, $SESSION;
$mform->addElement('text', 'webauthn_name', get_string('authenticatorname', 'factor_webauthn'));
@@ -283,10 +289,10 @@ class factor extends object_factor_base {
/**
* WebAuthn Factor implementation.
*
- * @param array $data
- * @return array
+ * @param object $data
+ * @return stdClass|null
*/
- public function setup_user_factor($data) {
+ public function setup_user_factor(object $data): stdClass|null {
global $DB, $USER, $SESSION;
if (!empty($data->webauthn_name) && !empty($data->response_input) && isset($SESSION->factor_webauthn_challenge)) {
diff --git a/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php b/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php
index 4841371da19..7e38d11c566 100644
--- a/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php
+++ b/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php
@@ -32,10 +32,11 @@ $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}.
Registering other security keys is possible, but you cannot use them during login.';
$string['error'] = 'Failed to authenticate';
-$string['info'] = 'Use a WebAuthn supported authenticator
';
-$string['loginskip'] = 'I don\'t have my authenticator';
-$string['loginsubmit'] = 'Verify authenticator';
-$string['pluginname'] = 'WebAuthn';
+$string['info'] = 'Use a security key
';
+$string['loginexplanation'] = 'Your account settings require that you authenticate with your security key in addition to your password.';
+$string['loginskip'] = 'I don\'t have my security key';
+$string['loginsubmit'] = 'Verify security key';
+$string['pluginname'] = 'Security Key';
$string['privacy:metadata'] = 'The WebAuthn factor plugin does not store any personal data';
$string['register'] = 'Register authenticator';
$string['settings:authenticatortypes'] = 'Types of authenticator';
diff --git a/admin/tool/mfa/factor/webauthn/version.php b/admin/tool/mfa/factor/webauthn/version.php
index e5c4d61f852..914865f3fb9 100644
--- a/admin/tool/mfa/factor/webauthn/version.php
+++ b/admin/tool/mfa/factor/webauthn/version.php
@@ -25,9 +25,9 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2023052400; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->release = 2023052400;
-$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
+$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->release = 2023062900;
+$plugin->requires = 2023042400.00; // Supports from 4.2.
$plugin->component = 'factor_webauthn';
$plugin->maturity = MATURITY_ALPHA;
-$plugin->dependencies = ['tool_mfa' => 2023031600];
+$plugin->dependencies = ['tool_mfa' => 2023080300];
diff --git a/admin/tool/mfa/guide.php b/admin/tool/mfa/guide.php
index 30dc1b674fb..709515157e2 100644
--- a/admin/tool/mfa/guide.php
+++ b/admin/tool/mfa/guide.php
@@ -37,7 +37,7 @@ if (!get_config('tool_mfa', 'guidance')) {
}
// Navigation. Target user preferences as previous node if authed.
-if (isloggedin() && (!empty($SESSION->tool_mfa_authenticated) || $SESSION->tool_mfa_authenticated)) {
+if (isloggedin() && (isset($SESSION->tool_mfa_authenticated) && $SESSION->tool_mfa_authenticated)) {
if ($node = $PAGE->settingsnav->find('usercurrentsettings', null)) {
$PAGE->navbar->add($node->get_content(), $node->action());
}
diff --git a/admin/tool/mfa/lang/en/tool_mfa.php b/admin/tool/mfa/lang/en/tool_mfa.php
index b93be039e6c..25572c75c6e 100644
--- a/admin/tool/mfa/lang/en/tool_mfa.php
+++ b/admin/tool/mfa/lang/en/tool_mfa.php
@@ -142,7 +142,7 @@ $string['settings:guidancepage_help'] = 'HTML here will be displayed on the guid
$string['settings:lockout'] = 'Lockout threshold';
$string['settings:lockout_help'] = 'Amount of attempts a user has at answering input factors before they are not permitted to login.';
$string['settings:redir_exclusions'] = 'Urls which should not redirect the MFA check';
-$string['settings:redir_exclusions_help'] = 'Each new line is a relative URL from the siteroot for which the MFA check will not redirect from eg. /admin/tool/securityquestions/set_responses.php';
+$string['settings:redir_exclusions_help'] = 'Each new line is a relative URL from the siteroot for which the MFA check will not redirect from';
$string['settings:weight'] = 'Factor weight';
$string['settings:weight_help'] = 'The weight of this factor if passed. A user needs at least 100 points to login.';
$string['setup'] = 'Setup';
diff --git a/admin/tool/mfa/lib.php b/admin/tool/mfa/lib.php
index 4a3709642f0..c858c764c3d 100644
--- a/admin/tool/mfa/lib.php
+++ b/admin/tool/mfa/lib.php
@@ -23,6 +23,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+use core\context;
+
/**
* Main hook.
*
@@ -37,7 +39,7 @@
* @throws \moodle_exception
*/
function tool_mfa_after_require_login($courseorid = null, $autologinguest = null, $cm = null,
- $setwantsurltome = null, $preventredirect = null) {
+ $setwantsurltome = null, $preventredirect = null): void {
global $SESSION;
// Tests for hooks being fired to test patches.
@@ -59,10 +61,10 @@ function tool_mfa_after_require_login($courseorid = null, $autologinguest = null
* @param stdClass $course
* @param context_course $coursecontext
*
- * @return void or null
+ * @return mix void or null
* @throws \moodle_exception
*/
-function tool_mfa_extend_navigation_user_settings($navigation, $user, $usercontext, $course, $coursecontext) {
+function tool_mfa_extend_navigation_user_settings(navigation_node $navigation, stdClass $user, $usercontext, stdClass $course, $coursecontext) {
global $PAGE;
// Only inject if user is on the preferences page.
@@ -83,8 +85,10 @@ function tool_mfa_extend_navigation_user_settings($navigation, $user, $userconte
/**
* Triggered as soon as practical on every moodle bootstrap after config has
* been loaded. The $USER object is available at this point too.
+ *
+ * @return void
*/
-function tool_mfa_after_config() {
+function tool_mfa_after_config(): void {
global $CFG, $SESSION;
// Tests for hooks being fired to test patches.
@@ -104,8 +108,10 @@ function tool_mfa_after_config() {
/**
* Any plugin typically an admin tool can add new bulk user actions
+ *
+ * @return array
*/
-function tool_mfa_bulk_user_actions() {
+function tool_mfa_bulk_user_actions(): array {
return [
'tool_mfa_reset_factors' => new action_link(
new moodle_url('/admin/tool/mfa/reset_factor.php'),
@@ -126,7 +132,8 @@ function tool_mfa_bulk_user_actions() {
* @param array $options
* @return bool
*/
-function tool_mfa_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = []) {
+function tool_mfa_pluginfile(stdClass $course, stdClass $cm, context $context, string $filearea,
+ array $args, bool $forcedownload, array $options = []): bool {
// Hardcode to only send guidance files from the top level.
$fs = get_file_storage();
$file = $fs->get_file(
diff --git a/admin/tool/mfa/patch/MOODLE_35_STABLE.diff b/admin/tool/mfa/patch/MOODLE_35_STABLE.diff
deleted file mode 100644
index 735a581724a..00000000000
--- a/admin/tool/mfa/patch/MOODLE_35_STABLE.diff
+++ /dev/null
@@ -1,86 +0,0 @@
-From 6d6f2d3543cd4b172aa85f0e47d7f531b7ec4d53 Mon Sep 17 00:00:00 2001
-From: Brendan Heywood
-Date: Wed, 18 Oct 2017 16:20:33 +1100
-Subject: [PATCH 1/2] MDL-60470 core: New hook 'after_require_login'
-
-This adds a hook towards the end of the require_login function.
----
- lib/moodlelib.php | 14 ++++++++++++++
- 1 file changed, 14 insertions(+)
-
-diff --git a/lib/moodlelib.php b/lib/moodlelib.php
-index 3ac3d8be1b7..d9e5baa175e 100644
---- a/lib/moodlelib.php
-+++ b/lib/moodlelib.php
-@@ -2705,6 +2705,8 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
- $CFG->forceclean = true;
- }
-
-+ $afterlogins = get_plugins_with_function('after_require_login', 'lib.php');
-+
- // Do not bother admins with any formalities, except for activities pending deletion.
- if (is_siteadmin() && !($cm && $cm->deletioninprogress)) {
- // Set the global $COURSE.
-@@ -2716,6 +2718,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
- }
- // Set accesstime or the user will appear offline which messes up messaging.
- user_accesstime_log($course->id);
-+
-+ foreach ($afterlogins as $plugintype => $plugins) {
-+ foreach ($plugins as $pluginfunction) {
-+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
-+ }
-+ }
- return;
- }
-
-@@ -2923,6 +2931,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
- $PAGE->set_course($course);
- }
-
-+ foreach ($afterlogins as $plugintype => $plugins) {
-+ foreach ($plugins as $pluginfunction) {
-+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
-+ }
-+ }
-+
- // Finally access granted, update lastaccess times.
- user_accesstime_log($course->id);
- }
---
-2.17.1
-
-
-From 7235752ed449ab6662a317f059e444598bf1a862 Mon Sep 17 00:00:00 2001
-From: Brendan Heywood
-Date: Thu, 8 Aug 2019 13:26:50 +1000
-Subject: [PATCH 2/2] MDL-66340 setup: Add after_config for after setup.php is
- loaded
-
----
- lib/setup.php | 13 +++++++++++++
- 1 file changed, 13 insertions(+)
-
-diff --git a/lib/setup.php b/lib/setup.php
-index b1cb7e054ec..df1a2d978a5 100644
---- a/lib/setup.php
-+++ b/lib/setup.php
-@@ -1042,3 +1042,16 @@ if (false) {
- $OUTPUT = new core_renderer(null, null);
- $PAGE = new moodle_page();
- }
-+
-+// Allow plugins to callback as soon possible after setup.php is loaded.
-+$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php');
-+foreach ($pluginswithfunction as $plugins) {
-+ foreach ($plugins as $function) {
-+ try {
-+ $function();
-+ } catch (Exception $e) {
-+ debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace());
-+ }
-+ }
-+}
-+
---
-2.17.1
diff --git a/admin/tool/mfa/patch/MOODLE_36_STABLE.diff b/admin/tool/mfa/patch/MOODLE_36_STABLE.diff
deleted file mode 100644
index 259ef93ea96..00000000000
--- a/admin/tool/mfa/patch/MOODLE_36_STABLE.diff
+++ /dev/null
@@ -1,87 +0,0 @@
-From 51484a595701e56897b0913974c566c6a13c1f32 Mon Sep 17 00:00:00 2001
-From: Brendan Heywood
-Date: Wed, 18 Oct 2017 16:20:33 +1100
-Subject: [PATCH 1/2] MDL-60470 core: New hook 'after_require_login'
-
-This adds a hook towards the end of the require_login function.
----
- lib/moodlelib.php | 14 ++++++++++++++
- 1 file changed, 14 insertions(+)
-
-diff --git a/lib/moodlelib.php b/lib/moodlelib.php
-index e2016a8bf75..29e11632233 100644
---- a/lib/moodlelib.php
-+++ b/lib/moodlelib.php
-@@ -2769,6 +2769,8 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
- $CFG->forceclean = true;
- }
-
-+ $afterlogins = get_plugins_with_function('after_require_login', 'lib.php');
-+
- // Do not bother admins with any formalities, except for activities pending deletion.
- if (is_siteadmin() && !($cm && $cm->deletioninprogress)) {
- // Set the global $COURSE.
-@@ -2783,6 +2785,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
- if (!WS_SERVER && !AJAX_SCRIPT) {
- user_accesstime_log($course->id);
- }
-+
-+ foreach ($afterlogins as $plugintype => $plugins) {
-+ foreach ($plugins as $pluginfunction) {
-+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
-+ }
-+ }
- return;
- }
-
-@@ -2990,6 +2998,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
- $PAGE->set_course($course);
- }
-
-+ foreach ($afterlogins as $plugintype => $plugins) {
-+ foreach ($plugins as $pluginfunction) {
-+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
-+ }
-+ }
-+
- // Finally access granted, update lastaccess times.
- // Do not update access time for webservice or ajax requests.
- if (!WS_SERVER && !AJAX_SCRIPT) {
---
-2.17.1
-
-
-From 838ad593636395e4c4884e1bf47836504413b702 Mon Sep 17 00:00:00 2001
-From: Brendan Heywood
-Date: Thu, 8 Aug 2019 13:26:50 +1000
-Subject: [PATCH 2/2] MDL-66340 setup: Add after_config for after setup.php is
- loaded
-
----
- lib/setup.php | 13 +++++++++++++
- 1 file changed, 13 insertions(+)
-
-diff --git a/lib/setup.php b/lib/setup.php
-index 2cf12822c6d..a4e0acf0bb8 100644
---- a/lib/setup.php
-+++ b/lib/setup.php
-@@ -1041,3 +1041,16 @@ if (false) {
- $OUTPUT = new core_renderer(null, null);
- $PAGE = new moodle_page();
- }
-+
-+// Allow plugins to callback as soon possible after setup.php is loaded.
-+$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php');
-+foreach ($pluginswithfunction as $plugins) {
-+ foreach ($plugins as $function) {
-+ try {
-+ $function();
-+ } catch (Exception $e) {
-+ debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace());
-+ }
-+ }
-+}
-+
---
-2.17.1
-
diff --git a/admin/tool/mfa/patch/MOODLE_37_STABLE.diff b/admin/tool/mfa/patch/MOODLE_37_STABLE.diff
deleted file mode 100644
index c8edc6eded3..00000000000
--- a/admin/tool/mfa/patch/MOODLE_37_STABLE.diff
+++ /dev/null
@@ -1,34 +0,0 @@
-From 1275e9b283c81b80b73d1c87c64449eb472cc037 Mon Sep 17 00:00:00 2001
-From: Brendan Heywood
-Date: Thu, 8 Aug 2019 13:26:50 +1000
-Subject: [PATCH] MDL-66340 setup: Add after_config for after setup.php is
- loaded
-
----
- lib/setup.php | 13 +++++++++++++
- 1 file changed, 13 insertions(+)
-
-diff --git a/lib/setup.php b/lib/setup.php
-index 2cf12822c6d..a4e0acf0bb8 100644
---- a/lib/setup.php
-+++ b/lib/setup.php
-@@ -1041,3 +1041,16 @@ if (false) {
- $OUTPUT = new core_renderer(null, null);
- $PAGE = new moodle_page();
- }
-+
-+// Allow plugins to callback as soon possible after setup.php is loaded.
-+$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php');
-+foreach ($pluginswithfunction as $plugins) {
-+ foreach ($plugins as $function) {
-+ try {
-+ $function();
-+ } catch (Exception $e) {
-+ debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace());
-+ }
-+ }
-+}
-+
---
-2.17.1
-
diff --git a/admin/tool/mfa/patch/TOTARA_16.patch b/admin/tool/mfa/patch/TOTARA_16.patch
deleted file mode 100644
index 0d3ea86ab0d..00000000000
--- a/admin/tool/mfa/patch/TOTARA_16.patch
+++ /dev/null
@@ -1,61 +0,0 @@
-diff --git a/server/lib/moodlelib.php b/server/lib/moodlelib.php
-index e80b84cb652..033de11df7f 100644
---- a/server/lib/moodlelib.php
-+++ b/server/lib/moodlelib.php
-@@ -2895,6 +2895,8 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
- // Make sure the USER has a sesskey set up. Used for CSRF protection.
- sesskey();
-
-+ $afterlogins = get_plugins_with_function('after_require_login', 'lib.php');
-+
- // Do not bother admins with any formalities, except for activities pending deletion.
- if (is_siteadmin() && !($cm && $cm->deletioninprogress)) {
- // Set the global $COURSE.
-@@ -2906,6 +2908,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
- }
- // Set accesstime or the user will appear offline which messes up messaging.
- user_accesstime_log($course->id);
-+
-+ foreach ($afterlogins as $plugintype => $plugins) {
-+ foreach ($plugins as $pluginfunction) {
-+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
-+ }
-+ }
- return;
- }
-
-@@ -3176,6 +3184,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
- }
- }
-
-+ foreach ($afterlogins as $plugintype => $plugins) {
-+ foreach ($plugins as $pluginfunction) {
-+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
-+ }
-+ }
-+
- // Finally access granted, update lastaccess times.
- user_accesstime_log($course->id);
- }
-diff --git a/server/lib/setup.php b/server/lib/setup.php
-index c05e13b03aa..4d89eda0713 100644
---- a/server/lib/setup.php
-+++ b/server/lib/setup.php
-@@ -800,3 +800,17 @@ if (!function_exists('hash_equals')) {
- return false;
- }
- }
-+
-+
-+// Allow plugins to callback as soon possible after setup.php is loaded.
-+$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php');
-+foreach ($pluginswithfunction as $plugins) {
-+ foreach ($plugins as $function) {
-+ try {
-+ $function();
-+ } catch (Throwable $e) {
-+ debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace());
-+ }
-+ }
-+}
-+
diff --git a/admin/tool/mfa/renderer.php b/admin/tool/mfa/renderer.php
index 7dbb5b74158..ea65a92dbd8 100644
--- a/admin/tool/mfa/renderer.php
+++ b/admin/tool/mfa/renderer.php
@@ -28,9 +28,9 @@ class tool_mfa_renderer extends plugin_renderer_base {
* Returns the state of the factor as a badge
*
* @param string $state
- * @return html
+ * @return string
*/
- public function get_state_badge($state) {
+ public function get_state_badge(string $state): string {
switch ($state) {
case \tool_mfa\plugininfo\factor::STATE_PASS:
@@ -57,9 +57,9 @@ class tool_mfa_renderer extends plugin_renderer_base {
/**
* Returns a list of factors which a user can add
*
- * @return html
+ * @return string
*/
- public function available_factors() {
+ public function available_factors(): string {
$html = $this->output->heading(get_string('preferences:availablefactors', 'tool_mfa'), 2);
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
@@ -77,10 +77,10 @@ class tool_mfa_renderer extends plugin_renderer_base {
/**
* Returns the html section for factor setup
*
- * @param object $factor object of the factor class
- * @return void
+ * @param object $factor object of the factor class
+ * @return string
*/
- public function setup_factor($factor) {
+ public function setup_factor(object $factor): string {
$html = '';
$html .= html_writer::start_tag('div', ['class' => 'card']);
@@ -105,7 +105,7 @@ class tool_mfa_renderer extends plugin_renderer_base {
* @return string $html
* @throws \coding_exception
*/
- public function active_factors() {
+ public function active_factors(): string {
global $USER, $CFG;
require_once($CFG->dirroot . '/iplookup/lib.php');
@@ -206,7 +206,7 @@ class tool_mfa_renderer extends plugin_renderer_base {
*
* @return string $notification
*/
- public function not_enough_factors() {
+ public function not_enough_factors(): string {
global $CFG, $SITE;
$notification = \html_writer::tag('h4', get_string('error:notenoughfactors', 'tool_mfa'));
@@ -231,7 +231,7 @@ class tool_mfa_renderer extends plugin_renderer_base {
// Logout button.
$url = new \moodle_url('/admin/tool/mfa/auth.php', ['logout' => 1]);
- $btn = new \single_button($url, get_string('logout'), 'post', true);
+ $btn = new \single_button($url, get_string('logout'), 'post', \single_button::BUTTON_PRIMARY);
$return .= $this->render($btn);
$return .= $this->guide_link();
@@ -245,7 +245,7 @@ class tool_mfa_renderer extends plugin_renderer_base {
* @param int $lookback the period to view.
* @return string the HTML for the table
*/
- public function factors_in_use_table($lookback) {
+ public function factors_in_use_table(int $lookback): string {
global $DB;
$factors = \tool_mfa\plugininfo\factor::get_factors();
@@ -326,8 +326,8 @@ class tool_mfa_renderer extends plugin_renderer_base {
// Auth rows.
$authtypes = get_enabled_auth_plugins(true);
+ $row = [];
foreach ($authtypes as $authtype) {
- $row = [];
$row[] = \html_writer::tag('b', $authtype);
// Setup the overall totals columns.
@@ -380,7 +380,7 @@ class tool_mfa_renderer extends plugin_renderer_base {
*
* @return string the HTML for the table
*/
- public function factors_locked_table() {
+ public function factors_locked_table(): string {
global $DB;
$factors = \tool_mfa\plugininfo\factor::get_factors();
@@ -435,7 +435,7 @@ class tool_mfa_renderer extends plugin_renderer_base {
* @param object $factor the factor class
* @return string the HTML for the table
*/
- public function factor_locked_users_table($factor) {
+ public function factor_locked_users_table(object $factor): string {
global $DB;
$table = new html_table();
@@ -509,9 +509,9 @@ class tool_mfa_renderer extends plugin_renderer_base {
/**
* Returns a html section render of the guide link template
*
- * @return string
+ * @return string
*/
- public function guide_link() {
+ public function guide_link(): string {
if (!get_config('tool_mfa', 'guidance')) {
return '';
}
@@ -528,11 +528,12 @@ class tool_mfa_renderer extends plugin_renderer_base {
* @param HTML_QuickForm_element $element element
* @param bool $required if input is required field
* @param bool $advanced if input is an advanced field
- * @param string $error error message to display
+ * @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($element, $required, $advanced, $error, $ingroup) {
+ public function mform_element(HTML_QuickForm_element $element, bool $required,
+ bool $advanced, string|null $error, bool $ingroup): string|bool {
$script = null;
if ($element instanceof tool_mfa\local\form\verification_field) {
if ($this->page->pagelayout === 'secure') {
diff --git a/admin/tool/mfa/settings.php b/admin/tool/mfa/settings.php
index e34845c9458..3a5af4dc96d 100644
--- a/admin/tool/mfa/settings.php
+++ b/admin/tool/mfa/settings.php
@@ -73,15 +73,9 @@ if ($hassiteconfig) {
$plugin->load_settings($ADMIN, 'toolmfafolder', $hassiteconfig);
}
- if (file_exists($CFG->dirroot . '/totara')) {
- // Totara navigation.
- $section = 'toolmfafolder';
- } else {
- // Moodle navigation.
- $ADMIN->add('reports', new admin_category('toolmfareports', get_string('mfareports', 'tool_mfa')));
- $section = 'toolmfareports';
- }
- $ADMIN->add($section,
+ $ADMIN->add('reports', new admin_category('toolmfareports', get_string('mfareports', 'tool_mfa')));
+
+ $ADMIN->add('toolmfareports',
new admin_externalpage('factorreport', get_string('factorreport', 'tool_mfa'),
new moodle_url('/admin/tool/mfa/factor_report.php')));
}
diff --git a/admin/tool/mfa/styles.css b/admin/tool/mfa/styles.css
index 635719b7894..4d7301fc60b 100644
--- a/admin/tool/mfa/styles.css
+++ b/admin/tool/mfa/styles.css
@@ -1,8 +1,8 @@
input.tool-mfa-verification-code,
.tool-mfa-verification-code input {
/* Some elements must be important to override form element*/
- font-size: 1.25em !important;
+ font-size: 1.25em;
letter-spacing: 1.05em;
font-family: monospace;
- width: 11.5em !important;
+ width: 11.5em;
}
\ No newline at end of file
diff --git a/admin/tool/mfa/templates/guide_link.mustache b/admin/tool/mfa/templates/guide_link.mustache
index df1ce003d2e..313c99cf92b 100644
--- a/admin/tool/mfa/templates/guide_link.mustache
+++ b/admin/tool/mfa/templates/guide_link.mustache
@@ -25,6 +25,6 @@
}}
\ No newline at end of file
diff --git a/admin/tool/mfa/tests/admin_setting_managemfa_test.php b/admin/tool/mfa/tests/admin_setting_managemfa_test.php
index f5fb6c2e3d7..5af422fd589 100644
--- a/admin/tool/mfa/tests/admin_setting_managemfa_test.php
+++ b/admin/tool/mfa/tests/admin_setting_managemfa_test.php
@@ -14,11 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-namespace tool_mfa\tests;
+namespace tool_mfa;
+use tool_mfa\tool_mfa_trait;
defined('MOODLE_INTERNAL') || die();
-
-require_once(__DIR__ . '/tool_mfa_testcase.php');
+require_once(__DIR__ . '/tool_mfa_trait.php');
/**
* Tests for MFA admin settings
@@ -29,8 +29,15 @@ require_once(__DIR__ . '/tool_mfa_testcase.php');
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class admin_setting_managemfa_test extends tool_mfa_testcase {
+class admin_setting_managemfa_test extends \advanced_testcase {
+ use tool_mfa_trait;
+
+ /**
+ * Tests getting the factor combinations
+ *
+ * @covers ::get_factor_combinations
+ */
public function test_get_factor_combinations_default() {
$namagemfa = new \tool_mfa\local\admin_setting_managemfa();
$factors = \tool_mfa\plugininfo\factor::get_enabled_factors();
@@ -39,7 +46,12 @@ class admin_setting_managemfa_test extends tool_mfa_testcase {
$this->assertEquals(0, count($combinations));
}
- public function test_get_factor_combinations_provider() {
+ /**
+ * Data provider for test_get_factor_combinations_with_data_provider().
+ *
+ * @return array
+ */
+ public function get_factor_combinations_provider() {
$provider = [];
$factors = [];
@@ -122,9 +134,10 @@ class admin_setting_managemfa_test extends tool_mfa_testcase {
}
/**
- * Tests getting the factor combinations
+ * Tests getting the factor combinations with data provider
*
- * @dataProvider test_get_factor_combinations_provider
+ * @covers ::get_factor_combinations
+ * @dataProvider get_factor_combinations_provider
* @param array $factorset configured factors
* @param int $combinationscount expected count of available combinations
*/
@@ -156,6 +169,11 @@ class admin_setting_managemfa_test extends tool_mfa_testcase {
$this->assertEquals($combinationscount, count($combinations));
}
+ /**
+ * Tests checking the factor combinations
+ *
+ * @covers ::get_factor_combinations
+ */
public function test_factor_combination_checker() {
$this->resetAfterTest();
$managemfa = new \tool_mfa\local\admin_setting_managemfa();
diff --git a/admin/tool/mfa/tests/manager_test.php b/admin/tool/mfa/tests/manager_test.php
index 1996007c737..1e976cb334e 100644
--- a/admin/tool/mfa/tests/manager_test.php
+++ b/admin/tool/mfa/tests/manager_test.php
@@ -14,10 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-namespace tool_mfa\tests;
+namespace tool_mfa;
+use tool_mfa\tool_mfa_trait;
defined('MOODLE_INTERNAL') || die();
-require_once(__DIR__ . '/tool_mfa_testcase.php');
+require_once(__DIR__ . '/tool_mfa_trait.php');
/**
* Tests for MFA manager class.
@@ -27,8 +28,16 @@ require_once(__DIR__ . '/tool_mfa_testcase.php');
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class manager_test extends tool_mfa_testcase {
+class manager_test extends \advanced_testcase {
+ use tool_mfa_trait;
+
+ /**
+ * Tests getting the factor total weight
+ *
+ * @covers ::get_total_weight
+ * @covers ::setup_user_factor
+ */
public function test_get_total_weight() {
$this->resetAfterTest(true);
@@ -69,6 +78,11 @@ class manager_test extends tool_mfa_testcase {
$this->assertEquals(300, \tool_mfa\manager::get_total_weight());
}
+ /**
+ * Tests getting the factor status
+ *
+ * @covers ::get_status
+ */
public function test_get_status() {
$this->resetAfterTest(true);
@@ -77,21 +91,21 @@ class manager_test extends tool_mfa_testcase {
$this->setUser($user);
// Check for fail status with no factors.
- $this->assertEquals(\tool_mfa\manager::get_status(), \tool_mfa\plugininfo\factor::STATE_FAIL);
+ $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_FAIL, \tool_mfa\manager::get_status());
// Now add a no input factor.
$this->set_factor_state('auth', 1, 100);
set_config('goodauth', 'manual', 'factor_auth');
// Check state is now passing.
- $this->assertEquals(\tool_mfa\manager::get_status(), \tool_mfa\plugininfo\factor::STATE_PASS);
+ $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_PASS, \tool_mfa\manager::get_status());
// Now add a failure state factor, and ensure that fail takes precedent.
$this->set_factor_state('email', 1, 100);
$factoremail = \tool_mfa\plugininfo\factor::get_factor('email');
$factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_FAIL);
- $this->assertEquals(\tool_mfa\manager::get_status(), \tool_mfa\plugininfo\factor::STATE_FAIL);
+ $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_FAIL, \tool_mfa\manager::get_status());
// Remove no input factor, and remove fail state by logging in/out. Simulates no data entered yet.
$this->setUser(null);
@@ -99,9 +113,14 @@ class manager_test extends tool_mfa_testcase {
$this->set_factor_state('auth', 0, 100);
$factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_UNKNOWN);
- $this->assertEquals(\tool_mfa\manager::get_status(), \tool_mfa\plugininfo\factor::STATE_NEUTRAL);
+ $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_NEUTRAL, \tool_mfa\manager::get_status());
}
+ /**
+ * Tests checking if passed enough factors
+ *
+ * @covers ::passed_enough_factors
+ */
public function test_passed_enough_factors() {
$this->resetAfterTest(true);
@@ -110,27 +129,27 @@ class manager_test extends tool_mfa_testcase {
$this->setUser($user);
// Check when no factors are setup.
- $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), false);
+ $this->assertEquals(false, \tool_mfa\manager::passed_enough_factors());
// Setup a no input factor.
$this->set_factor_state('auth', 1, 100);
set_config('goodauth', 'manual', 'factor_auth');
// Check that is enough to pass.
- $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), true);
+ $this->assertEquals(true, \tool_mfa\manager::passed_enough_factors());
// Lower the weight of the factor.
$this->set_factor_state('auth', 1, 75);
- $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), false);
+ $this->assertEquals(false, \tool_mfa\manager::passed_enough_factors());
// Add another factor to get enough weight to pass, but dont set pass state yet.
$this->set_factor_state('email', 1, 100);
$factoremail = \tool_mfa\plugininfo\factor::get_factor('email');
- $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), false);
+ $this->assertEquals(false, \tool_mfa\manager::passed_enough_factors());
// Now pass the factor and check weight.
$factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
- $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), true);
+ $this->assertEquals(true, \tool_mfa\manager::passed_enough_factors());
}
/**
@@ -154,15 +173,13 @@ class manager_test extends tool_mfa_testcase {
['/admin/tool/mfa/action.php', 'http://test.server/parent/directory', true],
['/', 'http://test.server/parent/directory', true, ['url' => $badparam1]],
['/', 'http://test.server/parent/directory', true, ['url' => $badparam2]],
- ['/admin/tool/securityquestions/set_responses.php', 'http://test.server', false],
- ['/admin/tool/securityquestions/set_responses.php', 'http://test.server', false, ['delete' => 1]],
- ['/admin/tool/securityquestions/randompage.php', 'http://test.server', true, ['delete' => 1]],
];
}
/**
* Tests whether it should require mfa
*
+ * @covers ::should_require_mfa
* @param string $urlstring
* @param string $webroot
* @param bool $status
@@ -176,9 +193,14 @@ class manager_test extends tool_mfa_testcase {
$this->setUser($user);
$CFG->wwwroot = $webroot;
$url = new \moodle_url($urlstring, $params);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), $status);
+ $this->assertEquals($status, \tool_mfa\manager::should_require_mfa($url, false));
}
+ /**
+ * Tests whether it should require the mfa checks
+ *
+ * @covers ::should_require_mfa
+ */
public function test_should_require_mfa_checks() {
// Setup test and user.
global $CFG;
@@ -189,61 +211,68 @@ class manager_test extends tool_mfa_testcase {
// Upgrade checks.
$this->setAdminUser();
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT);
+ // Mark the site as upgraded so it will not fail when running the unittest as a whole.
+ $CFG->allversionshash = \core_component::get_all_versions_hash();
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$oldhash = $CFG->allversionshash;
$CFG->allversionshash = 'abc';
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$CFG->allversionshash = $oldhash;
$upgradesettings = new \moodle_url('/admin/upgradesettings.php');
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($upgradesettings, false), \tool_mfa\manager::NO_REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($upgradesettings, false));
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
// Admin not setup.
$this->setUser($user);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$CFG->adminsetuppending = 1;
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$CFG->adminsetuppending = 0;
// Check prevent_redirect.
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, true), \tool_mfa\manager::NO_REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
+ $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, true));
// User not setup properly.
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$notsetup = clone($user);
unset($notsetup->firstname);
$this->setUser($notsetup);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$this->setUser($user);
// Enrolment.
$enrolurl = new \moodle_url('/enrol/index.php');
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($enrolurl, false), \tool_mfa\manager::NO_REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
+ $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($enrolurl, false));
// Guest User.
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$this->setGuestUser();
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$this->setUser($user);
// Forced password changes.
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
set_user_preference('auth_forcepasswordchange', true);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
set_user_preference('auth_forcepasswordchange', false);
// Login as check.
$user2 = $this->getDataGenerator()->create_user();
$syscontext = \context_system::instance();
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$this->setAdminUser();
\core\session\manager::loginas($user2->id, $syscontext, false);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
$this->setUser($user);
}
+ /**
+ * Tests should require the mfa redirection loop
+ *
+ * @covers ::should_require_mfa
+ */
public function test_should_require_mfa_redirection_loop() {
// Setup test and user.
global $CFG, $SESSION;
@@ -257,44 +286,50 @@ class manager_test extends tool_mfa_testcase {
$url = new \moodle_url('/');
// Test you get three redirs then exception.
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
// Set count to threshold.
$SESSION->mfa_redir_count = 5;
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT_EXCEPTION);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT_EXCEPTION, \tool_mfa\manager::should_require_mfa($url, false));
// Reset session vars.
unset($SESSION->mfa_redir_referer);
unset($SESSION->mfa_redir_count);
// Test 4 different redir urls.
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
$_SERVER['HTTP_REFERER'] = 'http://phpunit.test/2';
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
$_SERVER['HTTP_REFERER'] = 'http://phpunit3.test/3';
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
$_SERVER['HTTP_REFERER'] = 'http://phpunit4.test/4';
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
// Reset session vars.
unset($SESSION->mfa_redir_referer);
unset($SESSION->mfa_redir_count);
// Test 6 then jump to new referer (5 + 1 to set the first time).
$_SERVER['HTTP_REFERER'] = 'http://phpunit.test';
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
$_SERVER['HTTP_REFERER'] = 'http://phpunit.test/2';
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
// Now test that going back to original URL doesnt cause exception.
$_SERVER['HTTP_REFERER'] = 'http://phpunit.test';
- $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT);
+ $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
}
+ /**
+ * Tests checking for possible setup factor
+ *
+ * @covers ::possible_factor_setup
+ * @covers ::setup_user_factor
+ */
public function test_possible_factor_setup() {
// Setup test and user.
$this->resetAfterTest(true);
@@ -325,6 +360,11 @@ class manager_test extends tool_mfa_testcase {
set_config('enabled', 0, 'factor_admin');
}
+ /**
+ * Tests checking if a factor is ready
+ *
+ * @covers ::is_ready
+ */
public function test_is_ready() {
// Setup test and user.
global $CFG;
@@ -360,6 +400,12 @@ class manager_test extends tool_mfa_testcase {
set_config('enabled', 1, 'factor_nosetup');
}
+ /**
+ * Tests core hooks
+ *
+ * @covers ::mfa_config_hook_test
+ * @covers ::mfa_login_hook_test
+ */
public function test_core_hooks() {
// Setup test and user.
global $CFG, $SESSION;
@@ -374,6 +420,11 @@ class manager_test extends tool_mfa_testcase {
$this->assertTrue($SESSION->mfa_login_hook_test);
}
+ /**
+ * Tests circular redirect auth
+ *
+ * @covers ::should_require_mfa
+ */
public function test_circular_redirect_auth() {
// Setup test and user.
$this->resetAfterTest(true);
@@ -388,7 +439,7 @@ class manager_test extends tool_mfa_testcase {
$this->assertEquals(\tool_mfa\manager::REDIRECT,
\tool_mfa\manager::should_require_mfa($baseurl, false));
- // Now hammer it up to the threshold to emulate a repeated force browse from auth.php
+ // Now hammer it up to the threshold to emulate a repeated force browse from auth.php.
for ($i = 0; $i < \tool_mfa\manager::REDIR_LOOP_THRESHOLD; $i++) {
\tool_mfa\manager::should_require_mfa($baseurl, false);
}
diff --git a/admin/tool/mfa/tests/object_factor_base_test.php b/admin/tool/mfa/tests/object_factor_base_test.php
index aa658d175a2..a2ab262b167 100644
--- a/admin/tool/mfa/tests/object_factor_base_test.php
+++ b/admin/tool/mfa/tests/object_factor_base_test.php
@@ -13,9 +13,12 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-namespace tool_mfa\tests;
-require_once(__DIR__ . '/tool_mfa_testcase.php');
+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.
@@ -25,7 +28,16 @@ require_once(__DIR__ . '/tool_mfa_testcase.php');
* @copyright 2023 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class object_factor_base_test extends tool_mfa_testcase {
+class object_factor_base_test extends \advanced_testcase {
+
+ use tool_mfa_trait;
+
+ /**
+ * Test deleting user's configured factors
+ *
+ * @covers ::setup_user_factor
+ * @return void
+ */
public function test_revoke_user_factor() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
diff --git a/admin/tool/mfa/tests/plugininfo_factor_test.php b/admin/tool/mfa/tests/plugininfo_factor_test.php
index 4e5aa27e616..61e57345ef8 100644
--- a/admin/tool/mfa/tests/plugininfo_factor_test.php
+++ b/admin/tool/mfa/tests/plugininfo_factor_test.php
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+namespace tool_mfa;
+
/**
* Tests for plugininfo.
*
@@ -22,8 +24,18 @@
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class plugininfo_factor_test extends advanced_testcase {
+class plugininfo_factor_test extends \advanced_testcase {
+ /**
+ * Tests getting next user factor
+ *
+ * @covers ::get_next_user_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() {
$this->resetAfterTest(true);
@@ -33,7 +45,7 @@ class plugininfo_factor_test extends advanced_testcase {
$this->setUser($user);
// Test that with no enabled factors, fallback is returned.
- $this->assertEquals(\tool_mfa\plugininfo\factor::get_next_user_factor()->name, 'fallback');
+ $this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_factor()->name);
// Setup enabled totp factor for user.
set_config('enabled', 1, 'factor_totp');
@@ -45,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(\tool_mfa\plugininfo\factor::get_next_user_factor()->name, 'totp');
+ $this->assertEquals('totp', \tool_mfa\plugininfo\factor::get_next_user_factor()->name);
// Now pass this factor, check for fallback.
$totpfactor->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
- $this->assertEquals(\tool_mfa\plugininfo\factor::get_next_user_factor()->name, 'fallback');
+ $this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_factor()->name);
// Add in a no-input factor.
set_config('enabled', 1, 'factor_auth');
@@ -61,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(\tool_mfa\plugininfo\factor::get_next_user_factor()->name, 'fallback');
+ $this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_factor()->name);
}
}
diff --git a/admin/tool/mfa/tests/secret_manager_test.php b/admin/tool/mfa/tests/secret_manager_test.php
index 6ef09c92292..5ad87b21db0 100644
--- a/admin/tool/mfa/tests/secret_manager_test.php
+++ b/admin/tool/mfa/tests/secret_manager_test.php
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-namespace tool_mfa\tests;
+namespace tool_mfa;
/**
* Tests for MFA secret manager class.
@@ -26,6 +26,11 @@ namespace tool_mfa\tests;
*/
class secret_manager_test extends \advanced_testcase {
+ /**
+ * Tests create factor's secret
+ *
+ * @covers ::create_secret
+ */
public function test_create_secret() {
global $DB;
@@ -75,6 +80,12 @@ class secret_manager_test extends \advanced_testcase {
$DB->delete_records('tool_mfa_secrets', []);
}
+ /**
+ * Tests add factor's secret to database
+ *
+ * @covers ::get_record
+ * @covers ::delete_records
+ */
public function test_add_secret_to_db() {
global $DB, $USER;
@@ -107,6 +118,12 @@ class secret_manager_test extends \advanced_testcase {
$this->assertEquals($sid, $record->sessionid);
}
+ /**
+ * Tests validating factor's secret
+ *
+ * @covers ::validate_secret
+ * @covers ::create_secret
+ */
public function test_validate_secret() {
global $DB;
@@ -161,6 +178,13 @@ class secret_manager_test extends \advanced_testcase {
$DB->delete_records('tool_mfa_secrets', []);
}
+ /**
+ * Tests revoking factor's secret
+ *
+ * @covers ::validate_secret
+ * @covers ::create_secret
+ * @covers ::revoke_secret
+ */
public function test_revoke_secret() {
global $DB, $SESSION;
@@ -186,6 +210,12 @@ class secret_manager_test extends \advanced_testcase {
$this->assertEquals(\tool_mfa\local\secret_manager::NONVALID, $secman->validate_secret('nonvalid'));
}
+ /**
+ * Tests checking if factor has an active secret
+ *
+ * @covers ::create_secret
+ * @covers ::revoke_secret
+ */
public function test_has_active_secret() {
global $DB;
diff --git a/admin/tool/mfa/tests/tool_mfa_testcase.php b/admin/tool/mfa/tests/tool_mfa_trait.php
similarity index 87%
rename from admin/tool/mfa/tests/tool_mfa_testcase.php
rename to admin/tool/mfa/tests/tool_mfa_trait.php
index 5d15a5a9908..0d24de4a739 100644
--- a/admin/tool/mfa/tests/tool_mfa_testcase.php
+++ b/admin/tool/mfa/tests/tool_mfa_trait.php
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-namespace tool_mfa\tests;
+namespace tool_mfa;
defined('MOODLE_INTERNAL') || die();
@@ -23,7 +23,7 @@ require_once($CFG->libdir.'/adminlib.php');
require_once(__DIR__ . '/../lib.php');
/**
- * Base testcase class for testing this plugin
+ * Trait for testing this plugin
*
* @package tool_mfa
* @author Mikhail Golenkov
@@ -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
*/
-abstract class tool_mfa_testcase extends \advanced_testcase {
+trait tool_mfa_trait {
/**
* Sets the state of the factor, in particular the weight and whether it is enabled
@@ -40,7 +40,7 @@ abstract class tool_mfa_testcase extends \advanced_testcase {
* @param int $enabled
* @param int $weight
*/
- protected function set_factor_state($factorname, $enabled = 0, $weight = 100) {
+ public function set_factor_state($factorname, $enabled = 0, $weight = 100) {
$factor = \tool_mfa\plugininfo\factor::get_factor($factorname);
$this->set_factor_config($factor, 'enabled', $enabled);
$this->set_factor_config($factor, 'weight', $weight);
@@ -53,7 +53,7 @@ abstract class tool_mfa_testcase extends \advanced_testcase {
* @param string $key
* @param mixed $value
*/
- protected function set_factor_config($factor, $key, $value) {
+ public function set_factor_config($factor, $key, $value) {
\tool_mfa\manager::set_factor_config([$key => $value], 'factor_' . $factor->name);
if ($key == 'enabled') {
diff --git a/admin/tool/mfa/version.php b/admin/tool/mfa/version.php
index 69e6ad8649a..e327895a96b 100644
--- a/admin/tool/mfa/version.php
+++ b/admin/tool/mfa/version.php
@@ -25,9 +25,8 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2023031600; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->release = 2023031600; // Same as version.
-$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required.
+$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->release = 2023080100; // Same as version.
+$plugin->requires = 2023042400.00; // Supports from 4.2.
$plugin->component = 'tool_mfa';
$plugin->maturity = MATURITY_STABLE;
-$plugin->supported = [35, 401];
diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php
index cf49ea22f1f..0a13f414564 100644
--- a/lib/classes/plugin_manager.php
+++ b/lib/classes/plugin_manager.php
@@ -2050,7 +2050,7 @@ class core_plugin_manager {
'installaddon', 'langimport', 'licensemanager', 'log', 'lp', 'lpimportcsv', 'lpmigrate', 'messageinbound',
'mobile', 'moodlenet', 'multilangupgrade', 'monitor', 'oauth2', 'phpunit', 'policy', 'profiling', 'recyclebin',
'replace', 'spamcleaner', 'task', 'templatelibrary', 'uploadcourse', 'uploaduser', 'unsuproles',
- 'usertours', 'xmldb'
+ 'usertours', 'xmldb', 'mfa'
),
'webservice' => array(