diff --git a/admin/tool/mfa/.github/workflows/ci.yml b/admin/tool/mfa/.github/workflows/ci.yml deleted file mode 100644 index 4d92066f98fa..000000000000 --- a/admin/tool/mfa/.github/workflows/ci.yml +++ /dev/null @@ -1,15 +0,0 @@ -# .github/workflows/ci.yml -name: ci - -on: [push, pull_request] - -jobs: - test: - uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main - secrets: - # Required if you plan to publish (uncomment the below) - moodle_org_token: ${{ secrets.MOODLE_ORG_TOKEN }} - with: - #Grunt fails due to CSS styling needing an !important. - disable_grunt: true - release_branches: master diff --git a/admin/tool/mfa/README.md b/admin/tool/mfa/README.md deleted file mode 100644 index cf9e573db6d3..000000000000 --- a/admin/tool/mfa/README.md +++ /dev/null @@ -1,12 +0,0 @@ -#NOTE: This master branch has been deprecated. Please see the table below for the correct supported branches. - -## Branches - -| Version | Branch | Patches | -|-----------------|--------------|----------------------| -| Moodle 4.0 - 4.2| MOODLE_400_STABLE | None | -| Moodle 3.8 -3.9 | MOODLE_35_STABLE | None | -| Moodle 3.7 | MOODLE_35_STABLE | MDL-66340 | -| Moodle 3.5-3.6 | MOODLE_35_STABLE | MDL-66340, MDL-60470 | -| Totara 12-15 | MOODLE_35_STABLE | MDL-66340, MDL-60470 | - diff --git a/admin/tool/mfa/classes/local/form/setup_factor_form.php b/admin/tool/mfa/classes/local/form/setup_factor_form.php index 45f0f9a3a69c..5ef7f47ae388 100644 --- a/admin/tool/mfa/classes/local/form/setup_factor_form.php +++ b/admin/tool/mfa/classes/local/form/setup_factor_form.php @@ -75,8 +75,7 @@ public function definition_after_data() { } /** - * In newer versions of Totara with consistent cleaning enabled we need to ensure to mark static elements - * as "xss safe". Or in Totara's ideal world to use 'html' if form-like display is not required. + * Form elements clean up * * @param \HTML_QuickForm $mform * @return void diff --git a/admin/tool/mfa/classes/manager.php b/admin/tool/mfa/classes/manager.php index 659017b1697f..ddce4f18735c 100644 --- a/admin/tool/mfa/classes/manager.php +++ b/admin/tool/mfa/classes/manager.php @@ -486,7 +486,7 @@ public static function should_require_mfa($url, $preventredirect) { // Site policy. if (isset($USER->policyagreed) && !$USER->policyagreed) { - // Privacy classes may not exist in older Moodles/Totara. + // Privacy classes may not exist in older Moodles. if (class_exists('\core_privacy\local\sitepolicy\manager')) { $manager = new \core_privacy\local\sitepolicy\manager(); $policyurl = $manager->get_redirect_url(false); @@ -596,7 +596,7 @@ public static function sleep_timer() { $duration = 0.05; } set_user_preference('mfa_sleep_duration', $duration, $USER); - sleep($duration); + sleep((int)$duration); } /** diff --git a/admin/tool/mfa/db/upgrade.php b/admin/tool/mfa/db/upgrade.php deleted file mode 100644 index ad6a2afdaa7c..000000000000 --- a/admin/tool/mfa/db/upgrade.php +++ /dev/null @@ -1,136 +0,0 @@ -. - -/** - * MFA upgrade library. - * - * @package tool_mfa - * @copyright 2020 Peter Burnett - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -/** - * Function to upgrade tool_mfa. - * - * @param int $oldversion the version we are upgrading from - * @return bool result - */ -function xmldb_tool_mfa_upgrade($oldversion) { - global $DB; - - $dbman = $DB->get_manager(); - - if ($oldversion < 2020050700) { - // Define field lockcounter to be added to tool_mfa. - $table = new xmldb_table('tool_mfa'); - $field = new xmldb_field('lockcounter', XMLDB_TYPE_INTEGER, '5', null, XMLDB_NOTNULL, null, '0', 'revoked'); - - // Conditionally launch add field lockcounter. - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - - // MFA savepoint reached. - upgrade_plugin_savepoint(true, 2020050700, 'tool', 'mfa'); - } - - if ($oldversion < 2020051900) { - // Define index userid (not unique) to be added to tool_mfa. - $table = new xmldb_table('tool_mfa'); - $index = new xmldb_index('userid', XMLDB_INDEX_NOTUNIQUE, ['userid']); - - // Conditionally launch add index userid. - if (!$dbman->index_exists($table, $index)) { - $dbman->add_index($table, $index); - } - - // Define index factor (not unique) to be added to tool_mfa. - $table = new xmldb_table('tool_mfa'); - $index = new xmldb_index('factor', XMLDB_INDEX_NOTUNIQUE, ['factor']); - - // Conditionally launch add index factor. - if (!$dbman->index_exists($table, $index)) { - $dbman->add_index($table, $index); - } - - // Define index lockcounter (not unique) to be added to tool_mfa. - $table = new xmldb_table('tool_mfa'); - $index = new xmldb_index('lockcounter', XMLDB_INDEX_NOTUNIQUE, ['userid', 'factor', 'lockcounter']); - - // Conditionally launch add index lockcounter. - if (!$dbman->index_exists($table, $index)) { - $dbman->add_index($table, $index); - } - - // Mfa savepoint reached. - upgrade_plugin_savepoint(true, 2020051900, 'tool', 'mfa'); - } - - if ($oldversion < 2020090300) { - // Define table tool_mfa_secrets to be created. - $table = new xmldb_table('tool_mfa_secrets'); - - // Adding fields to table tool_mfa_secrets. - $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); - $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); - $table->add_field('factor', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null); - $table->add_field('secret', XMLDB_TYPE_CHAR, '1333', null, XMLDB_NOTNULL, null, null); - $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '15', null, XMLDB_NOTNULL, null, null); - $table->add_field('expiry', XMLDB_TYPE_INTEGER, '15', null, XMLDB_NOTNULL, null, null); - $table->add_field('revoked', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); - $table->add_field('sessionid', XMLDB_TYPE_CHAR, '100', null, null, null, null); - - // Adding keys to table tool_mfa_secrets. - $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); - $table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']); - - // Adding indexes to table tool_mfa_secrets. - $table->add_index('factor', XMLDB_INDEX_NOTUNIQUE, ['factor']); - $table->add_index('expiry', XMLDB_INDEX_NOTUNIQUE, ['expiry']); - - // Conditionally launch create table for tool_mfa_secrets. - if (!$dbman->table_exists($table)) { - $dbman->create_table($table); - } - - // Mfa savepoint reached. - upgrade_plugin_savepoint(true, 2020090300, 'tool', 'mfa'); - } - - if ($oldversion < 2021021900) { - // Define table tool_mfa_auth to be created. - $table = new xmldb_table('tool_mfa_auth'); - - // Adding fields to table tool_mfa_auth. - $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); - $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); - $table->add_field('lastverified', XMLDB_TYPE_INTEGER, '15', null, XMLDB_NOTNULL, null, '0'); - - // Adding keys to table tool_mfa_auth. - $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); - $table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']); - - // Conditionally launch create table for tool_mfa_auth. - if (!$dbman->table_exists($table)) { - $dbman->create_table($table); - } - - // Mfa savepoint reached. - upgrade_plugin_savepoint(true, 2021021900, 'tool', 'mfa'); - } - - return true; -} diff --git a/admin/tool/mfa/example1 b/admin/tool/mfa/example1 deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/admin/tool/mfa/example2 b/admin/tool/mfa/example2 deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/admin/tool/mfa/factor/admin/classes/privacy/provider.php b/admin/tool/mfa/factor/admin/classes/privacy/provider.php index 7561d43b434f..1eab86049a65 100644 --- a/admin/tool/mfa/factor/admin/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/admin/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_admin\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/admin/version.php b/admin/tool/mfa/factor/admin/version.php index 393f9b321c32..75ea27e80231 100644 --- a/admin/tool/mfa/factor/admin/version.php +++ b/admin/tool/mfa/factor/admin/version.php @@ -26,8 +26,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019102400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_admin'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; diff --git a/admin/tool/mfa/factor/auth/classes/privacy/provider.php b/admin/tool/mfa/factor/auth/classes/privacy/provider.php index a0f8c208ac44..bb9498c9f7e1 100644 --- a/admin/tool/mfa/factor/auth/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/auth/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_auth\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/auth/version.php b/admin/tool/mfa/factor/auth/version.php index e3a5318509d6..253c803f7b6f 100644 --- a/admin/tool/mfa/factor/auth/version.php +++ b/admin/tool/mfa/factor/auth/version.php @@ -26,8 +26,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2021020500; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_auth'; $plugin->release = 2021020500; $plugin->maturity = MATURITY_STABLE; diff --git a/admin/tool/mfa/factor/capability/classes/privacy/provider.php b/admin/tool/mfa/factor/capability/classes/privacy/provider.php index 99ce4bb94d00..1d71426ee6bf 100644 --- a/admin/tool/mfa/factor/capability/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/capability/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_capability\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/capability/version.php b/admin/tool/mfa/factor/capability/version.php index ee17d8f22511..7ee9ced1a611 100644 --- a/admin/tool/mfa/factor/capability/version.php +++ b/admin/tool/mfa/factor/capability/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020071400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_capability'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/cohort/classes/factor.php b/admin/tool/mfa/factor/cohort/classes/factor.php index 61b5ad9bed1a..d4e57a2decf0 100644 --- a/admin/tool/mfa/factor/cohort/classes/factor.php +++ b/admin/tool/mfa/factor/cohort/classes/factor.php @@ -125,15 +125,29 @@ public function get_summary_condition() { $selectedcohorts = get_config('factor_cohort', 'cohorts'); if (empty($selectedcohorts)) { return get_string('summarycondition', 'factor_cohort', get_string('none')); - } else { - $selectedcohorts = explode(',', $selectedcohorts); } - $names = []; - foreach ($selectedcohorts as $cohort) { + + return get_string('summarycondition', 'factor_cohort', $this->get_cohorts($selectedcohorts)); + } + + /** + * Get string lang for the cohorts. + * + * @param string $selectedcohorts + * @return string + */ + public function get_cohorts(string $selectedcohorts) : string { + global $DB; + + $cohorts = []; + foreach (explode(',', $selectedcohorts) as $cohort) { + if ($cohort === 'admin') { + $cohorts[] = get_string('administrator'); + } else { $record = $DB->get_record('cohort', ['id' => $cohort]); - $names[] = $record->name; + $cohorts[] = $record->name; + } } - $string = implode(', ', $names); - return get_string('summarycondition', 'factor_cohort', $string); + return implode(', ', $cohorts); } } diff --git a/admin/tool/mfa/factor/cohort/classes/privacy/provider.php b/admin/tool/mfa/factor/cohort/classes/privacy/provider.php index 3e1c2de7759d..813c9b328a11 100644 --- a/admin/tool/mfa/factor/cohort/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/cohort/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_cohort\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/cohort/tests/factor_test.php b/admin/tool/mfa/factor/cohort/tests/factor_test.php new file mode 100644 index 000000000000..80d69df9dcd6 --- /dev/null +++ b/admin/tool/mfa/factor/cohort/tests/factor_test.php @@ -0,0 +1,74 @@ +. + +namespace factor_cohort; + +/** + * Tests for cohort factor. + * + * @covers \factor_cohort\factor + * @package factor_cohort + * @copyright 2023 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class factor_test extends \advanced_testcase { + + /** + * Tests getting the summary condition + * + * @covers ::get_summary_condition + * @covers ::get_cohorts + */ + public function test_get_summary_condition() { + global $DB, $USER; + $this->resetAfterTest(true); + + set_config('enabled', 1, 'factor_cohort'); + $cohortfactor = \tool_mfa\plugininfo\factor::get_factor('cohort'); + + // Create a cohort. + $cohortid = $DB->insert_record('cohort', [ + 'idnumber' => null, + 'name' => 'test', + 'contextid' => \context_system::instance()->id, + 'description' => '', + 'descriptionformat' => FORMAT_HTML, + 'visible' => 1, + 'component' => '', + 'theme' => '', + 'timecreated' => time(), + 'timemodified' => time(), + ]); + + // Add member to created cohort. + $DB->insert_record('cohort_members', [ + 'cohortid' => $cohortid, + 'userid' => $USER->id, + 'timeadded' => time() + ]); + + // Add the created cohortid into factor_cohort plugin. + set_config('cohorts', $cohortid, 'factor_cohort'); + + $selectedcohorts = get_config('factor_cohort', 'cohorts'); + $this->assertTrue( + strpos( + $cohortfactor->get_summary_condition(), + $cohortfactor->get_cohorts($selectedcohorts) + ) !== false + ); + } +} diff --git a/admin/tool/mfa/factor/cohort/version.php b/admin/tool/mfa/factor/cohort/version.php index ec9b3879ff29..3b593b11acbe 100644 --- a/admin/tool/mfa/factor/cohort/version.php +++ b/admin/tool/mfa/factor/cohort/version.php @@ -26,8 +26,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022101100; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_cohort'; $plugin->release = 'v0.2'; $plugin->maturity = MATURITY_STABLE; diff --git a/admin/tool/mfa/factor/email/classes/factor.php b/admin/tool/mfa/factor/email/classes/factor.php index 7379bfabba5c..6839f834aa17 100644 --- a/admin/tool/mfa/factor/email/classes/factor.php +++ b/admin/tool/mfa/factor/email/classes/factor.php @@ -246,6 +246,16 @@ private function check_verification_code($enteredcode) { return false; } + /** + * Access the check_verification_code() + * + * @param string $code + * @return bool + */ + public function unittest_verification_code($code) : bool { + return $this->check_verification_code($code); + } + /** * Cleans up email records once MFA passed. * diff --git a/admin/tool/mfa/factor/email/classes/privacy/provider.php b/admin/tool/mfa/factor/email/classes/privacy/provider.php index c7f2f22292b4..7f0c36ab0f58 100644 --- a/admin/tool/mfa/factor/email/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/email/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_email\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/email/email.php b/admin/tool/mfa/factor/email/email.php index c0e83ce23e45..a61f349c5e62 100644 --- a/admin/tool/mfa/factor/email/email.php +++ b/admin/tool/mfa/factor/email/email.php @@ -80,7 +80,7 @@ // Suspend user account. if (get_config('factor_email', 'suspend')) { - $DB->set_field('user', 'suspended', 1, ['id' => $userid]); + $DB->set_field('user', 'suspended', 1, ['id' => $user->id]); } $message = get_string('email:revokesuccess', 'factor_email', fullname($user)); diff --git a/admin/tool/mfa/factor/email/tests/factor_test.php b/admin/tool/mfa/factor/email/tests/factor_test.php new file mode 100644 index 000000000000..a5c1c2f2849a --- /dev/null +++ b/admin/tool/mfa/factor/email/tests/factor_test.php @@ -0,0 +1,80 @@ +. + +namespace factor_email; + +/** + * Tests for email factor. + * + * @covers \factor_email\factor + * @package factor_email + * @copyright 2023 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class factor_test extends \advanced_testcase { + + /** + * Tests checking verification code + * + * @covers ::unittest_verification_code + * @covers ::post_pass_state + */ + public function test_unittest_verification_code() { + global $DB, $USER; + $this->resetAfterTest(true); + + // Assigned email to be used in getting the email factor. + $USER->email = 'user@mail.com'; + + set_config('enabled', 1, 'factor_email'); + $emailfactor = \tool_mfa\plugininfo\factor::get_factor('email'); + + // Testing with current timecreated. + $newcode = random_int(100000, 999999); + $instanceid = $DB->insert_record('tool_mfa', [ + 'userid' => $USER->id, + 'factor' => 'email', + 'secret' => $newcode, + 'label' => 'unittest', + 'timecreated' => time(), + 'timemodified' => time(), + 'lastverified' => time(), + 'revoked' => 0, + ]); + + $data = $DB->get_record('tool_mfa', ['id' => $instanceid]); + $this->assertTrue($emailfactor->unittest_verification_code($data->secret)); + + // Update the data to test with really old timecreated. + $DB->update_record('tool_mfa', [ + 'id' => $instanceid, + 'timecreated' => time() - 1689657581, + 'timemodified' => time() - 1689657581, + 'lastverified' => time() - 1689657581, + 'revoked' => 0, + ]); + + $data = $DB->get_record('tool_mfa', ['id' => $instanceid]); + $this->assertFalse($emailfactor->unittest_verification_code($data->secret)); + + // Cleans up email records once MFA passed. + $emailfactor->post_pass_state(); + + // Check if the email records have been deleted. + $data = $DB->count_records('tool_mfa', ['factor' => 'email']); + $this->assertEquals(0, $data); + } +} diff --git a/admin/tool/mfa/factor/email/version.php b/admin/tool/mfa/factor/email/version.php index 7f60b349879d..7400c4af34cb 100644 --- a/admin/tool/mfa/factor/email/version.php +++ b/admin/tool/mfa/factor/email/version.php @@ -26,8 +26,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019102400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_email'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; diff --git a/admin/tool/mfa/factor/grace/classes/privacy/provider.php b/admin/tool/mfa/factor/grace/classes/privacy/provider.php index 86a20054e0d4..11b966d014a7 100644 --- a/admin/tool/mfa/factor/grace/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/grace/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_grace\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/grace/tests/factor_test.php b/admin/tool/mfa/factor/grace/tests/factor_test.php index f5d8b86ae6cd..140f29b7b266 100644 --- a/admin/tool/mfa/factor/grace/tests/factor_test.php +++ b/admin/tool/mfa/factor/grace/tests/factor_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 factor_grace\tests; +namespace factor_grace; /** * Tests for grace factor. @@ -26,6 +26,12 @@ */ class factor_test extends \advanced_testcase { + /** + * Test affecting factors + * + * @covers ::get_affecting_factors + * @return void + */ public function test_affecting_factors() { $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); diff --git a/admin/tool/mfa/factor/grace/version.php b/admin/tool/mfa/factor/grace/version.php index 6af7fcf345b6..1fe8436ef807 100644 --- a/admin/tool/mfa/factor/grace/version.php +++ b/admin/tool/mfa/factor/grace/version.php @@ -26,8 +26,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022020401; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_grace'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; diff --git a/admin/tool/mfa/factor/iprange/classes/privacy/provider.php b/admin/tool/mfa/factor/iprange/classes/privacy/provider.php index 517e4d0fbac8..9526fb6bd0cf 100644 --- a/admin/tool/mfa/factor/iprange/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/iprange/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_iprange\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/iprange/version.php b/admin/tool/mfa/factor/iprange/version.php index 59369753e98f..1836da122499 100644 --- a/admin/tool/mfa/factor/iprange/version.php +++ b/admin/tool/mfa/factor/iprange/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019102400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_iprange'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; diff --git a/admin/tool/mfa/factor/nosetup/classes/privacy/provider.php b/admin/tool/mfa/factor/nosetup/classes/privacy/provider.php index 934f50e457a7..b1f4da0bed73 100644 --- a/admin/tool/mfa/factor/nosetup/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/nosetup/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_nosetup\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/nosetup/version.php b/admin/tool/mfa/factor/nosetup/version.php index 5508c8d41fb5..4435268d0eaa 100644 --- a/admin/tool/mfa/factor/nosetup/version.php +++ b/admin/tool/mfa/factor/nosetup/version.php @@ -26,8 +26,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020042302; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_nosetup'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; diff --git a/admin/tool/mfa/factor/role/classes/factor.php b/admin/tool/mfa/factor/role/classes/factor.php index e84ad33fadc3..f9b67e34431b 100644 --- a/admin/tool/mfa/factor/role/classes/factor.php +++ b/admin/tool/mfa/factor/role/classes/factor.php @@ -143,21 +143,31 @@ public function get_summary_condition() { $selectedroles = get_config('factor_role', 'roles'); if (empty($selectedroles)) { return get_string('summarycondition', 'factor_role', get_string('none')); - } else { - $selectedroles = explode(',', $selectedroles); } - $names = []; - foreach ($selectedroles as $role) { + return get_string('summarycondition', 'factor_role', $this->get_roles($selectedroles)); + } + + /** + * Get string lang for the roles. + * + * @param string $selectedroles + * @return string + */ + public function get_roles(string $selectedroles) : string { + global $DB; + + $roles = []; + foreach (explode(',', $selectedroles) as $role) { if ($role === 'admin') { - $names[] = get_string('administrator'); + $roles[] = get_string('administrator'); } else { $record = $DB->get_record('role', ['id' => $role]); - $names[] = role_get_name($record); + if (!empty($record)) { + $roles[] = role_get_name($record); + } } } - - $string = implode(', ', $names); - return get_string('summarycondition', 'factor_role', $string); + return implode(', ', $roles); } } diff --git a/admin/tool/mfa/factor/role/classes/privacy/provider.php b/admin/tool/mfa/factor/role/classes/privacy/provider.php index 4ddac45b3bfb..3c42264412c2 100644 --- a/admin/tool/mfa/factor/role/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/role/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_role\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/role/tests/factor_test.php b/admin/tool/mfa/factor/role/tests/factor_test.php new file mode 100644 index 000000000000..7a93d9bbcd87 --- /dev/null +++ b/admin/tool/mfa/factor/role/tests/factor_test.php @@ -0,0 +1,90 @@ +. + +namespace factor_role; + +/** + * Tests for role factor. + * + * @covers \factor_role\factor + * @package factor_role + * @copyright 2023 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class factor_test extends \advanced_testcase { + + /** + * Tests getting the summary condition + * + * @covers ::get_summary_condition + * @covers ::get_roles + */ + public function test_get_summary_condition() { + $this->resetAfterTest(true); + + set_config('enabled', 1, 'factor_role'); + $rolefactor = \tool_mfa\plugininfo\factor::get_factor('role'); + + // Admin is disabled by default in this role. + $selectedroles = get_config('factor_role', 'roles'); + $this->assertTrue( + strpos( + $rolefactor->get_summary_condition(), + $rolefactor->get_roles($selectedroles) + ) !== false + ); + + // Disabled role factor for managers. + set_config('roles', '1', 'factor_role'); + + $selectedroles = get_config('factor_role', 'roles'); + $this->assertTrue( + strpos( + $rolefactor->get_summary_condition(), + $rolefactor->get_roles($selectedroles) + ) !== false + ); + + // Disabled role factor for students. + set_config('roles', '5', 'factor_role'); + + $selectedroles = get_config('factor_role', 'roles'); + $this->assertTrue( + strpos( + $rolefactor->get_summary_condition(), + $rolefactor->get_roles($selectedroles) + ) !== false + ); + + // Disabled role factor for admins, managers and students. + set_config('roles', 'admin,1,5', 'factor_role'); + + $selectedroles = get_config('factor_role', 'roles'); + $this->assertTrue( + strpos( + $rolefactor->get_summary_condition(), + $rolefactor->get_roles($selectedroles) + ) !== false + ); + + // Enable all roles. + unset_config('roles', 'factor_role'); + $this->assertEquals( + get_string('summarycondition', 'factor_role', get_string('none')), + $rolefactor->get_summary_condition() + ); + } +} diff --git a/admin/tool/mfa/factor/role/version.php b/admin/tool/mfa/factor/role/version.php index 52cc3f0f0332..000aecd7dd59 100644 --- a/admin/tool/mfa/factor/role/version.php +++ b/admin/tool/mfa/factor/role/version.php @@ -26,8 +26,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020072100; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_role'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; diff --git a/admin/tool/mfa/factor/token/classes/privacy/provider.php b/admin/tool/mfa/factor/token/classes/privacy/provider.php index e76671f94963..505585af30f8 100644 --- a/admin/tool/mfa/factor/token/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/token/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_token\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/token/tests/factor_test.php b/admin/tool/mfa/factor/token/tests/factor_test.php index c14e8d4edd32..3a0b1450c66e 100644 --- a/admin/tool/mfa/factor/token/tests/factor_test.php +++ b/admin/tool/mfa/factor/token/tests/factor_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 factor_token\tests; +namespace factor_token; /** * Tests for MFA manager class. @@ -27,11 +27,24 @@ */ class factor_test extends \advanced_testcase { + /** + * Holds specific requested factor, which is token factor. + * + * @var \factor_token\factor $factor + */ + public \factor_token\factor $factor; + public function setUp(): void { $this->resetAfterTest(); $this->factor = new \factor_token\factor('token'); } + /** + * Test calculating expiry time in general + * + * @covers ::calculate_expiry_time + * @return void + */ public function test_calculate_expiry_time_in_general() { $timestamp = 1642213800; // 1230 UTC. @@ -42,7 +55,7 @@ public function test_calculate_expiry_time_in_general() { // Test that non-overnight timestamps are just exactly as configured. // We don't need to care about 0 or negative ints, they will just make the cookie expire immediately. $expiry = $method->invoke($this->factor, $timestamp); - $this->assertEquals($expiry[1], DAYSECS); + $this->assertEquals(DAYSECS, $expiry[1]); set_config('expiry', HOURSECS, 'factor_token'); $expiry = $method->invoke($this->factor, $timestamp); @@ -75,6 +88,7 @@ public function test_calculate_expiry_time_in_general() { * value, provided it never goes past raw value expiry time, and when it * needs to be 2am, it's 2am on the following morning. * + * @covers ::calculate_expiry_time * @param int $timestamp * @dataProvider timestamp_provider */ @@ -123,6 +137,7 @@ public function test_calculate_expiry_time_for_overnight_expiry_with_one_day_exp * value, provided it never goes past raw value expiry time, and when it * needs to be 2am, it's 2am on the morning after tomorrow. * + * @covers ::calculate_expiry_time * @param int $timestamp * @dataProvider timestamp_provider */ @@ -173,6 +188,7 @@ public function test_calculate_expiry_time_for_overnight_expiry_with_two_day_exp /** * This should check if the 3am expiry is pushed back to 2am as expected, but everything else appears as expected * + * @covers ::calculate_expiry_time * @param int $timestamp * @dataProvider timestamp_provider */ @@ -217,6 +233,7 @@ public function test_calculate_expiry_time_for_overnight_expiry_with_three_hour_ /** * Only relevant based on the hour padding used, which is currently set to 2 hours (2am). * + * @covers ::calculate_expiry_time * @param int $timestamp * @dataProvider timestamp_provider */ diff --git a/admin/tool/mfa/factor/token/version.php b/admin/tool/mfa/factor/token/version.php index 45dcff680997..778c946bc75c 100644 --- a/admin/tool/mfa/factor/token/version.php +++ b/admin/tool/mfa/factor/token/version.php @@ -26,8 +26,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022011700; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_token'; $plugin->release = 2022011700; $plugin->maturity = MATURITY_STABLE; diff --git a/admin/tool/mfa/factor/totp/classes/factor.php b/admin/tool/mfa/factor/totp/classes/factor.php index 1874cd6cef35..bd40068824ea 100644 --- a/admin/tool/mfa/factor/totp/classes/factor.php +++ b/admin/tool/mfa/factor/totp/classes/factor.php @@ -135,7 +135,7 @@ public function setup_factor_form_definition($mform) { public function setup_factor_form_definition_after_data($mform) { global $OUTPUT, $SITE, $USER; - // Array of elements to allow XSS on when we're running Totara. + // Array of elements to allow XSS. $xssallowedelements = []; $mform->addElement('html', $OUTPUT->heading(get_string('setupfactor', 'factor_totp'), 2)); @@ -199,7 +199,7 @@ public function setup_factor_form_definition_after_data($mform) { $html = $togglelink . $html; $xssallowedelements[] = $mform->addElement('static', 'enter', '', $html); - // Allow XSS on Totara. + // Allow XSS. if (method_exists('MoodleQuickForm_static', 'set_allow_xss')) { foreach ($xssallowedelements as $xssallowedelement) { $xssallowedelement->set_allow_xss(true); diff --git a/admin/tool/mfa/factor/totp/classes/privacy/provider.php b/admin/tool/mfa/factor/totp/classes/privacy/provider.php index c670b8558943..c6c5b23a20e2 100644 --- a/admin/tool/mfa/factor/totp/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/totp/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_totp\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/totp/extlib/Assert/LICENSE b/admin/tool/mfa/factor/totp/extlib/Assert/LICENSE new file mode 100644 index 000000000000..43672e7e62d4 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/Assert/LICENSE @@ -0,0 +1,11 @@ +Copyright (c) 2011-2013, Benjamin Eberlei +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. diff --git a/admin/tool/mfa/factor/totp/extlib/Assert/composer.json b/admin/tool/mfa/factor/totp/extlib/Assert/composer.json new file mode 100644 index 000000000000..37784796a9b4 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/Assert/composer.json @@ -0,0 +1,23 @@ +{ + "name": "beberlei/assert", + "description": "Thin assertion library for input validation in business models.", + "authors": [ + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"} + ], + "license": "BSD-2-Clause", + "keywords": ["assert", "assertion", "validation"], + "require": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-0": { + "Assert": "lib/" + }, + "files": ["lib/Assert/functions.php"] + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/admin/tool/mfa/factor/totp/extlib/Assert/readme_moodle.txt b/admin/tool/mfa/factor/totp/extlib/Assert/readme_moodle.txt new file mode 100644 index 000000000000..bccc0a3edbf3 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/Assert/readme_moodle.txt @@ -0,0 +1,17 @@ +Assert 2.1 +-------------- +https://github.com/beberlei/assert/releases/tag/v2.1 + +Instructions to import WebAuthn into Moodle: + +1. Download the latest release from https://github.com/beberlei/assert/releases/tag/vx.x + (choose "Source code") +2. Unzip the source code +3. Copy the following files from assert-x.x/lib/Assert into admin/tool/mfa/factor/totp/extlib/Assert: + 1. Assertion.php + 2. AssertionFailedException.php + 3. InvalidArgumentException.php + +4. Copy the following files from assert-x.x into admin/tool/mfa/factor/totp/extlib/Assert: + 1. LICENSE + 2. composer.json diff --git a/admin/tool/mfa/factor/totp/extlib/OTPHP/LICENSE b/admin/tool/mfa/factor/totp/extlib/OTPHP/LICENSE new file mode 100644 index 000000000000..e6a673e34dda --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/OTPHP/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014-2016 Florent Morselli + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/admin/tool/mfa/factor/totp/extlib/OTPHP/composer.json b/admin/tool/mfa/factor/totp/extlib/OTPHP/composer.json new file mode 100644 index 000000000000..1abd98395fa3 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/OTPHP/composer.json @@ -0,0 +1,40 @@ +{ + "name": "spomky-labs/otphp", + "type": "library", + "description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator", + "license": "MIT", + "keywords": ["otp", "hotp", "totp", "RFC 4226", "RFC 6238", "Google Authenticator", "FreeOTP"], + "homepage": "https://github.com/Spomky-Labs/otphp", + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/otphp/contributors" + } + ], + "require": { + "php": "^7.1", + "paragonie/constant_time_encoding": "^2.0", + "beberlei/assert": "^2.4" + }, + "require-dev": { + "phpunit/phpunit": "^6.0", + "satooshi/php-coveralls": "^1.0" + }, + "suggest": { + }, + "autoload": { + "psr-4": { "OTPHP\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "OTPHP\\Test\\": "tests/" } + }, + "extra": { + "branch-alias": { + "dev-master": "9.0.x-dev" + } + } +} diff --git a/admin/tool/mfa/factor/totp/extlib/OTPHP/readme_moodle.txt b/admin/tool/mfa/factor/totp/extlib/OTPHP/readme_moodle.txt new file mode 100644 index 000000000000..ba87585e7500 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/OTPHP/readme_moodle.txt @@ -0,0 +1,19 @@ +OTPHP 9.1.1 +-------------- +https://github.com/Spomky-Labs/otphp/releases/tag/v9.1.1 + +Instructions to import WebAuthn into Moodle: + +1. Download the latest release from https://github.com/Spomky-Labs/otphp/releases/tag/vx.x.x + (choose "Source code") +2. Unzip the source code +3. Copy the following files from otphp-x.x/lib/OTPHP into admin/tool/mfa/factor/totp/extlib/OTPHP: + 1. OTP.php + 2. OTPInterface.php + 3. ParameterTrait.php + 4. TOTP.php + 5. TOTPInterface.php + +4. Copy the following files from otphp-x.x into admin/tool/mfa/factor/totp/extlib/OTPHP: + 1. LICENSE + 2. composer.json diff --git a/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/LICENSE.txt b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/LICENSE.txt new file mode 100644 index 000000000000..06302425a340 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/LICENSE.txt @@ -0,0 +1,48 @@ +The MIT License (MIT) + +Copyright (c) 2016 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ +This library was based on the work of Steve "Sc00bz" Thomas. +------------------------------------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) 2014 Steve Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/composer.json b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/composer.json new file mode 100644 index 000000000000..7aad332b7d92 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/composer.json @@ -0,0 +1,40 @@ +{ + "name": "paragonie/constant_time_encoding", + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base64", "encoding", "rfc4648", "base32", "base16", "hex", "bin2hex", "hex2bin", "base64_encode", "base64_decode", "base32_encode", "base32_decode" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "support": { + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "vimeo/psalm": "^0.3|^" + }, + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + } +} diff --git a/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/readme_moodle.txt b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/readme_moodle.txt new file mode 100644 index 000000000000..43e40a645bad --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/readme_moodle.txt @@ -0,0 +1,17 @@ +paragonie/constant_time_encoding 2.1.1 +-------------- +https://github.com/paragonie/constant_time_encoding/releases/tag/v2.1.1 + +Instructions to import WebAuthn into Moodle: + +1. Download the latest release from https://github.com/paragonie/constant_time_encoding/releases/tag/vx.x.x + (choose "Source code") +2. Unzip the source code +3. Copy the following files from constant_time_encoding-x.x/lib/constant_time_encoding into admin/tool/mfa/factor/totp/extlib/constant_time_encoding: + 1. Base32.php + 2. Binary.php + 3. EncoderInterface.php + +4. Copy the following files from constant_time_encoding-x.x into admin/tool/mfa/factor/totp/extlib/constant_time_encoding: + 1. LICENSE + 2. composer.json diff --git a/admin/tool/mfa/factor/totp/tests/factor_test.php b/admin/tool/mfa/factor/totp/tests/factor_test.php index af0765d6f4c7..fe36fa7c07f5 100644 --- a/admin/tool/mfa/factor/totp/tests/factor_test.php +++ b/admin/tool/mfa/factor/totp/tests/factor_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 factor_totp\tests; +namespace factor_totp; defined('MOODLE_INTERNAL') || die(); diff --git a/admin/tool/mfa/factor/totp/thirdpartylibs.xml b/admin/tool/mfa/factor/totp/thirdpartylibs.xml index ce5b19d61097..c14f00a39175 100644 --- a/admin/tool/mfa/factor/totp/thirdpartylibs.xml +++ b/admin/tool/mfa/factor/totp/thirdpartylibs.xml @@ -3,22 +3,22 @@ extlib/Assert Assert - + 2.1 MIT - 2.1+ + https://github.com/beberlei/assert/releases/tag/v2.1 extlib/OTPHP OTPHP - + 9.1.1 MIT - 2.1+ + https://github.com/Spomky-Labs/otphp/releases/tag/v9.1.1 - extlib/ParagonIE - ParagonIE - - Custom - + extlib/ParagonIE/ConstantTime + Constant-Time Encoding + 2.1.1 + MIT + https://github.com/paragonie/constant_time_encoding/releases/tag/v2.1.1 diff --git a/admin/tool/mfa/factor/totp/version.php b/admin/tool/mfa/factor/totp/version.php index 0b8b45dc94ce..e77e8ce1d914 100644 --- a/admin/tool/mfa/factor/totp/version.php +++ b/admin/tool/mfa/factor/totp/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2021021700; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). $plugin->release = 2021021700; -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_totp'; $plugin->maturity = MATURITY_STABLE; $plugin->dependencies = ['tool_mfa' => 2019102400]; diff --git a/admin/tool/mfa/factor/webauthn/amd/build/login.min.js b/admin/tool/mfa/factor/webauthn/amd/build/login.min.js index 719a3e42c2ec..83d6151f3a0e 100644 --- a/admin/tool/mfa/factor/webauthn/amd/build/login.min.js +++ b/admin/tool/mfa/factor/webauthn/amd/build/login.min.js @@ -6,6 +6,6 @@ * @author Alex Morris * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("factor_webauthn/login",["factor_webauthn/utils"],(function(utils){return{init:function(getArgs){document.getElementById("id_submitbutton").addEventListener("click",(async function(e){if(e.preventDefault(),!navigator.credentials||!navigator.credentials.create)throw new Error("Browser not supported.");if(!1===(getArgs=JSON.parse(getArgs)).success)throw new Error(getArgs.msg||"unknown error occured");utils.recursiveBase64StrToArrayBuffer(getArgs);const cred=await navigator.credentials.get(getArgs),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()}))}}})); +define("factor_webauthn/login",["factor_webauthn/utils"],(function(utils){return{init:function(getArgs){const idsubmitbutton=document.getElementById("id_submitbutton");idsubmitbutton&&idsubmitbutton.addEventListener("click",(async function(e){if(e.preventDefault(),!navigator.credentials||!navigator.credentials.create)throw new Error("Browser not supported.");if(!1===(getArgs=JSON.parse(getArgs)).success)throw new Error(getArgs.msg||"unknown error occured");utils.recursiveBase64StrToArrayBuffer(getArgs);const cred=await navigator.credentials.get(getArgs),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()}))}}})); //# sourceMappingURL=login.min.js.map \ No newline at end of file diff --git a/admin/tool/mfa/factor/webauthn/amd/build/login.min.js.map b/admin/tool/mfa/factor/webauthn/amd/build/login.min.js.map index dae3578cda6a..4efe4db06e0b 100644 --- a/admin/tool/mfa/factor/webauthn/amd/build/login.min.js.map +++ b/admin/tool/mfa/factor/webauthn/amd/build/login.min.js.map @@ -1 +1 @@ -{"version":3,"file":"login.min.js","sources":["../src/login.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * For collecting WebAuthn authenticator details on login\n *\n * @module factor_webauthn/login\n * @copyright Catalyst IT\n * @author Alex Morris \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['factor_webauthn/utils'], function(utils) {\n return {\n init: function(getArgs) {\n document.getElementById('id_submitbutton').addEventListener('click', async function(e) {\n e.preventDefault();\n if (!navigator.credentials || !navigator.credentials.create) {\n throw new Error('Browser not supported.');\n }\n\n getArgs = JSON.parse(getArgs);\n\n if (getArgs.success === false) {\n throw new Error(getArgs.msg || 'unknown error occured');\n }\n\n utils.recursiveBase64StrToArrayBuffer(getArgs);\n\n const cred = await navigator.credentials.get(getArgs);\n\n const authenticatorAttestationResponse = {\n id: cred.rawId ? utils.arrayBufferToBase64(cred.rawId) : null,\n clientDataJSON: cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,\n authenticatorData:\n cred.response.authenticatorData ? utils.arrayBufferToBase64(cred.response.authenticatorData) : null,\n signature: cred.response.signature ? utils.arrayBufferToBase64(cred.response.signature) : null,\n userHandle: cred.response.userHandle ? utils.arrayBufferToBase64(cred.response.userHandle) : null\n };\n\n document.getElementById('id_response_input').value = JSON.stringify(authenticatorAttestationResponse);\n document.getElementById('id_response_input').form.submit();\n });\n }\n };\n});\n"],"names":["define","utils","init","getArgs","document","getElementById","addEventListener","async","e","preventDefault","navigator","credentials","create","Error","JSON","parse","success","msg","recursiveBase64StrToArrayBuffer","cred","get","authenticatorAttestationResponse","id","rawId","arrayBufferToBase64","clientDataJSON","response","authenticatorData","signature","userHandle","value","stringify","form","submit"],"mappings":";;;;;;;;AAwBAA,+BAAO,CAAC,0BAA0B,SAASC,aAChC,CACHC,KAAM,SAASC,SACXC,SAASC,eAAe,mBAAmBC,iBAAiB,SAASC,eAAeC,MAChFA,EAAEC,kBACGC,UAAUC,cAAgBD,UAAUC,YAAYC,aAC3C,IAAIC,MAAM,8BAKI,KAFxBV,QAAUW,KAAKC,MAAMZ,UAETa,cACF,IAAIH,MAAMV,QAAQc,KAAO,yBAGnChB,MAAMiB,gCAAgCf,eAEhCgB,WAAaT,UAAUC,YAAYS,IAAIjB,SAEvCkB,iCAAmC,CACrCC,GAAIH,KAAKI,MAAQtB,MAAMuB,oBAAoBL,KAAKI,OAAS,KACzDE,eAAgBN,KAAKO,SAASD,eAAiBxB,MAAMuB,oBAAoBL,KAAKO,SAASD,gBAAkB,KACzGE,kBACIR,KAAKO,SAASC,kBAAoB1B,MAAMuB,oBAAoBL,KAAKO,SAASC,mBAAqB,KACnGC,UAAWT,KAAKO,SAASE,UAAY3B,MAAMuB,oBAAoBL,KAAKO,SAASE,WAAa,KAC1FC,WAAYV,KAAKO,SAASG,WAAa5B,MAAMuB,oBAAoBL,KAAKO,SAASG,YAAc,MAGjGzB,SAASC,eAAe,qBAAqByB,MAAQhB,KAAKiB,UAAUV,kCACpEjB,SAASC,eAAe,qBAAqB2B,KAAKC"} \ No newline at end of file +{"version":3,"file":"login.min.js","sources":["../src/login.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * For collecting WebAuthn authenticator details on login\n *\n * @module factor_webauthn/login\n * @copyright Catalyst IT\n * @author Alex Morris \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['factor_webauthn/utils'], function(utils) {\n return {\n init: function(getArgs) {\n const idsubmitbutton = document.getElementById('id_submitbutton');\n if (idsubmitbutton) {\n idsubmitbutton.addEventListener('click', async function(e) {\n e.preventDefault();\n if (!navigator.credentials || !navigator.credentials.create) {\n throw new Error('Browser not supported.');\n }\n\n getArgs = JSON.parse(getArgs);\n\n if (getArgs.success === false) {\n throw new Error(getArgs.msg || 'unknown error occured');\n }\n\n utils.recursiveBase64StrToArrayBuffer(getArgs);\n\n const cred = await navigator.credentials.get(getArgs);\n\n const authenticatorAttestationResponse = {\n id: cred.rawId ? utils.arrayBufferToBase64(cred.rawId) : null,\n clientDataJSON:\n cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,\n authenticatorData:\n cred.response.authenticatorData ? utils.arrayBufferToBase64(cred.response.authenticatorData) : null,\n signature: cred.response.signature ? utils.arrayBufferToBase64(cred.response.signature) : null,\n userHandle: cred.response.userHandle ? utils.arrayBufferToBase64(cred.response.userHandle) : null\n };\n\n document.getElementById('id_response_input').value = JSON.stringify(authenticatorAttestationResponse);\n document.getElementById('id_response_input').form.submit();\n });\n }\n }\n };\n});\n"],"names":["define","utils","init","getArgs","idsubmitbutton","document","getElementById","addEventListener","async","e","preventDefault","navigator","credentials","create","Error","JSON","parse","success","msg","recursiveBase64StrToArrayBuffer","cred","get","authenticatorAttestationResponse","id","rawId","arrayBufferToBase64","clientDataJSON","response","authenticatorData","signature","userHandle","value","stringify","form","submit"],"mappings":";;;;;;;;AAwBAA,+BAAO,CAAC,0BAA0B,SAASC,aAChC,CACHC,KAAM,SAASC,eACLC,eAAiBC,SAASC,eAAe,mBAC3CF,gBACAA,eAAeG,iBAAiB,SAASC,eAAeC,MACpDA,EAAEC,kBACGC,UAAUC,cAAgBD,UAAUC,YAAYC,aAC3C,IAAIC,MAAM,8BAKI,KAFxBX,QAAUY,KAAKC,MAAMb,UAETc,cACF,IAAIH,MAAMX,QAAQe,KAAO,yBAGnCjB,MAAMkB,gCAAgChB,eAEhCiB,WAAaT,UAAUC,YAAYS,IAAIlB,SAEvCmB,iCAAmC,CACrCC,GAAIH,KAAKI,MAAQvB,MAAMwB,oBAAoBL,KAAKI,OAAS,KACzDE,eACIN,KAAKO,SAASD,eAAiBzB,MAAMwB,oBAAoBL,KAAKO,SAASD,gBAAkB,KAC7FE,kBACIR,KAAKO,SAASC,kBAAoB3B,MAAMwB,oBAAoBL,KAAKO,SAASC,mBAAqB,KACnGC,UAAWT,KAAKO,SAASE,UAAY5B,MAAMwB,oBAAoBL,KAAKO,SAASE,WAAa,KAC1FC,WAAYV,KAAKO,SAASG,WAAa7B,MAAMwB,oBAAoBL,KAAKO,SAASG,YAAc,MAGjGzB,SAASC,eAAe,qBAAqByB,MAAQhB,KAAKiB,UAAUV,kCACpEjB,SAASC,eAAe,qBAAqB2B,KAAKC"} \ No newline at end of file diff --git a/admin/tool/mfa/factor/webauthn/amd/build/utils.min.js b/admin/tool/mfa/factor/webauthn/amd/build/utils.min.js index f4caf2699f14..1e0e2eaf8aac 100644 --- a/admin/tool/mfa/factor/webauthn/amd/build/utils.min.js +++ b/admin/tool/mfa/factor/webauthn/amd/build/utils.min.js @@ -6,6 +6,6 @@ * @author Alex Morris * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("factor_webauthn/utils",[],(function(){return{recursiveBase64StrToArrayBuffer:function(obj){if("object"==typeof obj)for(let key in obj)if("string"==typeof obj[key]){let str=obj[key];if("=?BINARY?B?"===str.substring(0,"=?BINARY?B?".length)&&"?="===str.substring(str.length-"?=".length)){str=str.substring("=?BINARY?B?".length,str.length-"?=".length);let binary_string=window.atob(str),len=binary_string.length,bytes=new Uint8Array(len);for(let i=0;i.\n\n/**\n * WebAuthn utility functions, for handling array buffers.\n *\n * @module factor_webauthn/utils\n * @copyright Catalyst IT\n * @author Alex Morris \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 if (typeof obj[key] === 'string') {\n let str = obj[key];\n if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) {\n str = str.substring(prefix.length, str.length - suffix.length);\n\n let binary_string = window.atob(str);\n let len = binary_string.length;\n let bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = binary_string.charCodeAt(i);\n }\n obj[key] = bytes.buffer;\n }\n } else {\n this.recursiveBase64StrToArrayBuffer(obj[key]);\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","str","substring","length","binary_string","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,OACY,iBAAbA,IAAIC,KAAmB,KAC1BC,IAAMF,IAAIC,QALb,gBAMGC,IAAIC,UAAU,EANjB,cAM2BC,SAL3B,OAKiDF,IAAIC,UAAUD,IAAIE,OALnE,KAKmFA,QAAoB,CACpGF,IAAMA,IAAIC,UAPb,cAO8BC,OAAQF,IAAIE,OAN1C,KAM0DA,YAEnDC,cAAgBC,OAAOC,KAAKL,KAC5BM,IAAMH,cAAcD,OACpBK,MAAQ,IAAIC,WAAWF,SACtB,IAAIG,EAAI,EAAGA,EAAIH,IAAKG,IACrBF,MAAME,GAAKN,cAAcO,WAAWD,GAExCX,IAAIC,KAAOQ,MAAMI,kBAGhBd,gCAAgCC,IAAIC,OAKzDa,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 +{"version":3,"file":"utils.min.js","sources":["../src/utils.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * WebAuthn utility functions, for handling array buffers.\n *\n * @module factor_webauthn/utils\n * @copyright Catalyst IT\n * @author Alex Morris \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 let 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,YAEnDC,aAAeC,OAAOC,KAAKL,KAC3BM,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 741f16d19af7..67e9de2635ef 100644 --- a/admin/tool/mfa/factor/webauthn/amd/src/login.js +++ b/admin/tool/mfa/factor/webauthn/amd/src/login.js @@ -25,34 +25,38 @@ 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.'); - } - - getArgs = JSON.parse(getArgs); - - if (getArgs.success === false) { - throw new Error(getArgs.msg || 'unknown error occured'); - } - - utils.recursiveBase64StrToArrayBuffer(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 - }; - - document.getElementById('id_response_input').value = JSON.stringify(authenticatorAttestationResponse); - document.getElementById('id_response_input').form.submit(); - }); + 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); + + if (getArgs.success === false) { + throw new Error(getArgs.msg || 'unknown error occured'); + } + + utils.recursiveBase64StrToArrayBuffer(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 + }; + + document.getElementById('id_response_input').value = JSON.stringify(authenticatorAttestationResponse); + document.getElementById('id_response_input').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 2985fb52bdf0..94144ce9e211 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 isString = true; + if (typeof obj[key] !== 'string') { + this.recursiveBase64StrToArrayBuffer(obj[key]); + isString = false; + } - 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; + 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); + + let 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); } - } else { - this.recursiveBase64StrToArrayBuffer(obj[key]); + 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 6c8feb079d19..950adba72614 100644 --- a/admin/tool/mfa/factor/webauthn/classes/factor.php +++ b/admin/tool/mfa/factor/webauthn/classes/factor.php @@ -150,6 +150,8 @@ public function get_setup_string() { public function login_form_definition($mform) { 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); @@ -283,7 +285,7 @@ public function setup_factor_form_definition($mform) { /** * WebAuthn Factor implementation. * - * @param array $data + * @param object $data * @return array */ public function setup_user_factor($data) { 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 04b4dba4589f..dfd8c34ec39c 100644 --- a/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php +++ b/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php @@ -29,11 +29,13 @@ $string['authenticator:internal'] = 'Internal'; $string['authenticator:nfc'] = 'NFC'; $string['authenticator:usb'] = 'USB'; +$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 e5c4d61f8523..bf0c17c88a23 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]; diff --git a/admin/tool/mfa/guide.php b/admin/tool/mfa/guide.php index 30dc1b674fb0..0e93e44ad4e0 100644 --- a/admin/tool/mfa/guide.php +++ b/admin/tool/mfa/guide.php @@ -37,7 +37,8 @@ } // 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) && + (!empty($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 b93be039e6cc..25572c75c6ef 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: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/patch/MOODLE_35_STABLE.diff b/admin/tool/mfa/patch/MOODLE_35_STABLE.diff deleted file mode 100644 index 735a581724ac..000000000000 --- 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 259ef93ea961..000000000000 --- 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 c8edc6eded37..000000000000 --- 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 0d3ea86ab0d9..000000000000 --- 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 7dbb5b741582..9bcb89817d63 100644 --- a/admin/tool/mfa/renderer.php +++ b/admin/tool/mfa/renderer.php @@ -231,7 +231,7 @@ public function not_enough_factors() { // 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(); @@ -326,8 +326,8 @@ public function factors_in_use_table($lookback) { // 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. diff --git a/admin/tool/mfa/settings.php b/admin/tool/mfa/settings.php index e34845c94581..dd404c224a96 100644 --- a/admin/tool/mfa/settings.php +++ b/admin/tool/mfa/settings.php @@ -73,15 +73,10 @@ $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, + // Moodle navigation. + $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 635719b78946..4d7301fc60b5 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 df1ce003d2e5..313c99cf92b7 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 f5fb6c2e3d7f..5af422fd589a 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 @@ * @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 @@ public function test_get_factor_combinations_default() { $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 @@ public function test_get_factor_combinations_provider() { } /** - * 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 @@ public function test_get_factor_combinations_with_data_provider($factorset, $com $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 1996007c7375..1e976cb334e6 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 @@ * @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 @@ public function test_get_total_weight() { $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 @@ public function test_get_status() { $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 @@ public function test_get_status() { $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 @@ public function test_passed_enough_factors() { $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 @@ public static function should_redirect_urls_provider() { ['/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 @@ public function test_should_require_mfa_urls($urlstring, $webroot, $status, $par $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 @@ public function test_should_require_mfa_checks() { // 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 @@ public function test_should_require_mfa_redirection_loop() { $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 @@ public function test_possible_factor_setup() { 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 @@ public function test_is_ready() { 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 @@ public function test_core_hooks() { $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 @@ public function test_circular_redirect_auth() { $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 aa658d175a26..a2ab262b1678 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 @@ * @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 4e5aa27e616a..61e57345ef8a 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 @@ public function test_get_next_user_factor() { $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 @@ public function test_get_next_user_factor() { $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 @@ public function test_get_next_user_factor() { // 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 6ef09c92292c..5ad87b21db0c 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 @@ */ 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 @@ public function test_create_secret() { $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 @@ public function test_add_secret_to_db() { $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 @@ public function test_validate_secret() { $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 @@ public function test_revoke_secret() { $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 5d15a5a99085..0d24de4a7392 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(__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 @@ * @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 @@ protected function set_factor_state($factorname, $enabled = 0, $weight = 100) { * @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 69e6ad8649a4..e327895a96bc 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 cf49ea22f1fe..0a13f4145645 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -2050,7 +2050,7 @@ public static function standard_plugins_list($type) { '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(