Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 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 core\session;
18
 
19
use core\tests\session\mock_handler;
20
 
21
/**
22
 * Unit tests for session manager class.
23
 *
24
 * @package    core
25
 * @category   test
26
 * @copyright  2013 Petr Skoda {@link http://skodak.org}
27
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28
 * @covers     \core\session\manager
29
 */
30
final class manager_test extends \advanced_testcase {
31
 
32
    /** @var mock_handler $mockhandler Dedicated testing handler. */
33
    protected mock_handler $mockhandler;
34
 
35
    protected function setUp(): void {
36
        parent::setUp();
37
        $this->mockhandler = new mock_handler();
38
    }
39
 
40
    public function test_start(): void {
41
        $this->resetAfterTest();
42
        // Session must be started only once...
43
        \core\session\manager::start();
44
        $this->assertDebuggingCalled('Session was already started!', DEBUG_DEVELOPER);
45
    }
46
 
47
    public function test_init_empty_session(): void {
48
        global $SESSION, $USER;
49
        $this->resetAfterTest();
50
 
51
        $user = $this->getDataGenerator()->create_user();
52
 
53
        $SESSION->test = true;
54
        $this->assertTrue($GLOBALS['SESSION']->test);
55
        $this->assertTrue($_SESSION['SESSION']->test);
56
 
57
        \core\session\manager::set_user($user);
58
        $this->assertSame($user, $USER);
59
        $this->assertSame($user, $GLOBALS['USER']);
60
        $this->assertSame($user, $_SESSION['USER']);
61
 
62
        \core\session\manager::init_empty_session();
63
 
64
        $this->assertInstanceOf('stdClass', $SESSION);
65
        $this->assertEmpty((array)$SESSION);
66
        $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
67
        $this->assertSame($GLOBALS['SESSION'], $SESSION);
68
 
69
        $this->assertInstanceOf('stdClass', $USER);
70
        $this->assertEqualsCanonicalizing(array('id' => 0, 'mnethostid' => 1), (array)$USER);
71
        $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
72
        $this->assertSame($GLOBALS['USER'], $USER);
73
 
74
        // Now test how references work.
75
 
76
        $GLOBALS['SESSION'] = new \stdClass();
77
        $GLOBALS['SESSION']->test = true;
78
        $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
79
        $this->assertSame($GLOBALS['SESSION'], $SESSION);
80
 
81
        $SESSION = new \stdClass();
82
        $SESSION->test2 = true;
83
        $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
84
        $this->assertSame($GLOBALS['SESSION'], $SESSION);
85
 
86
        $_SESSION['SESSION'] = new \stdClass();
87
        $_SESSION['SESSION']->test3 = true;
88
        $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
89
        $this->assertSame($GLOBALS['SESSION'], $SESSION);
90
 
91
        $GLOBALS['USER'] = new \stdClass();
92
        $GLOBALS['USER']->test = true;
93
        $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
94
        $this->assertSame($GLOBALS['USER'], $USER);
95
 
96
        $USER = new \stdClass();
97
        $USER->test2 = true;
98
        $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
99
        $this->assertSame($GLOBALS['USER'], $USER);
100
 
101
        $_SESSION['USER'] = new \stdClass();
102
        $_SESSION['USER']->test3 = true;
103
        $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
104
        $this->assertSame($GLOBALS['USER'], $USER);
105
    }
106
 
107
    public function test_set_user(): void {
108
        global $USER;
109
        $this->resetAfterTest();
110
 
111
        $this->assertEquals(0, $USER->id);
112
 
113
        $user = $this->getDataGenerator()->create_user();
114
        $this->assertObjectHasProperty('description', $user);
115
        $this->assertObjectHasProperty('password', $user);
116
 
117
        \core\session\manager::set_user($user);
118
 
119
        $this->assertEquals($user->id, $USER->id);
120
        $this->assertObjectNotHasProperty('description', $user);
121
        $this->assertObjectNotHasProperty('password', $user);
122
        $this->assertObjectHasProperty('sesskey', $user);
123
        $this->assertSame($user, $GLOBALS['USER']);
124
        $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
125
        $this->assertSame($GLOBALS['USER'], $USER);
126
    }
127
 
128
    public function test_login_user(): void {
129
        global $USER;
130
        $this->resetAfterTest();
131
 
132
        $this->assertEquals(0, $USER->id);
133
 
134
        $user = $this->getDataGenerator()->create_user();
135
 
136
        @\core\session\manager::login_user($user); // Ignore header error messages.
137
        $this->assertEquals($user->id, $USER->id);
138
 
139
        $this->assertObjectNotHasProperty('description', $user);
140
        $this->assertObjectNotHasProperty('password', $user);
141
        $this->assertSame($user, $GLOBALS['USER']);
142
        $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
143
        $this->assertSame($GLOBALS['USER'], $USER);
144
    }
145
 
146
    public function test_terminate_current(): void {
147
        global $USER, $SESSION;
148
        $this->resetAfterTest();
149
 
150
        $this->setAdminUser();
151
        \core\session\manager::terminate_current();
152
        $this->assertEquals(0, $USER->id);
153
 
154
        $this->assertInstanceOf('stdClass', $SESSION);
155
        $this->assertEmpty((array)$SESSION);
156
        $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
157
        $this->assertSame($GLOBALS['SESSION'], $SESSION);
158
 
159
        $this->assertInstanceOf('stdClass', $USER);
160
        $this->assertEqualsCanonicalizing(array('id' => 0, 'mnethostid' => 1), (array)$USER);
161
        $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
162
        $this->assertSame($GLOBALS['USER'], $USER);
163
    }
164
 
165
    public function test_write_close(): void {
166
        global $USER;
167
        $this->resetAfterTest();
168
 
169
        // Just make sure no errors and $USER->id is kept
170
        $this->setAdminUser();
171
        $userid = $USER->id;
172
        \core\session\manager::write_close();
173
        $this->assertSame($userid, $USER->id);
174
 
175
        $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
176
        $this->assertSame($GLOBALS['USER'], $USER);
177
    }
178
 
179
    public function test_session_exists(): void {
180
        global $CFG, $DB;
181
        $this->resetAfterTest();
182
 
183
        $this->assertFalse(\core\session\manager::session_exists('abc'));
184
 
185
        $user = $this->getDataGenerator()->create_user();
186
        $guest = guest_user();
187
 
188
        // The file handler is used by default, so let's fake the data somehow.
189
        $sid = md5('hokus');
190
        mkdir("$CFG->dataroot/sessions/", $CFG->directorypermissions, true);
191
        touch("$CFG->dataroot/sessions/sess_$sid");
192
 
193
        $this->assertFalse(\core\session\manager::session_exists($sid));
194
 
195
        $record = new \stdClass();
196
        $record->userid = 0;
197
        $record->sid = $sid;
198
        $record->timecreated = time();
199
        $record->timemodified = $record->timecreated;
200
        $record->id = $this->mockhandler->add_test_session($record);
201
 
202
        $this->assertTrue(\core\session\manager::session_exists($sid));
203
 
204
        $record->timecreated = time() - $CFG->sessiontimeout - 100;
205
        $record->timemodified = $record->timecreated + 10;
206
        \core\session\manager::update_session($record);
207
 
208
        $this->assertTrue(\core\session\manager::session_exists($sid));
209
 
210
        $record->userid = $guest->id;
211
        \core\session\manager::update_session($record);
212
 
213
        $this->assertTrue(\core\session\manager::session_exists($sid));
214
 
215
        $record->userid = $user->id;
216
        \core\session\manager::update_session($record);
217
 
218
        $this->assertFalse(\core\session\manager::session_exists($sid));
219
 
220
        $CFG->sessiontimeout = $CFG->sessiontimeout + 3000;
221
 
222
        $this->assertTrue(\core\session\manager::session_exists($sid));
223
    }
224
 
225
    public function test_touch_session(): void {
226
        $this->resetAfterTest();
227
 
228
        $sid = md5('hokus');
229
        $record = new \stdClass();
230
        $record->state        = 0;
231
        $record->sid          = $sid;
232
        $record->sessdata     = null;
233
        $record->userid       = 2;
234
        $record->timecreated  = time() - 60*60;
235
        $record->timemodified = time() - 30;
236
        $record->firstip      = $record->lastip = '10.0.0.1';
237
        $record->id = $this->mockhandler->add_test_session($record);
238
 
239
        $now = time();
240
        \core\session\manager::touch_session($sid);
241
        $session = \core\session\manager::get_session_by_sid($sid);
242
 
243
        $this->assertGreaterThanOrEqual($now, $session->timemodified);
244
        $this->assertLessThanOrEqual(time(), $session->timemodified);
245
    }
246
 
247
    /**
248
     * Test destroy method.
249
     *
250
     * @return void
251
     * @throws \dml_exception
252
     */
253
    public function test_destroy(): void {
254
        global $DB, $USER;
255
        $this->resetAfterTest();
256
 
257
        $this->setAdminUser();
258
        $userid = $USER->id;
259
 
260
        $sid = md5('hokus');
261
        $record = new \stdClass();
262
        $record->state        = 0;
263
        $record->sid          = $sid;
264
        $record->sessdata     = null;
265
        $record->userid       = $userid;
266
        $record->timecreated  = time() - 60*60;
267
        $record->timemodified = time() - 30;
268
        $record->firstip      = $record->lastip = '10.0.0.1';
269
        $this->mockhandler->add_test_session($record);
270
 
271
        $record->userid       = 0;
272
        $record->sid          = md5('pokus');
273
        $this->mockhandler->add_test_session($record);
274
 
275
        $this->assertEquals(2, $this->mockhandler->count_sessions());
276
 
277
        \core\session\manager::destroy($sid);
278
        $sessions = $this->mockhandler->get_all_sessions();
279
        $this->assertEquals(1, count($sessions));
280
        $this->assertFalse($this->contains_session(['sid' => $sid], $sessions));
281
 
282
        $this->assertSame($userid, $USER->id);
283
    }
284
 
285
    public function test_destroy_user_sessions(): void {
286
        global $DB, $USER;
287
        $this->resetAfterTest();
288
 
289
        $this->setAdminUser();
290
        $userid = $USER->id;
291
 
292
        $sid = md5('hokus');
293
        $record = new \stdClass();
294
        $record->state        = 0;
295
        $record->sid          = $sid;
296
        $record->sessdata     = null;
297
        $record->userid       = $userid;
298
        $record->timecreated  = time() - 60*60;
299
        $record->timemodified = time() - 30;
300
        $record->firstip      = $record->lastip = '10.0.0.1';
301
        $this->mockhandler->add_test_session($record);
302
 
303
        $record->sid          = md5('hokus2');
304
        $this->mockhandler->add_test_session($record);
305
 
306
        $record->userid       = 0;
307
        $record->sid          = md5('pokus');
308
        $this->mockhandler->add_test_session($record);
309
 
310
        $this->assertEquals(3, $DB->count_records('sessions'));
311
 
312
        \core\session\manager::destroy_user_sessions($userid);
313
 
314
        $sessions = $this->mockhandler->get_all_sessions();
315
        $this->assertEquals(1, count($sessions));
316
        $this->assertFalse($this->contains_session(['userid' => $userid], $sessions));
317
 
318
        $record->userid       = $userid;
319
        $record->sid          = md5('pokus3');
320
        $this->mockhandler->add_test_session($record);
321
 
322
        $record->userid       = $userid;
323
        $record->sid          = md5('pokus4');
324
        $this->mockhandler->add_test_session($record);
325
 
326
        $record->userid       = $userid;
327
        $record->sid          = md5('pokus5');
328
        $this->mockhandler->add_test_session($record);
329
 
330
        $sessions = \core\session\manager::get_sessions_by_userid($userid);
331
        $this->assertCount(3, $sessions);
332
 
333
        \core\session\manager::destroy_user_sessions($userid, md5('pokus5'));
334
 
335
        $sessions = \core\session\manager::get_sessions_by_userid($userid);
336
        $session = reset($sessions);
337
        $this->assertCount(1, $sessions);
338
        $this->assertEquals(md5('pokus5'), $session->sid);
339
    }
340
 
341
    public function test_apply_concurrent_login_limit(): void {
342
        global $DB;
343
        $this->resetAfterTest();
344
 
345
        $user1 = $this->getDataGenerator()->create_user();
346
        $user2 = $this->getDataGenerator()->create_user();
347
        $guest = guest_user();
348
 
349
        $record = new \stdClass();
350
        $record->state        = 0;
351
        $record->sessdata     = null;
352
        $record->userid       = $user1->id;
353
        $record->timemodified = time();
354
        $record->firstip      = $record->lastip = '10.0.0.1';
355
 
356
        $record->sid = md5('hokus1');
357
        $record->timecreated = 20;
358
        $this->mockhandler->add_test_session($record);
359
        $record->sid = md5('hokus2');
360
        $record->timecreated = 10;
361
        $this->mockhandler->add_test_session($record);
362
        $record->sid = md5('hokus3');
363
        $record->timecreated = 30;
364
        $this->mockhandler->add_test_session($record);
365
 
366
        $record->userid = $user2->id;
367
        $record->sid = md5('pokus1');
368
        $record->timecreated = 20;
369
        $this->mockhandler->add_test_session($record);
370
        $record->sid = md5('pokus2');
371
        $record->timecreated = 10;
372
        $this->mockhandler->add_test_session($record);
373
        $record->sid = md5('pokus3');
374
        $record->timecreated = 30;
375
        $this->mockhandler->add_test_session($record);
376
 
377
        $record->timecreated = 10;
378
        $record->userid = $guest->id;
379
        $record->sid = md5('g1');
380
        $this->mockhandler->add_test_session($record);
381
        $record->sid = md5('g2');
382
        $this->mockhandler->add_test_session($record);
383
        $record->sid = md5('g3');
384
        $this->mockhandler->add_test_session($record);
385
 
386
        $record->userid = 0;
387
        $record->sid = md5('nl1');
388
        $this->mockhandler->add_test_session($record);
389
        $record->sid = md5('nl2');
390
        $this->mockhandler->add_test_session($record);
391
        $record->sid = md5('nl3');
392
        $this->mockhandler->add_test_session($record);
393
 
394
        set_config('limitconcurrentlogins', 0);
395
        $this->assertCount(12, $DB->get_records('sessions'));
396
 
397
        \core\session\manager::apply_concurrent_login_limit($user1->id);
398
        \core\session\manager::apply_concurrent_login_limit($user2->id);
399
        \core\session\manager::apply_concurrent_login_limit($guest->id);
400
        \core\session\manager::apply_concurrent_login_limit(0);
401
        $this->assertCount(12, $DB->get_records('sessions'));
402
 
403
        set_config('limitconcurrentlogins', -1);
404
 
405
        \core\session\manager::apply_concurrent_login_limit($user1->id);
406
        \core\session\manager::apply_concurrent_login_limit($user2->id);
407
        \core\session\manager::apply_concurrent_login_limit($guest->id);
408
        \core\session\manager::apply_concurrent_login_limit(0);
409
        $this->assertCount(12, $DB->get_records('sessions'));
410
 
411
        set_config('limitconcurrentlogins', 2);
412
 
413
        \core\session\manager::apply_concurrent_login_limit($user1->id);
414
        $sessions = $this->mockhandler->get_all_sessions();
415
        $this->assertCount(11, $sessions);
416
        $this->assertTrue($this->contains_session(['userid' => $user1->id, 'timecreated' => 20], $sessions));
417
        $this->assertTrue($this->contains_session(['userid' => $user1->id, 'timecreated' => 30], $sessions));
418
        $this->assertFalse($this->contains_session(['userid' => $user1->id, 'timecreated' => 10], $sessions));
419
 
420
        $this->assertTrue($this->contains_session(['userid' => $user2->id, 'timecreated' => 20], $sessions));
421
        $this->assertTrue($this->contains_session(['userid' => $user2->id, 'timecreated' => 30], $sessions));
422
        $this->assertTrue($this->contains_session(['userid' => $user2->id, 'timecreated' => 10], $sessions));
423
        set_config('limitconcurrentlogins', 2);
424
        \core\session\manager::apply_concurrent_login_limit($user2->id, md5('pokus2'));
425
        $sessions = $this->mockhandler->get_all_sessions();
426
        $this->assertCount(10, $sessions);
427
        $this->assertFalse($this->contains_session(['userid' => $user2->id, 'timecreated' => 20], $sessions));
428
        $this->assertTrue($this->contains_session(['userid' => $user2->id, 'timecreated' => 30], $sessions));
429
        $this->assertTrue($this->contains_session(['userid' => $user2->id, 'timecreated' => 10], $sessions));
430
 
431
        \core\session\manager::apply_concurrent_login_limit($guest->id);
432
        \core\session\manager::apply_concurrent_login_limit(0);
433
        $sessions = $this->mockhandler->get_all_sessions();
434
        $this->assertCount(10, $sessions);
435
 
436
        set_config('limitconcurrentlogins', 1);
437
 
438
        \core\session\manager::apply_concurrent_login_limit($user1->id, md5('grrr'));
439
        $sessions = $this->mockhandler->get_all_sessions();
440
        $this->assertCount(9, $sessions);
441
        $this->assertFalse($this->contains_session(['userid' => $user1->id, 'timecreated' => 20], $sessions));
442
        $this->assertTrue($this->contains_session(['userid' => $user1->id, 'timecreated' => 30], $sessions));
443
        $this->assertFalse($this->contains_session(['userid' => $user1->id, 'timecreated' => 10], $sessions));
444
 
445
        \core\session\manager::apply_concurrent_login_limit($user1->id);
446
        $sessions = $this->mockhandler->get_all_sessions();
447
        $this->assertCount(9, $sessions);
448
        $this->assertFalse($this->contains_session(['userid' => $user1->id, 'timecreated' => 20], $sessions));
449
        $this->assertTrue($this->contains_session(['userid' => $user1->id, 'timecreated' => 30], $sessions));
450
        $this->assertFalse($this->contains_session(['userid' => $user1->id, 'timecreated' => 10], $sessions));
451
 
452
        \core\session\manager::apply_concurrent_login_limit($user2->id, md5('pokus2'));
453
        $sessions = $this->mockhandler->get_all_sessions();
454
        $this->assertCount(8, $sessions);
455
        $this->assertFalse($this->contains_session(['userid' => $user2->id, 'timecreated' => 20], $sessions));
456
        $this->assertFalse($this->contains_session(['userid' => $user2->id, 'timecreated' => 30], $sessions));
457
        $this->assertTrue($this->contains_session(['userid' => $user2->id, 'timecreated' => 10], $sessions));
458
 
459
        \core\session\manager::apply_concurrent_login_limit($user2->id);
460
        $sessions = $this->mockhandler->get_all_sessions();
461
        $this->assertCount(8, $sessions);
462
        $this->assertFalse($this->contains_session(['userid' => $user2->id, 'timecreated' => 20], $sessions));
463
        $this->assertFalse($this->contains_session(['userid' => $user2->id, 'timecreated' => 30], $sessions));
464
        $this->assertTrue($this->contains_session(['userid' => $user2->id, 'timecreated' => 10], $sessions));
465
 
466
        \core\session\manager::apply_concurrent_login_limit($guest->id);
467
        \core\session\manager::apply_concurrent_login_limit(0);
468
        $sessions = $this->mockhandler->get_all_sessions();
469
        $this->assertCount(8, $sessions);
470
    }
471
 
472
    /**
473
     * Helper method to check if the sessions array contains a session with the given conditions.
474
     *
475
     * @param array $conditions Conditions to match.
476
     * @param null|\Iterator $sessions Sessions to check.
477
     * @return bool
478
     */
479
    protected function contains_session(array $conditions, ?\Iterator $sessions = null): bool {
480
        foreach ($sessions as $session) {
481
            if ($this->matches_session($conditions, $session)) {
482
                return true;
483
            }
484
        }
485
        return false;
486
    }
487
 
488
    /**
489
     * Helper method to check if the session matches the given conditions.
490
     *
491
     * @param array $conditions Conditions to match.
492
     * @param \stdClass $session Session to check.
493
     * @return bool
494
     */
495
    protected function matches_session(array $conditions, \stdClass $session): bool {
496
        foreach ($conditions as $key => $value) {
497
            if ($session->$key != $value) {
498
                return false;
499
            }
500
        }
501
        return true;
502
    }
503
 
504
    /**
505
     * Test destroy_all method.
506
     *
507
     * @return void
508
     * @throws \dml_exception
509
     */
510
    public function test_destroy_all(): void {
511
        global $DB, $USER;
512
        $this->resetAfterTest();
513
 
514
        $this->setAdminUser();
515
        $userid = $USER->id;
516
 
517
        $sid = md5('hokus');
518
        $record = new \stdClass();
519
        $record->state        = 0;
520
        $record->sid          = $sid;
521
        $record->sessdata     = null;
522
        $record->userid       = $userid;
523
        $record->timecreated  = time() - 60*60;
524
        $record->timemodified = time() - 30;
525
        $record->firstip      = $record->lastip = '10.0.0.1';
526
        $this->mockhandler->add_test_session($record);
527
 
528
        $record->sid          = md5('hokus2');
529
        $this->mockhandler->add_test_session($record);
530
 
531
        $record->userid       = 0;
532
        $record->sid          = md5('pokus');
533
        $this->mockhandler->add_test_session($record);
534
 
535
        $this->assertEquals(3, $DB->count_records('sessions'));
536
 
537
        \core\session\manager::destroy_all();
538
 
539
        $this->assertEquals(0, $DB->count_records('sessions'));
540
        $this->assertSame(0, $USER->id);
541
    }
542
 
543
    public function test_gc(): void {
544
        global $CFG, $USER;
545
        $this->resetAfterTest();
546
 
547
        $this->setAdminUser();
548
        $adminid = $USER->id;
549
        $this->setGuestUser();
550
        $guestid = $USER->id;
551
        $this->setUser(0);
552
 
553
        // Set sessions timeout to 600 (10 minutes) seconds.
554
        // We will test if sessions not modified for 600 seconds are removed.
555
        $CFG->sessiontimeout = 60*10;
556
 
557
        $record = new \stdClass();
558
        $record->state        = 0;
559
        $record->sid          = md5('hokus1');
560
        $record->sessdata     = null;
561
        $record->userid       = $adminid;
562
        $record->timecreated  = time() - 60*60;
563
        $record->timemodified = time() - 30;
564
        $record->firstip      = $record->lastip = '10.0.0.1';
565
        $r1 = $this->mockhandler->add_test_session($record);
566
 
567
        $record->sid          = md5('hokus2');
568
        $record->userid       = $adminid;
569
        $record->timecreated  = time() - 60*60;
570
        $record->timemodified = time() - 60*20;
571
        $r2 = $this->mockhandler->add_test_session($record);
572
 
573
        // Guest session still within the session timeout limit.
574
        $record->sid          = md5('hokus3');
575
        $record->userid       = $guestid;
576
        $record->timecreated  = time() - 60*60*60;
577
        $record->timemodified = time() - 60*5;
578
        $r3 = $this->mockhandler->add_test_session($record);
579
 
580
        // Guest session outside the session timeout limit.
581
        $record->sid          = md5('hokus4');
582
        $record->userid       = $guestid;
583
        $record->timecreated  = time() - 60*60*60;
584
        $record->timemodified = time() - 60*10*5 - 60;
585
        $r4 = $this->mockhandler->add_test_session($record);
586
 
587
        $record->sid          = md5('hokus5');
588
        $record->userid       = 0;
589
        $record->timecreated  = time() - 60*5;
590
        $record->timemodified = time() - 60*5;
591
        $r5 = $this->mockhandler->add_test_session($record);
592
 
593
        $record->sid          = md5('hokus6');
594
        $record->userid       = 0;
595
        $record->timecreated  = time() - 60*60;
596
        $record->timemodified = time() - 60*10 -10;
597
        $r6 = $this->mockhandler->add_test_session($record);
598
 
599
        $record->sid          = md5('hokus7');
600
        $record->userid       = 0;
601
        $record->timecreated  = time() - 60*60;
602
        $record->timemodified = time() - 60*9;
603
        $r7 = $this->mockhandler->add_test_session($record);
604
 
605
        \core\session\manager::gc($CFG->sessiontimeout);
606
        $sessions = $this->mockhandler->get_all_sessions();
607
        $this->assertTrue($this->contains_session(['id' => $r1], $sessions));
608
        $this->assertFalse($this->contains_session(['id' => $r2], $sessions));
609
        $this->assertTrue($this->contains_session(['id' => $r3], $sessions));
610
        $this->assertFalse($this->contains_session(['id' => $r4], $sessions));
611
        $this->assertFalse($this->contains_session(['id' => $r5], $sessions));
612
        $this->assertFalse($this->contains_session(['id' => $r6], $sessions));
613
        $this->assertTrue($this->contains_session(['id' => $r7], $sessions));
614
    }
615
 
616
    /**
617
     * Test loginas.
618
     * @copyright  2103 Rajesh Taneja <rajesh@moodle.com>
619
     */
620
    public function test_loginas(): void {
621
        global $USER, $SESSION;
622
        $this->resetAfterTest();
623
 
624
        // Set current user as Admin user and save it for later use.
625
        $this->setAdminUser();
626
        $adminuser = $USER;
627
        $adminsession = $SESSION;
628
        $user = $this->getDataGenerator()->create_user();
629
        $_SESSION['extra'] = true;
630
 
631
        // Try admin loginas this user in system context.
632
        $this->assertObjectNotHasProperty('realuser', $USER);
633
        \core\session\manager::loginas($user->id, \context_system::instance());
634
 
635
        $this->assertSame($user->id, $USER->id);
636
        $this->assertEquals(\context_system::instance(), $USER->loginascontext);
637
        $this->assertSame($adminuser->id, $USER->realuser);
638
        $this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
639
        $this->assertSame($GLOBALS['USER'], $USER);
640
        $this->assertNotSame($adminuser, $_SESSION['REALUSER']);
641
        $this->assertEquals($adminuser, $_SESSION['REALUSER']);
642
 
643
        $this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
644
        $this->assertSame($GLOBALS['SESSION'], $SESSION);
645
        $this->assertNotSame($adminsession, $_SESSION['REALSESSION']);
646
        $this->assertEquals($adminsession, $_SESSION['REALSESSION']);
647
 
648
        $this->assertArrayNotHasKey('extra', $_SESSION);
649
 
650
        // Set user as current user and login as admin user in course context.
651
        \core\session\manager::init_empty_session();
652
        $this->setUser($user);
653
        $this->assertNotEquals($adminuser->id, $USER->id);
654
        $course = $this->getDataGenerator()->create_course();
655
        $coursecontext = \context_course::instance($course->id);
656
 
657
        // Catch event triggered.
658
        $sink = $this->redirectEvents();
659
        \core\session\manager::loginas($adminuser->id, $coursecontext);
660
        $events = $sink->get_events();
661
        $sink->close();
662
        $event = array_pop($events);
663
 
664
        $this->assertSame($adminuser->id, $USER->id);
665
        $this->assertSame($coursecontext, $USER->loginascontext);
666
        $this->assertSame($user->id, $USER->realuser);
667
 
668
        // Test event captured has proper information.
669
        $this->assertInstanceOf('\core\event\user_loggedinas', $event);
670
        $this->assertSame($user->id, $event->objectid);
671
        $this->assertSame($adminuser->id, $event->relateduserid);
672
        $this->assertSame($course->id, $event->courseid);
673
        $this->assertEquals($coursecontext, $event->get_context());
674
        $oldfullname = fullname($user, true);
675
        $newfullname = fullname($adminuser, true);
676
    }
677
 
678
    public function test_is_loggedinas(): void {
679
        $this->resetAfterTest();
680
 
681
        $user1 = $this->getDataGenerator()->create_user();
682
        $user2 = $this->getDataGenerator()->create_user();
683
 
684
        $this->assertFalse(\core\session\manager::is_loggedinas());
685
 
686
        $this->setUser($user1);
687
        \core\session\manager::loginas($user2->id, \context_system::instance());
688
 
689
        $this->assertTrue(\core\session\manager::is_loggedinas());
690
    }
691
 
692
    public function test_get_realuser(): void {
693
        $this->resetAfterTest();
694
 
695
        $user1 = $this->getDataGenerator()->create_user();
696
        $user2 = $this->getDataGenerator()->create_user();
697
 
698
        $this->setUser($user1);
699
        $normal = \core\session\manager::get_realuser();
700
        $this->assertSame($GLOBALS['USER'], $normal);
701
 
702
        \core\session\manager::loginas($user2->id, \context_system::instance());
703
 
704
        $real = \core\session\manager::get_realuser();
705
 
706
        unset($real->password);
707
        unset($real->description);
708
        unset($real->sesskey);
709
        unset($user1->password);
710
        unset($user1->description);
711
        unset($user1->sesskey);
712
 
713
        $this->assertEquals($real, $user1);
714
        $this->assertSame($_SESSION['REALUSER'], $real);
715
    }
716
 
717
    /**
718
     * Session lock info on pages.
719
     *
720
     * @return array
721
     */
722
    public function pages_sessionlocks(): array {
723
        return [
724
            [
725
                'url'      => '/good.php',
726
                'start'    => 1500000001.000,
727
                'gained'   => 1500000002.000,
728
                'released' => 1500000003.000,
729
                'wait'     => 1.0,
730
                'held'     => 1.0
731
            ],
732
            [
733
                'url'      => '/bad.php?wait=5',
734
                'start'    => 1500000003.000,
735
                'gained'   => 1500000005.000,
736
                'released' => 1500000007.000,
737
                'held'     => 2.0,
738
                'wait'     => 2.0
739
            ]
740
        ];
741
    }
742
 
743
    /**
744
     * Test to get recent session locks.
745
     */
746
    public function test_get_recent_session_locks(): void {
747
        global $CFG;
748
 
749
        $this->resetAfterTest();
750
        $CFG->debugsessionlock = 5;
751
        $pages = $this->pages_sessionlocks();
752
        // Recent session locks must be empty at first.
753
        $recentsessionlocks = \core\session\manager::get_recent_session_locks();
754
        $this->assertEmpty($recentsessionlocks);
755
 
756
        // Add page to the recentsessionlocks array.
757
        \core\session\manager::update_recent_session_locks($pages[0]);
758
        $recentsessionlocks = \core\session\manager::get_recent_session_locks();
759
        // Make sure we are getting the first page we added.
760
        $this->assertEquals($pages[0], $recentsessionlocks[0]);
761
        // There should be 1 page in the array.
762
        $this->assertCount(1, $recentsessionlocks);
763
 
764
        // Add second page to the recentsessionlocks array.
765
        \core\session\manager::update_recent_session_locks($pages[1]);
766
        $recentsessionlocks = \core\session\manager::get_recent_session_locks();
767
        // Make sure we are getting the second page we added.
768
        $this->assertEquals($pages[1], $recentsessionlocks[1]);
769
        // There should be 2 pages in the array.
770
        $this->assertCount(2, $recentsessionlocks);
771
    }
772
 
773
    /**
774
     * Test to update recent session locks.
775
     */
776
    public function test_update_recent_session_locks(): void {
777
        global $CFG;
778
 
779
        $this->resetAfterTest();
780
        $CFG->debugsessionlock = 5;
781
        $pages = $this->pages_sessionlocks();
782
 
783
        \core\session\manager::update_recent_session_locks($pages[0]);
784
        \core\session\manager::update_recent_session_locks($pages[1]);
785
        $recentsessionlocks = \core\session\manager::get_recent_session_locks();
786
        // There should be 2 pages in the array.
787
        $this->assertCount(2, $recentsessionlocks);
788
        // Make sure the last page is added at the end of the array.
789
        $this->assertEquals($pages[1], end($recentsessionlocks));
790
 
791
    }
792
 
793
    /**
794
     * Test to get session lock info.
795
     */
796
    public function test_get_session_lock_info(): void {
797
        global $PERF;
798
 
799
        $this->resetAfterTest();
800
 
801
        $pages = $this->pages_sessionlocks();
802
        $PERF->sessionlock = $pages[0];
803
        $sessionlock = \core\session\manager::get_session_lock_info();
804
        $this->assertEquals($pages[0], $sessionlock);
805
    }
806
 
807
    /**
808
     * Session lock info on some pages to serve as history.
809
     *
810
     * @return array
811
     */
812
    public function sessionlock_history(): array {
813
        return [
814
            [
815
                'url'      => '/good.php',
816
                'start'    => 1500000001.000,
817
                'gained'   => 1500000001.100,
818
                'released' => 1500000001.500,
819
                'wait'     => 0.1
820
            ],
821
            [
822
                // This bad request doesn't release the session for 10 seconds.
823
                'url'      => '/bad.php',
824
                'start'    => 1500000012.000,
825
                'gained'   => 1500000012.200,
826
                'released' => 1500000020.200,
827
                'wait'     => 0.2
828
            ],
829
            [
830
                // All subsequent requests are blocked and need to wait.
831
                'url'      => '/good.php?id=1',
832
                'start'    => 1500000012.900,
833
                'gained'   => 1500000020.200,
834
                'released' => 1500000022.000,
835
                'wait'     => 7.29
836
            ],
837
            [
838
                'url'      => '/good.php?id=2',
839
                'start'    => 1500000014.000,
840
                'gained'   => 1500000022.000,
841
                'released' => 1500000025.000,
842
                'wait'     => 8.0
843
            ],
844
            [
845
                'url'      => '/good.php?id=3',
846
                'start'    => 1500000015.000,
847
                'gained'   => 1500000025.000,
848
                'released' => 1500000026.000,
849
                'wait'     => 10.0
850
            ],
851
            [
852
                'url'      => '/good.php?id=4',
853
                'start'    => 1500000016.000,
854
                'gained'   => 1500000026.000,
855
                'released' => 1500000027.000,
856
                'wait'     => 10.0
857
            ]
858
        ];
859
    }
860
 
861
    /**
862
     * Data provider for test_get_locked_page_at function.
863
     *
864
     * @return array
865
     */
866
    public static function sessionlocks_info_provider(): array {
867
        return [
868
            [
869
                'url'      => null,
870
                'time'    => 1500000001.000
871
            ],
872
            [
873
                'url'      => '/bad.php',
874
                'time'    => 1500000014.000
875
            ],
876
            [
877
                'url'      => '/good.php?id=2',
878
                'time'    => 1500000022.500
879
            ],
880
        ];
881
    }
882
 
883
    /**
884
     * Test to get locked page at a speficic timestamp.
885
     *
886
     * @dataProvider sessionlocks_info_provider
887
     * @param array $url Session lock page url.
888
     * @param array $time Session lock time.
889
     */
890
    public function test_get_locked_page_at($url, $time): void {
891
        global $CFG, $SESSION;
892
 
893
        $this->resetAfterTest();
894
        $CFG->debugsessionlock = 5;
895
        $SESSION->recentsessionlocks = $this->sessionlock_history();
896
 
897
        $page = \core\session\manager::get_locked_page_at($time);
898
        $this->assertEquals($url, is_array($page) ? $page['url'] : null);
899
    }
900
 
901
    /**
902
     * Test cleanup recent session locks.
903
     */
904
    public function test_cleanup_recent_session_locks(): void {
905
        global $CFG, $SESSION;
906
 
907
        $this->resetAfterTest();
908
        $CFG->debugsessionlock = 5;
909
 
910
        $SESSION->recentsessionlocks = $this->sessionlock_history();
911
        $this->assertCount(6, $SESSION->recentsessionlocks);
912
        \core\session\manager::cleanup_recent_session_locks();
913
        // Make sure the session history has been cleaned up and only has the latest page.
914
        $this->assertCount(1, $SESSION->recentsessionlocks);
915
        $this->assertEquals('/good.php?id=4', $SESSION->recentsessionlocks[0]['url']);
916
    }
917
 
918
    /**
919
     * Data provider for the array_session_diff function.
920
     *
921
     * @return array
922
     */
923
    public static function array_session_diff_provider(): array {
924
        // Create an instance of this object so the comparison object's identities are the same.
925
        // Used in one of the tests below.
926
        $compareobjectb = (object) ['array' => 'b'];
927
 
928
        return [
929
            'both same objects' => [
930
                'a' => ['example' => (object) ['array' => 'a']],
931
                'b' => ['example' => (object) ['array' => 'a']],
932
                'expected' => [],
933
            ],
934
            'both same arrays' => [
935
                'a' => ['example' => ['array' => 'a']],
936
                'b' => ['example' => ['array' => 'a']],
937
                'expected' => [],
938
            ],
939
            'both the same with nested objects' => [
940
                'a' => ['example' => (object) ['array' => 'a', 'deeper' => (object) []]],
941
                'b' => ['example' => (object) ['array' => 'a', 'deeper' => (object) []]],
942
                'expected' => [],
943
            ],
944
            'first array larger' => [
945
                'a' => ['x' => 1, 'y' => 2],
946
                'b' => ['x' => 1],
947
                'expected' => ['y' => 2]
948
            ],
949
            'second array larger' => [
950
                'a' => ['x' => 1],
951
                'b' => ['x' => 1, 'y' => 2],
952
                'expected' => ['y' => 2]
953
            ],
954
            'objects with different values but same keys' => [
955
                'a' => ['example' => (object) ['array' => 'a']],
956
                'b' => ['example' => $compareobjectb],
957
                'expected' => ['example' => $compareobjectb]
958
            ],
959
            'different arrays with top level indexes' => [
960
                'a' => ['x', 'y'],
961
                'b' => ['x', 'y', 'z'],
962
                'expected' => [2 => 'z']
963
            ],
964
            'different types but same values as first level' => [
965
                'a' => ['example' => (object) ['array' => 'a']],
966
                'b' => ['example' => ['array' => 'a']],
967
                'expected' => ['example' => ['array' => 'a']]
968
            ],
969
            'different types but same values nested' => [
970
                'a' => ['example' => (object) ['array' => ['a' => 'test']]],
971
                'b' => ['example' => (object) ['array' => (object) ['a' => 'test']]],
972
                // Type checking is not done further than the first level, so we expect no difference.
973
                'expected' => []
974
            ]
975
        ];
976
    }
977
 
978
    /**
979
     * Tests array diff method in various situations.
980
     *
981
     * @dataProvider array_session_diff_provider
982
     * @covers \core\session\manager::array_session_diff
983
     * @param array $a first value.
984
     * @param array $b second value to compare to $a.
985
     * @param array $expected the expected difference.
986
     */
987
    public function test_array_session_diff(array $a, array $b, array $expected): void {
988
        $class = new \ReflectionClass('\core\session\manager');
989
        $method = $class->getMethod('array_session_diff');
990
 
991
        $result = $method->invokeArgs(null, [$a, $b]);
992
        $this->assertSame($expected, $result);
993
    }
994
 
995
    /**
996
     * Test destroy by auth plugin method.
997
     */
998
    public function test_destroy_by_auth_plugin(): void {
999
        $this->resetAfterTest();
1000
        global $DB;
1001
 
1002
        // Create test users.
1003
        $user1 = $this->getDataGenerator()->create_user();
1004
        $user2 = $this->getDataGenerator()->create_user(['auth' => 'db']);
1005
 
1006
        // Create sessions for the users.
1007
        $user1sid = md5('hokus');
1008
        $record = new \stdClass();
1009
        $record->state        = 0;
1010
        $record->sid          = $user1sid;
1011
        $record->sessdata     = null;
1012
        $record->userid       = $user1->id;
1013
        $record->timecreated  = time() - 60 * 60;
1014
        $record->timemodified = time() - 30;
1015
        $record->firstip      = $record->lastip = '10.0.0.1';
1016
        $this->mockhandler->add_test_session($record);
1017
 
1018
        $record->sid          = md5('pokus');
1019
        $record->userid       = $user2->id;
1020
        $this->mockhandler->add_test_session($record);
1021
 
1022
        // Check sessions.
1023
        $sessions = $this->mockhandler->get_all_sessions();
1024
        $this->assertEquals(2, count($sessions));
1025
 
1026
        // Destroy the session for the user with manual auth plugin.
1027
        \core\session\manager::destroy_by_auth_plugin('manual');
1028
 
1029
        // Check that the session for the user with manual auth plugin is destroyed.
1030
        $sessions = $this->mockhandler->get_all_sessions();
1031
        $this->assertEquals(1, count($sessions));
1032
        $this->assertFalse($this->contains_session(['sid' => $user1sid], $sessions));
1033
    }
1034
}