Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace tool_mfa;
18
use tool_mfa\tool_mfa_trait;
19
 
20
defined('MOODLE_INTERNAL') || die();
21
require_once(__DIR__ . '/tool_mfa_trait.php');
22
 
23
/**
24
 * Tests for MFA manager class.
25
 *
26
 * @package     tool_mfa
27
 * @author      Peter Burnett <peterburnett@catalyst-au.net>
28
 * @copyright   Catalyst IT
29
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 */
31
class manager_test extends \advanced_testcase {
32
 
33
    use tool_mfa_trait;
34
 
35
    /**
36
     * Tests getting the factor total weight
37
     *
38
     * @covers ::get_total_weight
39
     * @covers ::setup_user_factor
40
     */
11 efrain 41
    public function test_get_total_weight(): void {
1 efrain 42
        $this->resetAfterTest(true);
43
 
44
        // Create and login a user.
45
        $user = $this->getDataGenerator()->create_user();
46
        $this->setUser($user);
47
 
48
        // First get weight with no active factors.
49
        $this->assertEquals(0, \tool_mfa\manager::get_total_weight());
50
 
51
        // Now setup a couple of input based factors.
52
        $this->set_factor_state('totp', 1, 100);
53
 
54
        $this->set_factor_state('email', 1, 100);
55
 
56
        // Check weight is still 0 with no passes.
57
        $this->assertEquals(0, \tool_mfa\manager::get_total_weight());
58
 
59
        // Manually pass 1 .
60
        $factor = \tool_mfa\plugininfo\factor::get_factor('totp');
61
        $totpdata = [
62
            'secret' => 'fakekey',
63
            'devicename' => 'fakedevice',
64
        ];
65
        $this->assertNotEmpty($factor->setup_user_factor((object) $totpdata));
66
        $factor->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
67
        $this->assertEquals(100, \tool_mfa\manager::get_total_weight());
68
 
69
        // Now both.
70
        $factor2 = \tool_mfa\plugininfo\factor::get_factor('email');
71
        $factor2->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
72
        $this->assertEquals(200, \tool_mfa\manager::get_total_weight());
73
 
74
        // Now setup a no input factor, and check that weight is automatically added without input.
75
        $this->set_factor_state('auth', 1, 100);
76
        set_config('goodauth', 'manual', 'factor_auth');
77
 
78
        $this->assertEquals(300, \tool_mfa\manager::get_total_weight());
79
    }
80
 
81
    /**
82
     * Tests getting the factor status
83
     *
84
     * @covers ::get_status
85
     */
11 efrain 86
    public function test_get_status(): void {
1 efrain 87
        $this->resetAfterTest(true);
88
 
89
        // Create and login a user.
90
        $user = $this->getDataGenerator()->create_user();
91
        $this->setUser($user);
92
 
93
        // Check for fail status with no factors.
94
        $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_FAIL, \tool_mfa\manager::get_status());
95
 
96
        // Now add a no input factor.
97
        $this->set_factor_state('auth', 1, 100);
98
        set_config('goodauth', 'manual', 'factor_auth');
99
 
100
        // Check state is now passing.
101
        $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_PASS, \tool_mfa\manager::get_status());
102
 
103
        // Now add a failure state factor, and ensure that fail takes precedent.
104
        $this->set_factor_state('email', 1, 100);
105
        $factoremail = \tool_mfa\plugininfo\factor::get_factor('email');
106
        $factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_FAIL);
107
 
108
        $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_FAIL, \tool_mfa\manager::get_status());
109
 
110
        // Remove no input factor, and remove fail state by logging in/out. Simulates no data entered yet.
111
        $this->setUser(null);
112
        $this->setUser($user);
113
        $this->set_factor_state('auth', 0, 100);
114
        $factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_UNKNOWN);
115
 
116
        $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_NEUTRAL, \tool_mfa\manager::get_status());
117
    }
118
 
119
    /**
120
     * Tests checking if passed enough factors
121
     *
122
     * @covers ::passed_enough_factors
123
     */
11 efrain 124
    public function test_passed_enough_factors(): void {
1 efrain 125
        $this->resetAfterTest(true);
126
 
127
        // Create and login a user.
128
        $user = $this->getDataGenerator()->create_user();
129
        $this->setUser($user);
130
 
131
        // Check when no factors are setup.
132
        $this->assertEquals(false, \tool_mfa\manager::passed_enough_factors());
133
 
134
        // Setup a no input factor.
135
        $this->set_factor_state('auth', 1, 100);
136
        set_config('goodauth', 'manual', 'factor_auth');
137
 
138
        // Check that is enough to pass.
139
        $this->assertEquals(true, \tool_mfa\manager::passed_enough_factors());
140
 
141
        // Lower the weight of the factor.
142
        $this->set_factor_state('auth', 1, 75);
143
        $this->assertEquals(false, \tool_mfa\manager::passed_enough_factors());
144
 
145
        // Add another factor to get enough weight to pass, but dont set pass state yet.
146
        $this->set_factor_state('email', 1, 100);
147
        $factoremail = \tool_mfa\plugininfo\factor::get_factor('email');
148
        $this->assertEquals(false, \tool_mfa\manager::passed_enough_factors());
149
 
150
        // Now pass the factor and check weight.
151
        $factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_PASS);
152
        $this->assertEquals(true, \tool_mfa\manager::passed_enough_factors());
153
    }
154
 
155
    /**
156
     * The data provider for whether urls should be redirected or not
157
     *
158
     * @return  array
159
     */
160
    public static function should_redirect_urls_provider() {
161
        $badurl1 = new \moodle_url('/');
162
        $badparam1 = $badurl1->out();
163
        $badurl2 = new \moodle_url('admin/tool/mfa/auth.php');
164
        $badparam2 = $badurl2->out();
165
        return [
166
            ['/', 'http://test.server', true],
167
            ['/admin/tool/mfa/action.php', 'http://test.server', true],
168
            ['/admin/tool/mfa/factor/totp/settings.php', 'http://test.server', true],
169
            ['/', 'http://test.server', true, ['url' => $badparam1]],
170
            ['/', 'http://test.server', true, ['url' => $badparam2]],
171
            ['/admin/tool/mfa/auth.php', 'http://test.server', false],
172
            ['/admin/tool/mfa/auth.php', 'http://test.server/parent/directory', false],
173
            ['/admin/tool/mfa/action.php', 'http://test.server/parent/directory', true],
174
            ['/', 'http://test.server/parent/directory', true, ['url' => $badparam1]],
175
            ['/', 'http://test.server/parent/directory', true, ['url' => $badparam2]],
176
        ];
177
    }
178
 
179
    /**
180
     * Tests whether it should require mfa
181
     *
182
     * @covers ::should_require_mfa
183
     * @param string $urlstring
184
     * @param string $webroot
185
     * @param bool $status
186
     * @param array|null $params
187
     * @dataProvider should_redirect_urls_provider
188
     */
11 efrain 189
    public function test_should_require_mfa_urls($urlstring, $webroot, $status, $params = null): void {
1 efrain 190
        $this->resetAfterTest(true);
191
        global $CFG;
192
        $user = $this->getDataGenerator()->create_user();
193
        $this->setUser($user);
194
        $CFG->wwwroot = $webroot;
195
        $url = new \moodle_url($urlstring, $params);
196
        $this->assertEquals($status, \tool_mfa\manager::should_require_mfa($url, false));
197
    }
198
 
199
    /**
200
     * Tests whether it should require the mfa checks
201
     *
202
     * @covers ::should_require_mfa
203
     */
11 efrain 204
    public function test_should_require_mfa_checks(): void {
1 efrain 205
        // Setup test and user.
206
        global $CFG;
207
        $this->resetAfterTest(true);
208
        $user = $this->getDataGenerator()->create_user();
209
 
210
        $badurl = new \moodle_url('/');
211
 
212
        // Upgrade checks.
213
        $this->setAdminUser();
214
        // Mark the site as upgraded so it will not fail when running the unittest as a whole.
215
        $CFG->allversionshash = \core_component::get_all_versions_hash();
216
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
217
        $oldhash = $CFG->allversionshash;
218
        $CFG->allversionshash = 'abc';
219
        $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
220
        $CFG->allversionshash = $oldhash;
221
        $upgradesettings = new \moodle_url('/admin/upgradesettings.php');
222
        $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($upgradesettings, false));
223
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
224
 
225
        // Admin not setup.
226
        $this->setUser($user);
227
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
228
        $CFG->adminsetuppending = 1;
229
        $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
230
        $CFG->adminsetuppending = 0;
231
 
232
        // Check prevent_redirect.
233
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
234
        $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, true));
235
 
236
        // User not setup properly.
237
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
238
        $notsetup = clone($user);
239
        unset($notsetup->firstname);
240
        $this->setUser($notsetup);
241
        $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
242
        $this->setUser($user);
243
 
244
        // Enrolment.
245
        $enrolurl = new \moodle_url('/enrol/index.php');
246
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
247
        $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($enrolurl, false));
248
 
249
        // Guest User.
250
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
251
        $this->setGuestUser();
252
        $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
253
        $this->setUser($user);
254
 
255
        // Forced password changes.
256
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
257
        set_user_preference('auth_forcepasswordchange', true);
258
        $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
259
        set_user_preference('auth_forcepasswordchange', false);
260
 
261
        // Login as check.
262
        $user2 = $this->getDataGenerator()->create_user();
263
        $syscontext = \context_system::instance();
264
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
265
        $this->setAdminUser();
266
        \core\session\manager::loginas($user2->id, $syscontext, false);
267
        $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false));
268
        $this->setUser($user);
269
    }
270
 
271
    /**
272
     * Tests should require the mfa redirection loop
273
     *
274
     * @covers ::should_require_mfa
275
     */
11 efrain 276
    public function test_should_require_mfa_redirection_loop(): void {
1 efrain 277
        // Setup test and user.
278
        global $CFG, $SESSION;
279
        $CFG->wwwroot = 'http://phpunit.test';
280
        $this->resetAfterTest(true);
281
        $user = $this->getDataGenerator()->create_user();
282
        $this->setUser($user);
283
 
284
        // Set first referer url.
285
        $_SERVER['HTTP_REFERER'] = 'http://phpunit.test';
286
        $url = new \moodle_url('/');
287
 
288
        // Test you get three redirs then exception.
289
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
290
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
291
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
292
        // Set count to threshold.
293
        $SESSION->mfa_redir_count = 5;
294
        $this->assertEquals(\tool_mfa\manager::REDIRECT_EXCEPTION, \tool_mfa\manager::should_require_mfa($url, false));
295
        // Reset session vars.
296
        unset($SESSION->mfa_redir_referer);
297
        unset($SESSION->mfa_redir_count);
298
 
299
        // Test 4 different redir urls.
300
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
301
        $_SERVER['HTTP_REFERER'] = 'http://phpunit.test/2';
302
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
303
        $_SERVER['HTTP_REFERER'] = 'http://phpunit3.test/3';
304
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
305
        $_SERVER['HTTP_REFERER'] = 'http://phpunit4.test/4';
306
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
307
        // Reset session vars.
308
        unset($SESSION->mfa_redir_referer);
309
        unset($SESSION->mfa_redir_count);
310
 
311
        // Test 6 then jump to new referer (5 + 1 to set the first time).
312
        $_SERVER['HTTP_REFERER'] = 'http://phpunit.test';
313
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
314
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
315
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
316
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
317
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
318
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
319
 
320
        $_SERVER['HTTP_REFERER'] = 'http://phpunit.test/2';
321
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
322
        // Now test that going back to original URL doesnt cause exception.
323
        $_SERVER['HTTP_REFERER'] = 'http://phpunit.test';
324
        $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false));
325
    }
326
 
327
    /**
328
     * Tests checking for possible setup factor
329
     *
330
     * @covers ::possible_factor_setup
331
     * @covers ::setup_user_factor
332
     */
11 efrain 333
    public function test_possible_factor_setup(): void {
1 efrain 334
        // Setup test and user.
335
        $this->resetAfterTest(true);
336
        $user = $this->getDataGenerator()->create_user();
337
        $this->setUser($user);
338
 
339
        // Test for totp is able to be setup.
340
        set_config('enabled', 1, 'factor_totp');
341
        $this->assertTrue(\tool_mfa\manager::possible_factor_setup());
342
        set_config('enabled', 0, 'factor_totp');
343
 
344
        // Test TOTP is already setup and can be managed.
345
        $totp = \tool_mfa\plugininfo\factor::get_factor('totp');
346
        set_config('enabled', 1, 'factor_totp');
347
        $totpdata = [
348
            'secret' => 'fakekey',
349
            'devicename' => 'fakedevice',
350
        ];
351
        $this->assertNotEmpty($totp->setup_user_factor((object) $totpdata));
352
        $this->assertTrue(\tool_mfa\manager::possible_factor_setup());
353
        set_config('enabled', 0, 'factor_totp');
354
 
355
        // Test no factors can be setup.
356
        set_config('enabled', 1, 'factor_email');
357
        set_config('enabled', 1, 'factor_admin');
358
        $this->assertFalse(\tool_mfa\manager::possible_factor_setup());
359
        set_config('enabled', 0, 'factor_email');
360
        set_config('enabled', 0, 'factor_admin');
361
    }
362
 
363
    /**
364
     * Tests checking if a factor is ready
365
     *
366
     * @covers ::is_ready
367
     */
11 efrain 368
    public function test_is_ready(): void {
1 efrain 369
        // Setup test and user.
370
        global $CFG;
371
        $this->resetAfterTest(true);
372
        $user = $this->getDataGenerator()->create_user();
373
        $this->setUser($user);
374
        set_config('enabled', 1, 'factor_nosetup');
375
        set_config('enabled', 1, 'tool_mfa');
376
 
377
        // Capability Check.
378
        $this->assertTrue(\tool_mfa\manager::is_ready());
379
        // Swap to role without capability.
380
        $this->setGuestUser();
381
        $this->assertFalse(\tool_mfa\manager::is_ready());
382
        $this->setUser($user);
383
 
384
        // Enabled check.
385
        $this->assertTrue(\tool_mfa\manager::is_ready());
386
        set_config('enabled', 0, 'tool_mfa');
387
        $this->assertFalse(\tool_mfa\manager::is_ready());
388
        set_config('enabled', 1, 'tool_mfa');
389
 
390
        // Upgrade check.
391
        $this->assertTrue(\tool_mfa\manager::is_ready());
392
        $CFG->upgraderunning = true;
393
        $this->assertFalse(\tool_mfa\manager::is_ready());
394
        unset($CFG->upgraderunning);
395
 
396
        // No factors check.
397
        $this->assertTrue(\tool_mfa\manager::is_ready());
398
        set_config('enabled', 0, 'factor_nosetup');
399
        $this->assertFalse(\tool_mfa\manager::is_ready());
400
        set_config('enabled', 1, 'factor_nosetup');
401
    }
402
 
403
    /**
404
     * Tests core hooks
405
     *
406
     * @covers ::mfa_config_hook_test
407
     * @covers ::mfa_login_hook_test
408
     */
11 efrain 409
    public function test_core_hooks(): void {
1 efrain 410
        // Setup test and user.
411
        global $CFG, $SESSION;
412
        $this->resetAfterTest(true);
413
        $user = $this->getDataGenerator()->create_user();
414
        $this->setUser($user);
415
 
416
        // Require login to fire hooks. Config we get for free.
417
        require_login();
418
 
419
        $this->assertTrue($CFG->mfa_config_hook_test);
420
        $this->assertTrue($SESSION->mfa_login_hook_test);
421
    }
422
 
423
    /**
424
     * Tests circular redirect auth
425
     *
426
     * @covers ::should_require_mfa
427
     */
11 efrain 428
    public function test_circular_redirect_auth(): void {
1 efrain 429
        // Setup test and user.
430
        $this->resetAfterTest(true);
431
        $user = $this->getDataGenerator()->create_user();
432
        $this->setUser($user);
433
 
434
        // Spoof the referrer for the redirect check.
435
        $_SERVER['HTTP_REFERER'] = '/admin/tool/mfa/auth.php';
436
        $baseurl = new \moodle_url('/my/naughty/page.php');
437
 
438
        // After a single check, we should redirect.
439
        $this->assertEquals(\tool_mfa\manager::REDIRECT,
440
            \tool_mfa\manager::should_require_mfa($baseurl, false));
441
 
442
        // Now hammer it up to the threshold to emulate a repeated force browse from auth.php.
443
        for ($i = 0; $i < \tool_mfa\manager::REDIR_LOOP_THRESHOLD; $i++) {
444
            \tool_mfa\manager::should_require_mfa($baseurl, false);
445
        }
446
 
447
        // Now finally confirm that a 6th access attempt (after loop safety trigger) still redirects.
448
        $this->assertEquals(\tool_mfa\manager::REDIRECT,
449
            \tool_mfa\manager::should_require_mfa($baseurl, false));
450
    }
451
}