Rev 1 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>.namespace tool_mfa;use tool_mfa\tool_mfa_trait;defined('MOODLE_INTERNAL') || die();require_once(__DIR__ . '/tool_mfa_trait.php');/*** Tests for MFA manager class.** @package tool_mfa* @author Peter Burnett <peterburnett@catalyst-au.net>* @copyright Catalyst IT* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/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(): void {$this->resetAfterTest(true);// Create and login a user.$user = $this->getDataGenerator()->create_user();$this->setUser($user);// First get weight with no active factors.$this->assertEquals(0, \tool_mfa\manager::get_total_weight());// Now setup a couple of input based factors.$this->set_factor_state('totp', 1, 100);$this->set_factor_state('email', 1, 100);// Check weight is still 0 with no passes.$this->assertEquals(0, \tool_mfa\manager::get_total_weight());// Manually pass 1 .$factor = \tool_mfa\plugininfo\factor::get_factor('totp');$totpdata = ['secret' => 'fakekey','devicename' => 'fakedevice',];$this->assertNotEmpty($factor->setup_user_factor((object) $totpdata));$factor->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);$this->assertEquals(100, \tool_mfa\manager::get_total_weight());// Now both.$factor2 = \tool_mfa\plugininfo\factor::get_factor('email');$factor2->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);$this->assertEquals(200, \tool_mfa\manager::get_total_weight());// Now setup a no input factor, and check that weight is automatically added without input.$this->set_factor_state('auth', 1, 100);set_config('goodauth', 'manual', 'factor_auth');$this->assertEquals(300, \tool_mfa\manager::get_total_weight());}/*** Tests getting the factor status** @covers ::get_status*/public function test_get_status(): void {$this->resetAfterTest(true);// Create and login a user.$user = $this->getDataGenerator()->create_user();$this->setUser($user);// Check for fail status with no factors.$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\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\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);$this->setUser($user);$this->set_factor_state('auth', 0, 100);$factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_UNKNOWN);$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(): void {$this->resetAfterTest(true);// Create and login a user.$user = $this->getDataGenerator()->create_user();$this->setUser($user);// Check when no factors are setup.$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(true, \tool_mfa\manager::passed_enough_factors());// Lower the weight of the factor.$this->set_factor_state('auth', 1, 75);$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(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(true, \tool_mfa\manager::passed_enough_factors());}/*** The data provider for whether urls should be redirected or not** @return array*/public static function should_redirect_urls_provider() {$badurl1 = new \moodle_url('/');$badparam1 = $badurl1->out();$badurl2 = new \moodle_url('admin/tool/mfa/auth.php');$badparam2 = $badurl2->out();return [['/', 'http://test.server', true],['/admin/tool/mfa/action.php', 'http://test.server', true],['/admin/tool/mfa/factor/totp/settings.php', 'http://test.server', true],['/', 'http://test.server', true, ['url' => $badparam1]],['/', 'http://test.server', true, ['url' => $badparam2]],['/admin/tool/mfa/auth.php', 'http://test.server', false],['/admin/tool/mfa/auth.php', 'http://test.server/parent/directory', false],['/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]],];}/*** Tests whether it should require mfa** @covers ::should_require_mfa* @param string $urlstring* @param string $webroot* @param bool $status* @param array|null $params* @dataProvider should_redirect_urls_provider*/public function test_should_require_mfa_urls($urlstring, $webroot, $status, $params = null): void {$this->resetAfterTest(true);global $CFG;$user = $this->getDataGenerator()->create_user();$this->setUser($user);$CFG->wwwroot = $webroot;$url = new \moodle_url($urlstring, $params);$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(): void {// Setup test and user.global $CFG;$this->resetAfterTest(true);$user = $this->getDataGenerator()->create_user();$badurl = new \moodle_url('/');// Upgrade checks.$this->setAdminUser();// 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::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::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::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));$CFG->adminsetuppending = 1;$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::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::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));$notsetup = clone($user);unset($notsetup->firstname);$this->setUser($notsetup);$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::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::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));$this->setGuestUser();$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::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));set_user_preference('auth_forcepasswordchange', true);$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::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));$this->setAdminUser();\core\session\manager::loginas($user2->id, $syscontext, false);$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(): void {// Setup test and user.global $CFG, $SESSION;$CFG->wwwroot = 'http://phpunit.test';$this->resetAfterTest(true);$user = $this->getDataGenerator()->create_user();$this->setUser($user);// Set first referer url.$_SERVER['HTTP_REFERER'] = 'http://phpunit.test';$url = new \moodle_url('/');// Test you get three redirs then exception.$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::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::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));$_SERVER['HTTP_REFERER'] = 'http://phpunit.test/2';$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::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));$_SERVER['HTTP_REFERER'] = 'http://phpunit4.test/4';$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::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::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::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(): void {// Setup test and user.$this->resetAfterTest(true);$user = $this->getDataGenerator()->create_user();$this->setUser($user);// Test for totp is able to be setup.set_config('enabled', 1, 'factor_totp');$this->assertTrue(\tool_mfa\manager::possible_factor_setup());set_config('enabled', 0, 'factor_totp');// Test TOTP is already setup and can be managed.$totp = \tool_mfa\plugininfo\factor::get_factor('totp');set_config('enabled', 1, 'factor_totp');$totpdata = ['secret' => 'fakekey','devicename' => 'fakedevice',];$this->assertNotEmpty($totp->setup_user_factor((object) $totpdata));$this->assertTrue(\tool_mfa\manager::possible_factor_setup());set_config('enabled', 0, 'factor_totp');// Test no factors can be setup.set_config('enabled', 1, 'factor_email');set_config('enabled', 1, 'factor_admin');$this->assertFalse(\tool_mfa\manager::possible_factor_setup());set_config('enabled', 0, 'factor_email');set_config('enabled', 0, 'factor_admin');}/*** Tests checking if a factor is ready** @covers ::is_ready*/public function test_is_ready(): void {// Setup test and user.global $CFG;$this->resetAfterTest(true);$user = $this->getDataGenerator()->create_user();$this->setUser($user);set_config('enabled', 1, 'factor_nosetup');set_config('enabled', 1, 'tool_mfa');// Capability Check.$this->assertTrue(\tool_mfa\manager::is_ready());// Swap to role without capability.$this->setGuestUser();$this->assertFalse(\tool_mfa\manager::is_ready());$this->setUser($user);// Enabled check.$this->assertTrue(\tool_mfa\manager::is_ready());set_config('enabled', 0, 'tool_mfa');$this->assertFalse(\tool_mfa\manager::is_ready());set_config('enabled', 1, 'tool_mfa');// Upgrade check.$this->assertTrue(\tool_mfa\manager::is_ready());$CFG->upgraderunning = true;$this->assertFalse(\tool_mfa\manager::is_ready());unset($CFG->upgraderunning);// No factors check.$this->assertTrue(\tool_mfa\manager::is_ready());set_config('enabled', 0, 'factor_nosetup');$this->assertFalse(\tool_mfa\manager::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(): void {// Setup test and user.global $CFG, $SESSION;$this->resetAfterTest(true);$user = $this->getDataGenerator()->create_user();$this->setUser($user);// Require login to fire hooks. Config we get for free.require_login();$this->assertTrue($CFG->mfa_config_hook_test);$this->assertTrue($SESSION->mfa_login_hook_test);}/*** Tests circular redirect auth** @covers ::should_require_mfa*/public function test_circular_redirect_auth(): void {// Setup test and user.$this->resetAfterTest(true);$user = $this->getDataGenerator()->create_user();$this->setUser($user);// Spoof the referrer for the redirect check.$_SERVER['HTTP_REFERER'] = '/admin/tool/mfa/auth.php';$baseurl = new \moodle_url('/my/naughty/page.php');// After a single check, we should redirect.$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.for ($i = 0; $i < \tool_mfa\manager::REDIR_LOOP_THRESHOLD; $i++) {\tool_mfa\manager::should_require_mfa($baseurl, false);}// Now finally confirm that a 6th access attempt (after loop safety trigger) still redirects.$this->assertEquals(\tool_mfa\manager::REDIRECT,\tool_mfa\manager::should_require_mfa($baseurl, false));}}