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_token; 18 19 /** 20 * Tests for MFA manager class. 21 * 22 * @package factor_token 23 * @author Peter Burnett <peterburnett@catalyst-au.net> 24 * @author Kevin Pham <kevinpham@catalyst-au.net> 25 * @copyright Catalyst IT 26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 */ 28 class factor_test extends \advanced_testcase { 29 30 /** 31 * Holds specific requested factor, which is token factor. 32 * 33 * @var \factor_token\factor $factor 34 */ 35 public \factor_token\factor $factor; 36 37 public function setUp(): void { 38 $this->resetAfterTest(); 39 $this->factor = new \factor_token\factor('token'); 40 } 41 42 /** 43 * Test calculating expiry time in general 44 * 45 * @covers ::calculate_expiry_time 46 * @return void 47 */ 48 public function test_calculate_expiry_time_in_general() { 49 $timestamp = 1642213800; // 1230 UTC. 50 51 set_config('expireovernight', 0, 'factor_token'); 52 $method = new \ReflectionMethod($this->factor, 'calculate_expiry_time'); 53 $method->setAccessible(true); 54 55 // Test that non-overnight timestamps are just exactly as configured. 56 // We don't need to care about 0 or negative ints, they will just make the cookie expire immediately. 57 $expiry = $method->invoke($this->factor, $timestamp); 58 $this->assertEquals(DAYSECS, $expiry[1]); 59 60 set_config('expiry', HOURSECS, 'factor_token'); 61 $expiry = $method->invoke($this->factor, $timestamp); 62 $this->assertGreaterThan(HOURSECS - 30, $expiry[1]); 63 $this->assertLessThan(HOURSECS + 30, $expiry[1]); 64 65 set_config('expireovernight', 1, 'factor_token'); 66 // Manually calculate the next reset time. 67 $reset = strtotime('tomorrow 0200', $timestamp); 68 $resetdelta = $reset - $timestamp; 69 // Confirm that a timestamp that doesnt reach reset time. 70 if ($timestamp + HOURSECS < $reset) { 71 $expiry = $method->invoke($this->factor, $timestamp); 72 $this->assertGreaterThan(HOURSECS - 30, $expiry[1]); 73 $this->assertLessThan(HOURSECS + 30, $expiry[1]); 74 } 75 76 set_config('expiry', 2 * DAYSECS, 'factor_token'); 77 // Now confirm that the returned expiry is less than the absolute amount. 78 $expiry = $method->invoke($this->factor, $timestamp); 79 $this->assertGreaterThan(DAYSECS, $expiry[1]); 80 $this->assertLessThan(2 * DAYSECS, $expiry[1]); 81 $this->assertGreaterThan($resetdelta + DAYSECS - 30, $expiry[1]); 82 $this->assertLessThan($resetdelta + DAYSECS + 30, $expiry[1]); 83 } 84 85 /** 86 * Everything should end at 2am unless adding the hours lands it between 87 * 0 <= x < 2am, which in that case it should just expire using the raw 88 * value, provided it never goes past raw value expiry time, and when it 89 * needs to be 2am, it's 2am on the following morning. 90 * 91 * @covers ::calculate_expiry_time 92 * @param int $timestamp 93 * @dataProvider timestamp_provider 94 */ 95 public function test_calculate_expiry_time_for_overnight_expiry_with_one_day_expiry($timestamp) { 96 // Setup configuration. 97 $method = new \ReflectionMethod($this->factor, 'calculate_expiry_time'); 98 $method->setAccessible(true); 99 set_config('expireovernight', 1, 'factor_token'); 100 set_config('expiry', DAYSECS, 'factor_token'); 101 102 // All the results here, should be for 2am the following morning from the timestamp provided. 103 $expiry = $method->invoke($this->factor, $timestamp); 104 list($expiresat, $secondstillexpiry) = $expiry; 105 106 // Calculate the expected raw expiry if not considering 'overnight'. 107 $timezone = \core_date::get_user_timezone_object(); 108 $datetime = new \DateTime(); 109 $datetime->setTimezone($timezone); 110 111 $rawexpiry = $timestamp + DAYSECS; 112 $datetime->setTimestamp($rawexpiry); 113 $rawhour = $datetime->format('H'); 114 $rawminute = $datetime->format('m'); 115 116 // Sanity check, that the $secondstillexpiry is in the appropriate ranges. 117 $this->assertGreaterThan(0, $secondstillexpiry); 118 $this->assertLessThan(DAYSECS + 1, $secondstillexpiry); 119 120 if ($rawhour >= 0 && $rawhour < 2 || $rawhour == 2 && $rawminute == 0) { 121 // Should just use expiry time, if the hours will land between 0 and 2am. 122 $this->assertEquals($datetime->getTimestamp(), $expiresat); 123 // Ensure the $secondstillexpiry is calculated correctly. 124 $this->assertEquals($expiresat - $timestamp, $secondstillexpiry); 125 } else { 126 // Otherwise it should fall on 2am the following day. 127 $followingdayattwoam = strtotime('tomorrow 0200', $timestamp); 128 $this->assertEquals($followingdayattwoam, $expiresat); 129 // Ensure the $secondstillexpiry is calculated correctly. 130 $this->assertEquals($followingdayattwoam - $timestamp, $secondstillexpiry); 131 } 132 } 133 134 /** 135 * Everything should end at 2am unless adding the hours lands it between 136 * 0 <= x < 2am, which in that case it should just expire using the raw 137 * value, provided it never goes past raw value expiry time, and when it 138 * needs to be 2am, it's 2am on the morning after tomorrow. 139 * 140 * @covers ::calculate_expiry_time 141 * @param int $timestamp 142 * @dataProvider timestamp_provider 143 */ 144 public function test_calculate_expiry_time_for_overnight_expiry_with_two_day_expiry($timestamp) { 145 // Setup configuration. 146 $method = new \ReflectionMethod($this->factor, 'calculate_expiry_time'); 147 $method->setAccessible(true); 148 set_config('expireovernight', 1, 'factor_token'); 149 set_config('expiry', 2 * DAYSECS, 'factor_token'); 150 151 // All the results here, should be for 2am the following morning from the timestamp provided. 152 $expiry = $method->invoke($this->factor, $timestamp); 153 list($expiresat, $secondstillexpiry) = $expiry; 154 155 // Calculate the expected raw expiry if not considering 'overnight'. 156 $timezone = \core_date::get_user_timezone_object(); 157 $datetime = new \DateTime(); 158 $datetime->setTimezone($timezone); 159 160 $rawexpiry = $timestamp + (2 * DAYSECS); 161 $datetime->setTimestamp($rawexpiry); 162 $rawhour = $datetime->format('H'); 163 $rawminute = $datetime->format('m'); 164 165 // Sanity check, that the $secondstillexpiry is in the appropriate ranges. 166 $this->assertGreaterThan(0, $secondstillexpiry); 167 $this->assertLessThan((2 * DAYSECS) + 1, $secondstillexpiry); 168 169 if ($rawhour >= 0 && $rawhour < 2 || $rawhour == 2 && $rawminute == 0) { 170 // Should just use expiry time, if the hours will land between 0 and 2am. 171 $this->assertEquals($datetime->getTimestamp(), $expiresat); 172 // Ensure the $secondstillexpiry is calculated correctly. 173 $this->assertEquals($expiresat - $timestamp, $secondstillexpiry); 174 } else { 175 // Otherwise it should fall on 2am the following day after tomorrow. 176 $followingdayattwoam = strtotime('tomorrow 0200', $timestamp) + DAYSECS; 177 $this->assertEquals($followingdayattwoam, $expiresat); 178 // Ensure the $secondstillexpiry is calculated correctly. 179 $this->assertEquals($followingdayattwoam - $timestamp, $secondstillexpiry); 180 } 181 182 // Expiry should always be more than one day for an expiry duration of 183 // more than 1 day, but the overnight check should apply for the 184 // duration of the final night. 185 $this->assertGreaterThan(DAYSECS, $secondstillexpiry); 186 } 187 188 /** 189 * This should check if the 3am expiry is pushed back to 2am as expected, but everything else appears as expected 190 * 191 * @covers ::calculate_expiry_time 192 * @param int $timestamp 193 * @dataProvider timestamp_provider 194 */ 195 public function test_calculate_expiry_time_for_overnight_expiry_with_three_hour_expiry($timestamp) { 196 // Setup configuration. 197 $method = new \ReflectionMethod($this->factor, 'calculate_expiry_time'); 198 $method->setAccessible(true); 199 set_config('expireovernight', 1, 'factor_token'); 200 set_config('expiry', 3 * HOURSECS, 'factor_token'); 201 202 // All the results here, should be for 2am the following morning from the timestamp provided. 203 $expiry = $method->invoke($this->factor, $timestamp); 204 list($expiresat, $secondstillexpiry) = $expiry; 205 206 // Calculate the expected raw expiry if not considering 'overnight'. 207 $timezone = \core_date::get_user_timezone_object(); 208 $datetime = new \DateTime(); 209 $datetime->setTimezone($timezone); 210 211 $rawexpiry = $timestamp + (3 * HOURSECS); 212 $datetime->setTimestamp($rawexpiry); 213 214 // Sanity check, that the $secondstillexpiry is in the appropriate ranges. 215 $this->assertGreaterThan(0, $secondstillexpiry); 216 $this->assertLessThan((3 * HOURSECS) + 1, $secondstillexpiry); 217 218 // If the raw timestamp of the expiry, is less than tomorrow at 2am, 219 // then use the raw expiry time. 220 $followingdayattwoam = strtotime('tomorrow 0200', $timestamp); 221 if ($datetime->getTimestamp() < $followingdayattwoam) { 222 $this->assertEquals($datetime->getTimestamp(), $expiresat); 223 // Ensure the $secondstillexpiry is calculated correctly. 224 $this->assertEquals($expiresat - $timestamp, $secondstillexpiry); 225 } else { 226 // Otherwsie it should be pushed back to 2am. 227 $this->assertEquals($followingdayattwoam, $expiresat); 228 // Ensure the $secondstillexpiry is calculated correctly. 229 $this->assertEquals($followingdayattwoam - $timestamp, $secondstillexpiry); 230 } 231 } 232 233 /** 234 * Only relevant based on the hour padding used, which is currently set to 2 hours (2am). 235 * 236 * @covers ::calculate_expiry_time 237 * @param int $timestamp 238 * @dataProvider timestamp_provider 239 */ 240 public function test_calculate_expiry_time_for_overnight_expiry_with_an_hour_expiry($timestamp) { 241 // Setup configuration. 242 $method = new \ReflectionMethod($this->factor, 'calculate_expiry_time'); 243 $method->setAccessible(true); 244 set_config('expireovernight', 1, 'factor_token'); 245 set_config('expiry', HOURSECS, 'factor_token'); 246 247 // All the results here, should be for 2am the following morning from the timestamp provided. 248 $expiry = $method->invoke($this->factor, $timestamp); 249 list($expiresat, $secondstillexpiry) = $expiry; 250 251 // Calculate the expected raw expiry if not considering 'overnight'. 252 $timezone = \core_date::get_user_timezone_object(); 253 $datetime = new \DateTime(); 254 $datetime->setTimezone($timezone); 255 256 $rawexpiry = $timestamp + HOURSECS; 257 $datetime->setTimestamp($rawexpiry); 258 259 // Sanity check, that the $secondstillexpiry is in the appropriate ranges. 260 $this->assertGreaterThan(0, $secondstillexpiry); 261 $this->assertLessThan(HOURSECS + 1, $secondstillexpiry); 262 263 // If the raw timestamp of the expiry, is less than tomorrow at 2am, 264 // then use the raw expiry time. 265 $followingdayattwoam = strtotime('tomorrow 0200', $timestamp); 266 if ($datetime->getTimestamp() < $followingdayattwoam) { 267 $this->assertEquals($datetime->getTimestamp(), $expiresat); 268 // Ensure the $secondstillexpiry is calculated correctly. 269 $this->assertEquals($expiresat - $timestamp, $secondstillexpiry); 270 } else { 271 // Otherwsie it should be pushed back to 2am. 272 $this->assertEquals($followingdayattwoam, $expiresat); 273 // Ensure the $secondstillexpiry is calculated correctly. 274 $this->assertEquals($followingdayattwoam - $timestamp, $secondstillexpiry); 275 } 276 } 277 278 /** 279 * Timestamps for a 24 hour period starting from a fixed time. 280 * Increments by 30 minutes to cover half hour and hour cases. 281 * Starting timestamp: 2022-01-15 07:30:00 Australia/Melbourne time. 282 */ 283 public function timestamp_provider() { 284 $starttimestamp = 1642192200; 285 foreach (range(0, 23) as $i) { 286 $timestamps[] = [$starttimestamp + ($i * HOURSECS)]; 287 $timestamps[] = [$starttimestamp + ($i * HOURSECS) + (30 * MINSECS)]; 288 } 289 return $timestamps; 290 } 291 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body