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 19 /** 20 * Tests for MFA secret manager class. 21 * 22 * @package tool_mfa 23 * @author Peter Burnett <peterburnett@catalyst-au.net> 24 * @copyright Catalyst IT 25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 */ 27 class secret_manager_test extends \advanced_testcase { 28 29 /** 30 * Tests create factor's secret 31 * 32 * @covers ::create_secret 33 */ 34 public function test_create_secret() { 35 global $DB; 36 37 $this->resetAfterTest(true); 38 $this->setUser($this->getDataGenerator()->create_user()); 39 40 // Test adding secret to DB. 41 $secman = new \tool_mfa\local\secret_manager('mock'); 42 43 // Mutate the sessionid using reflection. 44 $reflectedsessionid = new \ReflectionProperty($secman, 'sessionid'); 45 $reflectedsessionid->setAccessible(true); 46 $reflectedsessionid->setValue($secman, 'fakesession'); 47 48 $sec1 = $secman->create_secret(1800, false); 49 $count1 = $DB->count_records('tool_mfa_secrets', ['factor' => 'mock']); 50 $record1 = $DB->get_record('tool_mfa_secrets', []); 51 $this->assertEquals(1, $count1); 52 $this->assertNotEquals('', $sec1); 53 $this->assertTrue(empty($record1->sessionid)); 54 $sec2 = $secman->create_secret(1800, false); 55 $count2 = $DB->count_records('tool_mfa_secrets', ['factor' => 'mock']); 56 $this->assertEquals(1, $count2); 57 $this->assertEquals('', $sec2); 58 $DB->delete_records('tool_mfa_secrets', []); 59 60 // Now adding secret to session. 61 $sec1 = $secman->create_secret(1800, true); 62 $count1 = $DB->count_records('tool_mfa_secrets', ['factor' => 'mock']); 63 $record1 = $DB->get_record('tool_mfa_secrets', []); 64 $this->assertEquals(1, $count1); 65 $this->assertNotEquals('', $sec1); 66 $this->assertEquals('fakesession', $record1->sessionid); 67 $sec2 = $secman->create_secret(1800, true); 68 $this->assertEquals('', $sec2); 69 $DB->delete_records('tool_mfa_secrets', []); 70 71 // Now test adding a forced code. 72 $sec1 = $secman->create_secret(1800, false); 73 $count1 = $DB->count_records('tool_mfa_secrets', ['factor' => 'mock']); 74 $this->assertEquals(1, $count1); 75 $this->assertNotEquals('', $sec1); 76 $sec2 = $secman->create_secret(1800, false, 'code'); 77 $count2 = $DB->count_records('tool_mfa_secrets', ['factor' => 'mock']); 78 $this->assertEquals(2, $count2); 79 $this->assertEquals('code', $sec2); 80 $DB->delete_records('tool_mfa_secrets', []); 81 } 82 83 /** 84 * Tests add factor's secret to database 85 * 86 * @covers ::get_record 87 * @covers ::delete_records 88 */ 89 public function test_add_secret_to_db() { 90 global $DB, $USER; 91 92 $this->resetAfterTest(true); 93 $secman = new \tool_mfa\local\secret_manager('mock'); 94 $this->setUser($this->getDataGenerator()->create_user()); 95 $sid = 'fakeid'; 96 97 // Let's make stuff public using reflection. 98 $reflectedscanner = new \ReflectionClass($secman); 99 $reflectedmethod = $reflectedscanner->getMethod('add_secret_to_db'); 100 $reflectedmethod->setAccessible(true); 101 102 // Now add a secret and confirm it creates the correct record. 103 $reflectedmethod->invoke($secman, 'code', 1800); 104 $record = $DB->get_record('tool_mfa_secrets', []); 105 $this->assertEquals('code', $record->secret); 106 $this->assertEquals($USER->id, $record->userid); 107 $this->assertEquals('mock', $record->factor); 108 $this->assertGreaterThanOrEqual(time(), (int) $record->expiry); 109 $this->assertEquals(0, $record->revoked); 110 $DB->delete_records('tool_mfa_secrets', []); 111 112 // Now add a sessionid and confirm it is added correctly. 113 $reflectedmethod->invoke($secman, 'code', 1800, $sid); 114 $record = $DB->get_record('tool_mfa_secrets', []); 115 $this->assertEquals('code', $record->secret); 116 $this->assertGreaterThanOrEqual(time(), (int) $record->expiry); 117 $this->assertEquals(0, $record->revoked); 118 $this->assertEquals($sid, $record->sessionid); 119 } 120 121 /** 122 * Tests validating factor's secret 123 * 124 * @covers ::validate_secret 125 * @covers ::create_secret 126 */ 127 public function test_validate_secret() { 128 global $DB; 129 130 // Test adding a code and getting it returned, then validated. 131 $this->resetAfterTest(true); 132 $this->setUser($this->getDataGenerator()->create_user()); 133 $secman = new \tool_mfa\local\secret_manager('mock'); 134 135 $secret = $secman->create_secret(1800, false); 136 $this->assertEquals(\tool_mfa\local\secret_manager::VALID, $secman->validate_secret($secret)); 137 $DB->delete_records('tool_mfa_secrets', []); 138 139 // Test a manual forced code. 140 $secret = $secman->create_secret(1800, false, 'code'); 141 $this->assertEquals(\tool_mfa\local\secret_manager::VALID, $secman->validate_secret($secret)); 142 $this->assertEquals('code', $secret); 143 $DB->delete_records('tool_mfa_secrets', []); 144 145 // Test bad codes. 146 $secret = $secman->create_secret(1800, false); 147 $this->assertEquals(\tool_mfa\local\secret_manager::NONVALID, $secman->validate_secret('nonvalid')); 148 $DB->delete_records('tool_mfa_secrets', []); 149 150 // Test validate when no secrets present. 151 $this->assertEquals(\tool_mfa\local\secret_manager::NONVALID, $secman->validate_secret('nonvalid')); 152 153 // Test revoked secrets. 154 $secret = $secman->create_secret(1800, false); 155 $DB->set_field('tool_mfa_secrets', 'revoked', 1, []); 156 $this->assertEquals(\tool_mfa\local\secret_manager::REVOKED, $secman->validate_secret($secret)); 157 $DB->delete_records('tool_mfa_secrets', []); 158 159 // Test expired secrets. 160 $secret = $secman->create_secret(-1, false); 161 $this->assertEquals(\tool_mfa\local\secret_manager::NONVALID, $secman->validate_secret($secret)); 162 $DB->delete_records('tool_mfa_secrets', []); 163 164 // Session locked code from the same session id. 165 // Mutate the sessionid using reflection. 166 $reflectedsessionid = new \ReflectionProperty($secman, 'sessionid'); 167 $reflectedsessionid->setAccessible(true); 168 $reflectedsessionid->setValue($secman, 'fakesession'); 169 170 $secret = $secman->create_secret(1800, true); 171 $this->assertEquals(\tool_mfa\local\secret_manager::VALID, $secman->validate_secret($secret)); 172 $DB->delete_records('tool_mfa_secrets', []); 173 174 // Now test a session locked code from a different sessionid. 175 $secret = $secman->create_secret(1800, true); 176 $reflectedsessionid->setValue($secman, 'diffsession'); 177 $this->assertEquals(\tool_mfa\local\secret_manager::NONVALID, $secman->validate_secret($secret)); 178 $DB->delete_records('tool_mfa_secrets', []); 179 } 180 181 /** 182 * Tests revoking factor's secret 183 * 184 * @covers ::validate_secret 185 * @covers ::create_secret 186 * @covers ::revoke_secret 187 */ 188 public function test_revoke_secret() { 189 global $DB, $SESSION; 190 191 $this->resetAfterTest(true); 192 $secman = new \tool_mfa\local\secret_manager('mock'); 193 $this->setUser($this->getDataGenerator()->create_user()); 194 195 // Session secrets. 196 $secret = $secman->create_secret(1800, true); 197 $secman->revoke_secret($secret); 198 $this->assertEquals(\tool_mfa\local\secret_manager::REVOKED, $secman->validate_secret($secret)); 199 unset($SESSION->tool_mfa_secrets_mock); 200 201 // DB secrets. 202 $secret = $secman->create_secret(1800, false); 203 $secman->revoke_secret($secret); 204 $this->assertEquals(\tool_mfa\local\secret_manager::REVOKED, $secman->validate_secret($secret)); 205 $DB->delete_records('tool_mfa_secrets', []); 206 207 // Revoke a non-valid secret. 208 $secret = $secman->create_secret(1800, false); 209 $secman->revoke_secret('nonvalid'); 210 $this->assertEquals(\tool_mfa\local\secret_manager::NONVALID, $secman->validate_secret('nonvalid')); 211 } 212 213 /** 214 * Tests checking if factor has an active secret 215 * 216 * @covers ::create_secret 217 * @covers ::revoke_secret 218 */ 219 public function test_has_active_secret() { 220 global $DB; 221 222 $this->resetAfterTest(true); 223 $secman = new \tool_mfa\local\secret_manager('mock'); 224 $this->setUser($this->getDataGenerator()->create_user()); 225 226 // Let's make stuff public using reflection. 227 $reflectedscanner = new \ReflectionClass($secman); 228 229 $reflectedmethod = $reflectedscanner->getMethod('has_active_secret'); 230 $reflectedmethod->setAccessible(true); 231 232 // DB secrets. 233 $this->assertFalse($reflectedmethod->invoke($secman)); 234 $secman->create_secret(1800, false); 235 $this->assertTrue($reflectedmethod->invoke($secman)); 236 $DB->delete_records('tool_mfa_secrets', []); 237 $secman->create_secret(-1, false); 238 $this->assertFalse($reflectedmethod->invoke($secman)); 239 $DB->delete_records('tool_mfa_secrets', []); 240 $secret = $secman->create_secret(1800, false); 241 $secman->revoke_secret($secret); 242 $this->assertFalse($reflectedmethod->invoke($secman)); 243 244 // Now check a secret with session involvement. 245 // Mutate the sessionid using reflection. 246 $reflectedsessionid = new \ReflectionProperty($secman, 'sessionid'); 247 $reflectedsessionid->setAccessible(true); 248 $reflectedsessionid->setValue($secman, 'fakesession'); 249 250 $this->assertFalse($reflectedmethod->invoke($secman, true)); 251 $secman->create_secret(1800, true); 252 $this->assertTrue($reflectedmethod->invoke($secman, true)); 253 $DB->delete_records('tool_mfa_secrets', []); 254 $secman->create_secret(-1, true); 255 $this->assertFalse($reflectedmethod->invoke($secman, true)); 256 $DB->delete_records('tool_mfa_secrets', []); 257 $secret = $secman->create_secret(1800, true); 258 $secman->revoke_secret($secret); 259 $this->assertFalse($reflectedmethod->invoke($secman, true)); 260 $DB->delete_records('tool_mfa_secrets', []); 261 $secret = $secman->create_secret(1800, true); 262 $reflectedsessionid->setValue($secman, 'diffsession'); 263 $this->assertFalse($reflectedmethod->invoke($secman, true)); 264 } 265 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body