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 factor_totp; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 require_once (__DIR__.'/../extlib/OTPHP/OTPInterface.php'); 22 require_once (__DIR__.'/../extlib/OTPHP/TOTPInterface.php'); 23 require_once (__DIR__.'/../extlib/OTPHP/ParameterTrait.php'); 24 require_once (__DIR__.'/../extlib/OTPHP/OTP.php'); 25 require_once (__DIR__.'/../extlib/OTPHP/TOTP.php'); 26 27 require_once (__DIR__.'/../extlib/Assert/Assertion.php'); 28 require_once (__DIR__.'/../extlib/Assert/AssertionFailedException.php'); 29 require_once (__DIR__.'/../extlib/Assert/InvalidArgumentException.php'); 30 require_once (__DIR__.'/../extlib/ParagonIE/ConstantTime/EncoderInterface.php'); 31 require_once (__DIR__.'/../extlib/ParagonIE/ConstantTime/Binary.php'); 32 require_once (__DIR__.'/../extlib/ParagonIE/ConstantTime/Base32.php'); 33 34 /** 35 * Tests for TOTP factor. 36 * 37 * @covers \factor_totp\factor 38 * @package factor_totp 39 * @author Peter Burnett <peterburnett@catalyst-au.net> 40 * @copyright Catalyst IT 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 class factor_test extends \advanced_testcase { 44 45 /** 46 * Test code validation of the TOTP factor 47 */ 48 public function test_validate_code() { 49 global $DB; 50 51 $this->resetAfterTest(true); 52 $user = $this->getDataGenerator()->create_user(); 53 $this->setUser($user); 54 // Setup test staples. 55 $totp = \OTPHP\TOTP::create('fakekey'); 56 $window = 10; 57 58 set_config('enabled', 1, 'factor_totp'); 59 $totpfactor = \tool_mfa\plugininfo\factor::get_factor('totp'); 60 $totpdata = [ 61 'secret' => 'fakekey', 62 'devicename' => 'fakedevice', 63 ]; 64 $factorinstance = $totpfactor->setup_user_factor((object) $totpdata); 65 66 // First check that a valid code is actually valid. 67 $code = $totp->at(time()); 68 // Manually set timeverified of factor. 69 $DB->set_field('tool_mfa', 'lastverified', time() - WEEKSECS, ['id' => $factorinstance->id]); 70 $result = $totpfactor->validate_code($code, $window, $totp, $factorinstance); 71 $this->assertEquals($totpfactor::TOTP_VALID, $result); 72 73 // Now update timeverified to 2 mins ago, and check codes within window are blocked. 74 $code = $totp->at(time() - (2 * MINSECS)); 75 $DB->set_field('tool_mfa', 'lastverified', time() - (2 * MINSECS), ['id' => $factorinstance->id]); 76 $result = $totpfactor->validate_code($code, $window, $totp, $factorinstance); 77 $this->assertEquals($totpfactor::TOTP_USED, $result); 78 79 // Now update timeverified to 2 mins ago, and check codes within window are blocked. 80 $code = $totp->at(time()); 81 $DB->set_field('tool_mfa', 'lastverified', time() - (2 * MINSECS), ['id' => $factorinstance->id]); 82 $result = $totpfactor->validate_code($code, $window, $totp, $factorinstance); 83 $this->assertEquals($totpfactor::TOTP_USED, $result); 84 85 // Now update timeverified to 2 mins ago, and check codes within window are blocked. 86 $code = $totp->at(time() - (4 * MINSECS)); 87 $DB->set_field('tool_mfa', 'lastverified', time() - (2 * MINSECS), ['id' => $factorinstance->id]); 88 $result = $totpfactor->validate_code($code, $window, $totp, $factorinstance); 89 $this->assertEquals($totpfactor::TOTP_USED, $result); 90 91 // Now check future codes. 92 $window = 1; 93 $code = $totp->at(time() + (2 * MINSECS)); 94 $DB->set_field('tool_mfa', 'lastverified', time() - WEEKSECS, ['id' => $factorinstance->id]); 95 $result = $totpfactor->validate_code($code, $window, $totp, $factorinstance); 96 $this->assertEquals($totpfactor::TOTP_FUTURE, $result); 97 98 // Codes in far future are invalid. 99 $code = $totp->at(time() + (20 * MINSECS)); 100 $result = $totpfactor->validate_code($code, $window, $totp, $factorinstance); 101 $this->assertEquals($totpfactor::TOTP_INVALID, $result); 102 103 // Do the same for past codes. 104 $window = 1; 105 $code = $totp->at(time() - (2 * MINSECS)); 106 $result = $totpfactor->validate_code($code, $window, $totp, $factorinstance); 107 $this->assertEquals($totpfactor::TOTP_OLD, $result); 108 109 // Codes in far future are invalid. 110 $code = $totp->at(time() - (20 * MINSECS)); 111 $result = $totpfactor->validate_code($code, $window, $totp, $factorinstance); 112 $this->assertEquals($totpfactor::TOTP_INVALID, $result); 113 114 // Check incorrect codes are invalid. 115 // Note code has a 1 in 30,000,000 chance of failing. 116 $code = '123456'; 117 $result = $totpfactor->validate_code($code, $window, $totp, $factorinstance); 118 $this->assertEquals($totpfactor::TOTP_INVALID, $result); 119 } 120 121 /** 122 * Do not store the TOTP secret + user combination more than once 123 * 124 * @covers ::setup_user_factor 125 */ 126 public function test_wont_store_same_secret_twice() { 127 global $DB; 128 $this->resetAfterTest(true); 129 $user = $this->getDataGenerator()->create_user(); 130 $this->setUser($user); 131 132 set_config('enabled', 1, 'factor_totp'); 133 $totpfactor = \tool_mfa\plugininfo\factor::get_factor('totp'); 134 $totpdata = [ 135 'secret' => 'fakekey', 136 'devicename' => 'fakedevice', 137 ]; 138 $record = $totpfactor->setup_user_factor((object) $totpdata); 139 140 // Trying to add the same TOTP should return the existing record (exactly). 141 $anotherecord = $totpfactor->setup_user_factor((object) $totpdata); 142 $this->assertEquals($record, $anotherecord); 143 144 // The total count for factors added should be 1 at this point. 145 $count = $DB->count_records('tool_mfa'); 146 $this->assertEquals(1, $count); 147 } 148 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body