Merge "Type hint against LinkTarget in WatchedItemStore"
[lhc/web/wiklou.git] / tests / phpunit / includes / session / SessionManagerTest.php
1 <?php
2
3 namespace MediaWiki\Session;
4
5 use MediaWikiTestCase;
6 use Psr\Log\LogLevel;
7 use User;
8 use Wikimedia\TestingAccessWrapper;
9
10 /**
11 * @group Session
12 * @group Database
13 * @covers MediaWiki\Session\SessionManager
14 */
15 class SessionManagerTest extends MediaWikiTestCase {
16
17 /** @var \HashConfig */
18 private $config;
19
20 /** @var \TestLogger */
21 private $logger;
22
23 /** @var TestBagOStuff */
24 private $store;
25
26 protected function getManager() {
27 \ObjectCache::$instances['testSessionStore'] = new TestBagOStuff();
28 $this->config = new \HashConfig( [
29 'LanguageCode' => 'en',
30 'SessionCacheType' => 'testSessionStore',
31 'ObjectCacheSessionExpiry' => 100,
32 'SessionProviders' => [
33 [ 'class' => \DummySessionProvider::class ],
34 ]
35 ] );
36 $this->logger = new \TestLogger( false, function ( $m ) {
37 return substr( $m, 0, 15 ) === 'SessionBackend ' ? null : $m;
38 } );
39 $this->store = new TestBagOStuff();
40
41 return new SessionManager( [
42 'config' => $this->config,
43 'logger' => $this->logger,
44 'store' => $this->store,
45 ] );
46 }
47
48 protected function objectCacheDef( $object ) {
49 return [ 'factory' => function () use ( $object ) {
50 return $object;
51 } ];
52 }
53
54 public function testSingleton() {
55 $reset = TestUtils::setSessionManagerSingleton( null );
56
57 $singleton = SessionManager::singleton();
58 $this->assertInstanceOf( SessionManager::class, $singleton );
59 $this->assertSame( $singleton, SessionManager::singleton() );
60 }
61
62 public function testGetGlobalSession() {
63 $context = \RequestContext::getMain();
64
65 if ( !PHPSessionHandler::isInstalled() ) {
66 PHPSessionHandler::install( SessionManager::singleton() );
67 }
68 $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' );
69 $rProp->setAccessible( true );
70 $handler = TestingAccessWrapper::newFromObject( $rProp->getValue() );
71 $oldEnable = $handler->enable;
72 $reset[] = new \Wikimedia\ScopedCallback( function () use ( $handler, $oldEnable ) {
73 if ( $handler->enable ) {
74 session_write_close();
75 }
76 $handler->enable = $oldEnable;
77 } );
78 $reset[] = TestUtils::setSessionManagerSingleton( $this->getManager() );
79
80 $handler->enable = true;
81 $request = new \FauxRequest();
82 $context->setRequest( $request );
83 $id = $request->getSession()->getId();
84
85 session_write_close();
86 session_id( '' );
87 $session = SessionManager::getGlobalSession();
88 $this->assertSame( $id, $session->getId() );
89
90 session_id( 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' );
91 $session = SessionManager::getGlobalSession();
92 $this->assertSame( 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', $session->getId() );
93 $this->assertSame( 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', $request->getSession()->getId() );
94
95 session_write_close();
96 $handler->enable = false;
97 $request = new \FauxRequest();
98 $context->setRequest( $request );
99 $id = $request->getSession()->getId();
100
101 session_id( '' );
102 $session = SessionManager::getGlobalSession();
103 $this->assertSame( $id, $session->getId() );
104
105 session_id( 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' );
106 $session = SessionManager::getGlobalSession();
107 $this->assertSame( $id, $session->getId() );
108 $this->assertSame( $id, $request->getSession()->getId() );
109 }
110
111 public function testConstructor() {
112 $manager = TestingAccessWrapper::newFromObject( $this->getManager() );
113 $this->assertSame( $this->config, $manager->config );
114 $this->assertSame( $this->logger, $manager->logger );
115 $this->assertSame( $this->store, $manager->store );
116
117 $manager = TestingAccessWrapper::newFromObject( new SessionManager() );
118 $this->assertSame( \RequestContext::getMain()->getConfig(), $manager->config );
119
120 $manager = TestingAccessWrapper::newFromObject( new SessionManager( [
121 'config' => $this->config,
122 ] ) );
123 $this->assertSame( \ObjectCache::$instances['testSessionStore'], $manager->store );
124
125 foreach ( [
126 'config' => '$options[\'config\'] must be an instance of Config',
127 'logger' => '$options[\'logger\'] must be an instance of LoggerInterface',
128 'store' => '$options[\'store\'] must be an instance of BagOStuff',
129 ] as $key => $error ) {
130 try {
131 new SessionManager( [ $key => new \stdClass ] );
132 $this->fail( 'Expected exception not thrown' );
133 } catch ( \InvalidArgumentException $ex ) {
134 $this->assertSame( $error, $ex->getMessage() );
135 }
136 }
137 }
138
139 public function testGetSessionForRequest() {
140 $manager = $this->getManager();
141 $request = new \FauxRequest();
142 $request->unpersist1 = false;
143 $request->unpersist2 = false;
144
145 $id1 = '';
146 $id2 = '';
147 $idEmpty = 'empty-session-------------------';
148
149 $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
150 ->setMethods(
151 [ 'provideSessionInfo', 'newSessionInfo', '__toString', 'describe', 'unpersistSession' ]
152 );
153
154 $provider1 = $providerBuilder->getMock();
155 $provider1->expects( $this->any() )->method( 'provideSessionInfo' )
156 ->with( $this->identicalTo( $request ) )
157 ->will( $this->returnCallback( function ( $request ) {
158 return $request->info1;
159 } ) );
160 $provider1->expects( $this->any() )->method( 'newSessionInfo' )
161 ->will( $this->returnCallback( function () use ( $idEmpty, $provider1 ) {
162 return new SessionInfo( SessionInfo::MIN_PRIORITY, [
163 'provider' => $provider1,
164 'id' => $idEmpty,
165 'persisted' => true,
166 'idIsSafe' => true,
167 ] );
168 } ) );
169 $provider1->expects( $this->any() )->method( '__toString' )
170 ->will( $this->returnValue( 'Provider1' ) );
171 $provider1->expects( $this->any() )->method( 'describe' )
172 ->will( $this->returnValue( '#1 sessions' ) );
173 $provider1->expects( $this->any() )->method( 'unpersistSession' )
174 ->will( $this->returnCallback( function ( $request ) {
175 $request->unpersist1 = true;
176 } ) );
177
178 $provider2 = $providerBuilder->getMock();
179 $provider2->expects( $this->any() )->method( 'provideSessionInfo' )
180 ->with( $this->identicalTo( $request ) )
181 ->will( $this->returnCallback( function ( $request ) {
182 return $request->info2;
183 } ) );
184 $provider2->expects( $this->any() )->method( '__toString' )
185 ->will( $this->returnValue( 'Provider2' ) );
186 $provider2->expects( $this->any() )->method( 'describe' )
187 ->will( $this->returnValue( '#2 sessions' ) );
188 $provider2->expects( $this->any() )->method( 'unpersistSession' )
189 ->will( $this->returnCallback( function ( $request ) {
190 $request->unpersist2 = true;
191 } ) );
192
193 $this->config->set( 'SessionProviders', [
194 $this->objectCacheDef( $provider1 ),
195 $this->objectCacheDef( $provider2 ),
196 ] );
197
198 // No provider returns info
199 $request->info1 = null;
200 $request->info2 = null;
201 $session = $manager->getSessionForRequest( $request );
202 $this->assertInstanceOf( Session::class, $session );
203 $this->assertSame( $idEmpty, $session->getId() );
204 $this->assertFalse( $request->unpersist1 );
205 $this->assertFalse( $request->unpersist2 );
206
207 // Both providers return info, picks best one
208 $request->info1 = new SessionInfo( SessionInfo::MIN_PRIORITY + 1, [
209 'provider' => $provider1,
210 'id' => ( $id1 = $manager->generateSessionId() ),
211 'persisted' => true,
212 'idIsSafe' => true,
213 ] );
214 $request->info2 = new SessionInfo( SessionInfo::MIN_PRIORITY + 2, [
215 'provider' => $provider2,
216 'id' => ( $id2 = $manager->generateSessionId() ),
217 'persisted' => true,
218 'idIsSafe' => true,
219 ] );
220 $session = $manager->getSessionForRequest( $request );
221 $this->assertInstanceOf( Session::class, $session );
222 $this->assertSame( $id2, $session->getId() );
223 $this->assertFalse( $request->unpersist1 );
224 $this->assertFalse( $request->unpersist2 );
225
226 $request->info1 = new SessionInfo( SessionInfo::MIN_PRIORITY + 2, [
227 'provider' => $provider1,
228 'id' => ( $id1 = $manager->generateSessionId() ),
229 'persisted' => true,
230 'idIsSafe' => true,
231 ] );
232 $request->info2 = new SessionInfo( SessionInfo::MIN_PRIORITY + 1, [
233 'provider' => $provider2,
234 'id' => ( $id2 = $manager->generateSessionId() ),
235 'persisted' => true,
236 'idIsSafe' => true,
237 ] );
238 $session = $manager->getSessionForRequest( $request );
239 $this->assertInstanceOf( Session::class, $session );
240 $this->assertSame( $id1, $session->getId() );
241 $this->assertFalse( $request->unpersist1 );
242 $this->assertFalse( $request->unpersist2 );
243
244 // Tied priorities
245 $request->info1 = new SessionInfo( SessionInfo::MAX_PRIORITY, [
246 'provider' => $provider1,
247 'id' => ( $id1 = $manager->generateSessionId() ),
248 'persisted' => true,
249 'userInfo' => UserInfo::newAnonymous(),
250 'idIsSafe' => true,
251 ] );
252 $request->info2 = new SessionInfo( SessionInfo::MAX_PRIORITY, [
253 'provider' => $provider2,
254 'id' => ( $id2 = $manager->generateSessionId() ),
255 'persisted' => true,
256 'userInfo' => UserInfo::newAnonymous(),
257 'idIsSafe' => true,
258 ] );
259 try {
260 $manager->getSessionForRequest( $request );
261 $this->fail( 'Expcected exception not thrown' );
262 } catch ( \OverflowException $ex ) {
263 $this->assertStringStartsWith(
264 'Multiple sessions for this request tied for top priority: ',
265 $ex->getMessage()
266 );
267 $this->assertCount( 2, $ex->sessionInfos );
268 $this->assertContains( $request->info1, $ex->sessionInfos );
269 $this->assertContains( $request->info2, $ex->sessionInfos );
270 }
271 $this->assertFalse( $request->unpersist1 );
272 $this->assertFalse( $request->unpersist2 );
273
274 // Bad provider
275 $request->info1 = new SessionInfo( SessionInfo::MAX_PRIORITY, [
276 'provider' => $provider2,
277 'id' => ( $id1 = $manager->generateSessionId() ),
278 'persisted' => true,
279 'idIsSafe' => true,
280 ] );
281 $request->info2 = null;
282 try {
283 $manager->getSessionForRequest( $request );
284 $this->fail( 'Expcected exception not thrown' );
285 } catch ( \UnexpectedValueException $ex ) {
286 $this->assertSame(
287 'Provider1 returned session info for a different provider: ' . $request->info1,
288 $ex->getMessage()
289 );
290 }
291 $this->assertFalse( $request->unpersist1 );
292 $this->assertFalse( $request->unpersist2 );
293
294 // Unusable session info
295 $this->logger->setCollect( true );
296 $request->info1 = new SessionInfo( SessionInfo::MAX_PRIORITY, [
297 'provider' => $provider1,
298 'id' => ( $id1 = $manager->generateSessionId() ),
299 'persisted' => true,
300 'userInfo' => UserInfo::newFromName( 'UTSysop', false ),
301 'idIsSafe' => true,
302 ] );
303 $request->info2 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
304 'provider' => $provider2,
305 'id' => ( $id2 = $manager->generateSessionId() ),
306 'persisted' => true,
307 'idIsSafe' => true,
308 ] );
309 $session = $manager->getSessionForRequest( $request );
310 $this->assertInstanceOf( Session::class, $session );
311 $this->assertSame( $id2, $session->getId() );
312 $this->logger->setCollect( false );
313 $this->assertTrue( $request->unpersist1 );
314 $this->assertFalse( $request->unpersist2 );
315 $request->unpersist1 = false;
316
317 $this->logger->setCollect( true );
318 $request->info1 = new SessionInfo( SessionInfo::MAX_PRIORITY, [
319 'provider' => $provider1,
320 'id' => ( $id1 = $manager->generateSessionId() ),
321 'persisted' => true,
322 'idIsSafe' => true,
323 ] );
324 $request->info2 = new SessionInfo( SessionInfo::MAX_PRIORITY, [
325 'provider' => $provider2,
326 'id' => ( $id2 = $manager->generateSessionId() ),
327 'persisted' => true,
328 'userInfo' => UserInfo::newFromName( 'UTSysop', false ),
329 'idIsSafe' => true,
330 ] );
331 $session = $manager->getSessionForRequest( $request );
332 $this->assertInstanceOf( Session::class, $session );
333 $this->assertSame( $id1, $session->getId() );
334 $this->logger->setCollect( false );
335 $this->assertFalse( $request->unpersist1 );
336 $this->assertTrue( $request->unpersist2 );
337 $request->unpersist2 = false;
338
339 // Unpersisted session ID
340 $request->info1 = new SessionInfo( SessionInfo::MAX_PRIORITY, [
341 'provider' => $provider1,
342 'id' => ( $id1 = $manager->generateSessionId() ),
343 'persisted' => false,
344 'userInfo' => UserInfo::newFromName( 'UTSysop', true ),
345 'idIsSafe' => true,
346 ] );
347 $request->info2 = null;
348 $session = $manager->getSessionForRequest( $request );
349 $this->assertInstanceOf( Session::class, $session );
350 $this->assertSame( $id1, $session->getId() );
351 $this->assertTrue( $request->unpersist1 ); // The saving of the session does it
352 $this->assertFalse( $request->unpersist2 );
353 $session->persist();
354 $this->assertTrue( $session->isPersistent(), 'sanity check' );
355 }
356
357 public function testGetSessionById() {
358 $manager = $this->getManager();
359 try {
360 $manager->getSessionById( 'bad' );
361 $this->fail( 'Expected exception not thrown' );
362 } catch ( \InvalidArgumentException $ex ) {
363 $this->assertSame( 'Invalid session ID', $ex->getMessage() );
364 }
365
366 // Unknown session ID
367 $id = $manager->generateSessionId();
368 $session = $manager->getSessionById( $id, true );
369 $this->assertInstanceOf( Session::class, $session );
370 $this->assertSame( $id, $session->getId() );
371
372 $id = $manager->generateSessionId();
373 $this->assertNull( $manager->getSessionById( $id, false ) );
374
375 // Known but unloadable session ID
376 $this->logger->setCollect( true );
377 $id = $manager->generateSessionId();
378 $this->store->setSession( $id, [ 'metadata' => [
379 'userId' => User::idFromName( 'UTSysop' ),
380 'userToken' => 'bad',
381 ] ] );
382
383 $this->assertNull( $manager->getSessionById( $id, true ) );
384 $this->assertNull( $manager->getSessionById( $id, false ) );
385 $this->logger->setCollect( false );
386
387 // Known session ID
388 $this->store->setSession( $id, [] );
389 $session = $manager->getSessionById( $id, false );
390 $this->assertInstanceOf( Session::class, $session );
391 $this->assertSame( $id, $session->getId() );
392
393 // Store isn't checked if the session is already loaded
394 $this->store->setSession( $id, [ 'metadata' => [
395 'userId' => User::idFromName( 'UTSysop' ),
396 'userToken' => 'bad',
397 ] ] );
398 $session2 = $manager->getSessionById( $id, false );
399 $this->assertInstanceOf( Session::class, $session2 );
400 $this->assertSame( $id, $session2->getId() );
401 unset( $session, $session2 );
402 $this->logger->setCollect( true );
403 $this->assertNull( $manager->getSessionById( $id, true ) );
404 $this->logger->setCollect( false );
405
406 // Failure to create an empty session
407 $manager = $this->getManager();
408 $provider = $this->getMockBuilder( \DummySessionProvider::class )
409 ->setMethods( [ 'provideSessionInfo', 'newSessionInfo', '__toString' ] )
410 ->getMock();
411 $provider->expects( $this->any() )->method( 'provideSessionInfo' )
412 ->will( $this->returnValue( null ) );
413 $provider->expects( $this->any() )->method( 'newSessionInfo' )
414 ->will( $this->returnValue( null ) );
415 $provider->expects( $this->any() )->method( '__toString' )
416 ->will( $this->returnValue( 'MockProvider' ) );
417 $this->config->set( 'SessionProviders', [
418 $this->objectCacheDef( $provider ),
419 ] );
420 $this->logger->setCollect( true );
421 $this->assertNull( $manager->getSessionById( $id, true ) );
422 $this->logger->setCollect( false );
423 $this->assertSame( [
424 [ LogLevel::ERROR, 'Failed to create empty session: {exception}' ]
425 ], $this->logger->getBuffer() );
426 }
427
428 public function testGetEmptySession() {
429 $manager = $this->getManager();
430 $pmanager = TestingAccessWrapper::newFromObject( $manager );
431 $request = new \FauxRequest();
432
433 $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
434 ->setMethods( [ 'provideSessionInfo', 'newSessionInfo', '__toString' ] );
435
436 $expectId = null;
437 $info1 = null;
438 $info2 = null;
439
440 $provider1 = $providerBuilder->getMock();
441 $provider1->expects( $this->any() )->method( 'provideSessionInfo' )
442 ->will( $this->returnValue( null ) );
443 $provider1->expects( $this->any() )->method( 'newSessionInfo' )
444 ->with( $this->callback( function ( $id ) use ( &$expectId ) {
445 return $id === $expectId;
446 } ) )
447 ->will( $this->returnCallback( function () use ( &$info1 ) {
448 return $info1;
449 } ) );
450 $provider1->expects( $this->any() )->method( '__toString' )
451 ->will( $this->returnValue( 'MockProvider1' ) );
452
453 $provider2 = $providerBuilder->getMock();
454 $provider2->expects( $this->any() )->method( 'provideSessionInfo' )
455 ->will( $this->returnValue( null ) );
456 $provider2->expects( $this->any() )->method( 'newSessionInfo' )
457 ->with( $this->callback( function ( $id ) use ( &$expectId ) {
458 return $id === $expectId;
459 } ) )
460 ->will( $this->returnCallback( function () use ( &$info2 ) {
461 return $info2;
462 } ) );
463 $provider1->expects( $this->any() )->method( '__toString' )
464 ->will( $this->returnValue( 'MockProvider2' ) );
465
466 $this->config->set( 'SessionProviders', [
467 $this->objectCacheDef( $provider1 ),
468 $this->objectCacheDef( $provider2 ),
469 ] );
470
471 // No info
472 $expectId = null;
473 $info1 = null;
474 $info2 = null;
475 try {
476 $manager->getEmptySession();
477 $this->fail( 'Expected exception not thrown' );
478 } catch ( \UnexpectedValueException $ex ) {
479 $this->assertSame(
480 'No provider could provide an empty session!',
481 $ex->getMessage()
482 );
483 }
484
485 // Info
486 $expectId = null;
487 $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
488 'provider' => $provider1,
489 'id' => 'empty---------------------------',
490 'persisted' => true,
491 'idIsSafe' => true,
492 ] );
493 $info2 = null;
494 $session = $manager->getEmptySession();
495 $this->assertInstanceOf( Session::class, $session );
496 $this->assertSame( 'empty---------------------------', $session->getId() );
497
498 // Info, explicitly
499 $expectId = 'expected------------------------';
500 $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
501 'provider' => $provider1,
502 'id' => $expectId,
503 'persisted' => true,
504 'idIsSafe' => true,
505 ] );
506 $info2 = null;
507 $session = $pmanager->getEmptySessionInternal( null, $expectId );
508 $this->assertInstanceOf( Session::class, $session );
509 $this->assertSame( $expectId, $session->getId() );
510
511 // Wrong ID
512 $expectId = 'expected-----------------------2';
513 $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
514 'provider' => $provider1,
515 'id' => "un$expectId",
516 'persisted' => true,
517 'idIsSafe' => true,
518 ] );
519 $info2 = null;
520 try {
521 $pmanager->getEmptySessionInternal( null, $expectId );
522 $this->fail( 'Expected exception not thrown' );
523 } catch ( \UnexpectedValueException $ex ) {
524 $this->assertSame(
525 'MockProvider1 returned empty session info with a wrong id: ' .
526 "un$expectId != $expectId",
527 $ex->getMessage()
528 );
529 }
530
531 // Unsafe ID
532 $expectId = 'expected-----------------------2';
533 $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
534 'provider' => $provider1,
535 'id' => $expectId,
536 'persisted' => true,
537 ] );
538 $info2 = null;
539 try {
540 $pmanager->getEmptySessionInternal( null, $expectId );
541 $this->fail( 'Expected exception not thrown' );
542 } catch ( \UnexpectedValueException $ex ) {
543 $this->assertSame(
544 'MockProvider1 returned empty session info with id flagged unsafe',
545 $ex->getMessage()
546 );
547 }
548
549 // Wrong provider
550 $expectId = null;
551 $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
552 'provider' => $provider2,
553 'id' => 'empty---------------------------',
554 'persisted' => true,
555 'idIsSafe' => true,
556 ] );
557 $info2 = null;
558 try {
559 $manager->getEmptySession();
560 $this->fail( 'Expected exception not thrown' );
561 } catch ( \UnexpectedValueException $ex ) {
562 $this->assertSame(
563 'MockProvider1 returned an empty session info for a different provider: ' . $info1,
564 $ex->getMessage()
565 );
566 }
567
568 // Highest priority wins
569 $expectId = null;
570 $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY + 1, [
571 'provider' => $provider1,
572 'id' => 'empty1--------------------------',
573 'persisted' => true,
574 'idIsSafe' => true,
575 ] );
576 $info2 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
577 'provider' => $provider2,
578 'id' => 'empty2--------------------------',
579 'persisted' => true,
580 'idIsSafe' => true,
581 ] );
582 $session = $manager->getEmptySession();
583 $this->assertInstanceOf( Session::class, $session );
584 $this->assertSame( 'empty1--------------------------', $session->getId() );
585
586 $expectId = null;
587 $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY + 1, [
588 'provider' => $provider1,
589 'id' => 'empty1--------------------------',
590 'persisted' => true,
591 'idIsSafe' => true,
592 ] );
593 $info2 = new SessionInfo( SessionInfo::MIN_PRIORITY + 2, [
594 'provider' => $provider2,
595 'id' => 'empty2--------------------------',
596 'persisted' => true,
597 'idIsSafe' => true,
598 ] );
599 $session = $manager->getEmptySession();
600 $this->assertInstanceOf( Session::class, $session );
601 $this->assertSame( 'empty2--------------------------', $session->getId() );
602
603 // Tied priorities throw an exception
604 $expectId = null;
605 $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
606 'provider' => $provider1,
607 'id' => 'empty1--------------------------',
608 'persisted' => true,
609 'userInfo' => UserInfo::newAnonymous(),
610 'idIsSafe' => true,
611 ] );
612 $info2 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
613 'provider' => $provider2,
614 'id' => 'empty2--------------------------',
615 'persisted' => true,
616 'userInfo' => UserInfo::newAnonymous(),
617 'idIsSafe' => true,
618 ] );
619 try {
620 $manager->getEmptySession();
621 $this->fail( 'Expected exception not thrown' );
622 } catch ( \UnexpectedValueException $ex ) {
623 $this->assertStringStartsWith(
624 'Multiple empty sessions tied for top priority: ',
625 $ex->getMessage()
626 );
627 }
628
629 // Bad id
630 try {
631 $pmanager->getEmptySessionInternal( null, 'bad' );
632 $this->fail( 'Expected exception not thrown' );
633 } catch ( \InvalidArgumentException $ex ) {
634 $this->assertSame( 'Invalid session ID', $ex->getMessage() );
635 }
636
637 // Session already exists
638 $expectId = 'expected-----------------------3';
639 $this->store->setSessionMeta( $expectId, [
640 'provider' => 'MockProvider2',
641 'userId' => 0,
642 'userName' => null,
643 'userToken' => null,
644 ] );
645 try {
646 $pmanager->getEmptySessionInternal( null, $expectId );
647 $this->fail( 'Expected exception not thrown' );
648 } catch ( \InvalidArgumentException $ex ) {
649 $this->assertSame( 'Session ID already exists', $ex->getMessage() );
650 }
651 }
652
653 public function testInvalidateSessionsForUser() {
654 $user = User::newFromName( 'UTSysop' );
655 $manager = $this->getManager();
656
657 $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
658 ->setMethods( [ 'invalidateSessionsForUser', '__toString' ] );
659
660 $provider1 = $providerBuilder->getMock();
661 $provider1->expects( $this->once() )->method( 'invalidateSessionsForUser' )
662 ->with( $this->identicalTo( $user ) );
663 $provider1->expects( $this->any() )->method( '__toString' )
664 ->will( $this->returnValue( 'MockProvider1' ) );
665
666 $provider2 = $providerBuilder->getMock();
667 $provider2->expects( $this->once() )->method( 'invalidateSessionsForUser' )
668 ->with( $this->identicalTo( $user ) );
669 $provider2->expects( $this->any() )->method( '__toString' )
670 ->will( $this->returnValue( 'MockProvider2' ) );
671
672 $this->config->set( 'SessionProviders', [
673 $this->objectCacheDef( $provider1 ),
674 $this->objectCacheDef( $provider2 ),
675 ] );
676
677 $oldToken = $user->getToken( true );
678 $manager->invalidateSessionsForUser( $user );
679 $this->assertNotEquals( $oldToken, $user->getToken() );
680 }
681
682 public function testGetVaryHeaders() {
683 $manager = $this->getManager();
684
685 $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
686 ->setMethods( [ 'getVaryHeaders', '__toString' ] );
687
688 $provider1 = $providerBuilder->getMock();
689 $provider1->expects( $this->once() )->method( 'getVaryHeaders' )
690 ->will( $this->returnValue( [
691 'Foo' => null,
692 'Bar' => [ 'X', 'Bar1' ],
693 'Quux' => null,
694 ] ) );
695 $provider1->expects( $this->any() )->method( '__toString' )
696 ->will( $this->returnValue( 'MockProvider1' ) );
697
698 $provider2 = $providerBuilder->getMock();
699 $provider2->expects( $this->once() )->method( 'getVaryHeaders' )
700 ->will( $this->returnValue( [
701 'Baz' => null,
702 'Bar' => [ 'X', 'Bar2' ],
703 'Quux' => [ 'Quux' ],
704 ] ) );
705 $provider2->expects( $this->any() )->method( '__toString' )
706 ->will( $this->returnValue( 'MockProvider2' ) );
707
708 $this->config->set( 'SessionProviders', [
709 $this->objectCacheDef( $provider1 ),
710 $this->objectCacheDef( $provider2 ),
711 ] );
712
713 $expect = [
714 'Foo' => null,
715 'Bar' => null,
716 'Quux' => null,
717 'Baz' => null,
718 ];
719
720 $this->assertEquals( $expect, $manager->getVaryHeaders() );
721
722 // Again, to ensure it's cached
723 $this->assertEquals( $expect, $manager->getVaryHeaders() );
724 }
725
726 public function testGetVaryCookies() {
727 $manager = $this->getManager();
728
729 $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
730 ->setMethods( [ 'getVaryCookies', '__toString' ] );
731
732 $provider1 = $providerBuilder->getMock();
733 $provider1->expects( $this->once() )->method( 'getVaryCookies' )
734 ->will( $this->returnValue( [ 'Foo', 'Bar' ] ) );
735 $provider1->expects( $this->any() )->method( '__toString' )
736 ->will( $this->returnValue( 'MockProvider1' ) );
737
738 $provider2 = $providerBuilder->getMock();
739 $provider2->expects( $this->once() )->method( 'getVaryCookies' )
740 ->will( $this->returnValue( [ 'Foo', 'Baz' ] ) );
741 $provider2->expects( $this->any() )->method( '__toString' )
742 ->will( $this->returnValue( 'MockProvider2' ) );
743
744 $this->config->set( 'SessionProviders', [
745 $this->objectCacheDef( $provider1 ),
746 $this->objectCacheDef( $provider2 ),
747 ] );
748
749 $expect = [ 'Foo', 'Bar', 'Baz' ];
750
751 $this->assertEquals( $expect, $manager->getVaryCookies() );
752
753 // Again, to ensure it's cached
754 $this->assertEquals( $expect, $manager->getVaryCookies() );
755 }
756
757 public function testGetProviders() {
758 $realManager = $this->getManager();
759 $manager = TestingAccessWrapper::newFromObject( $realManager );
760
761 $this->config->set( 'SessionProviders', [
762 [ 'class' => \DummySessionProvider::class ],
763 ] );
764 $providers = $manager->getProviders();
765 $this->assertArrayHasKey( 'DummySessionProvider', $providers );
766 $provider = TestingAccessWrapper::newFromObject( $providers['DummySessionProvider'] );
767 $this->assertSame( $manager->logger, $provider->logger );
768 $this->assertSame( $manager->config, $provider->config );
769 $this->assertSame( $realManager, $provider->getManager() );
770
771 $this->config->set( 'SessionProviders', [
772 [ 'class' => \DummySessionProvider::class ],
773 [ 'class' => \DummySessionProvider::class ],
774 ] );
775 $manager->sessionProviders = null;
776 try {
777 $manager->getProviders();
778 $this->fail( 'Expected exception not thrown' );
779 } catch ( \UnexpectedValueException $ex ) {
780 $this->assertSame(
781 'Duplicate provider name "DummySessionProvider"',
782 $ex->getMessage()
783 );
784 }
785 }
786
787 public function testShutdown() {
788 $manager = TestingAccessWrapper::newFromObject( $this->getManager() );
789 $manager->setLogger( new \Psr\Log\NullLogger() );
790
791 $mock = $this->getMockBuilder( stdClass::class )
792 ->setMethods( [ 'shutdown' ] )->getMock();
793 $mock->expects( $this->once() )->method( 'shutdown' );
794
795 $manager->allSessionBackends = [ $mock ];
796 $manager->shutdown();
797 }
798
799 public function testGetSessionFromInfo() {
800 $manager = TestingAccessWrapper::newFromObject( $this->getManager() );
801 $request = new \FauxRequest();
802
803 $id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
804
805 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
806 'provider' => $manager->getProvider( 'DummySessionProvider' ),
807 'id' => $id,
808 'persisted' => true,
809 'userInfo' => UserInfo::newFromName( 'UTSysop', true ),
810 'idIsSafe' => true,
811 ] );
812 TestingAccessWrapper::newFromObject( $info )->idIsSafe = true;
813 $session1 = TestingAccessWrapper::newFromObject(
814 $manager->getSessionFromInfo( $info, $request )
815 );
816 $session2 = TestingAccessWrapper::newFromObject(
817 $manager->getSessionFromInfo( $info, $request )
818 );
819
820 $this->assertSame( $session1->backend, $session2->backend );
821 $this->assertNotEquals( $session1->index, $session2->index );
822 $this->assertSame( $session1->getSessionId(), $session2->getSessionId() );
823 $this->assertSame( $id, $session1->getId() );
824
825 TestingAccessWrapper::newFromObject( $info )->idIsSafe = false;
826 $session3 = $manager->getSessionFromInfo( $info, $request );
827 $this->assertNotSame( $id, $session3->getId() );
828 }
829
830 public function testBackendRegistration() {
831 $manager = $this->getManager();
832
833 $session = $manager->getSessionForRequest( new \FauxRequest );
834 $backend = TestingAccessWrapper::newFromObject( $session )->backend;
835 $sessionId = $session->getSessionId();
836 $id = (string)$sessionId;
837
838 $this->assertSame( $sessionId, $manager->getSessionById( $id, true )->getSessionId() );
839
840 $manager->changeBackendId( $backend );
841 $this->assertSame( $sessionId, $session->getSessionId() );
842 $this->assertNotEquals( $id, (string)$sessionId );
843 $id = (string)$sessionId;
844
845 $this->assertSame( $sessionId, $manager->getSessionById( $id, true )->getSessionId() );
846
847 // Destruction of the session here causes the backend to be deregistered
848 $session = null;
849
850 try {
851 $manager->changeBackendId( $backend );
852 $this->fail( 'Expected exception not thrown' );
853 } catch ( \InvalidArgumentException $ex ) {
854 $this->assertSame(
855 'Backend was not registered with this SessionManager', $ex->getMessage()
856 );
857 }
858
859 try {
860 $manager->deregisterSessionBackend( $backend );
861 $this->fail( 'Expected exception not thrown' );
862 } catch ( \InvalidArgumentException $ex ) {
863 $this->assertSame(
864 'Backend was not registered with this SessionManager', $ex->getMessage()
865 );
866 }
867
868 $session = $manager->getSessionById( $id, true );
869 $this->assertSame( $sessionId, $session->getSessionId() );
870 }
871
872 public function testGenerateSessionId() {
873 $manager = $this->getManager();
874
875 $id = $manager->generateSessionId();
876 $this->assertTrue( SessionManager::validateSessionId( $id ), "Generated ID: $id" );
877 }
878
879 public function testPreventSessionsForUser() {
880 $manager = $this->getManager();
881
882 $providerBuilder = $this->getMockBuilder( \DummySessionProvider::class )
883 ->setMethods( [ 'preventSessionsForUser', '__toString' ] );
884
885 $provider1 = $providerBuilder->getMock();
886 $provider1->expects( $this->once() )->method( 'preventSessionsForUser' )
887 ->with( $this->equalTo( 'UTSysop' ) );
888 $provider1->expects( $this->any() )->method( '__toString' )
889 ->will( $this->returnValue( 'MockProvider1' ) );
890
891 $this->config->set( 'SessionProviders', [
892 $this->objectCacheDef( $provider1 ),
893 ] );
894
895 $this->assertFalse( $manager->isUserSessionPrevented( 'UTSysop' ) );
896 $manager->preventSessionsForUser( 'UTSysop' );
897 $this->assertTrue( $manager->isUserSessionPrevented( 'UTSysop' ) );
898 }
899
900 public function testLoadSessionInfoFromStore() {
901 $manager = $this->getManager();
902 $logger = new \TestLogger( true );
903 $manager->setLogger( $logger );
904 $request = new \FauxRequest();
905
906 // TestingAccessWrapper can't handle methods with reference arguments, sigh.
907 $rClass = new \ReflectionClass( $manager );
908 $rMethod = $rClass->getMethod( 'loadSessionInfoFromStore' );
909 $rMethod->setAccessible( true );
910 $loadSessionInfoFromStore = function ( &$info ) use ( $rMethod, $manager, $request ) {
911 return $rMethod->invokeArgs( $manager, [ &$info, $request ] );
912 };
913
914 $userInfo = UserInfo::newFromName( 'UTSysop', true );
915 $unverifiedUserInfo = UserInfo::newFromName( 'UTSysop', false );
916
917 $id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
918 $metadata = [
919 'userId' => $userInfo->getId(),
920 'userName' => $userInfo->getName(),
921 'userToken' => $userInfo->getToken( true ),
922 'provider' => 'Mock',
923 ];
924
925 $builder = $this->getMockBuilder( SessionProvider::class )
926 ->setMethods( [ '__toString', 'mergeMetadata', 'refreshSessionInfo' ] );
927
928 $provider = $builder->getMockForAbstractClass();
929 $provider->setManager( $manager );
930 $provider->expects( $this->any() )->method( 'persistsSessionId' )
931 ->will( $this->returnValue( true ) );
932 $provider->expects( $this->any() )->method( 'canChangeUser' )
933 ->will( $this->returnValue( true ) );
934 $provider->expects( $this->any() )->method( 'refreshSessionInfo' )
935 ->will( $this->returnValue( true ) );
936 $provider->expects( $this->any() )->method( '__toString' )
937 ->will( $this->returnValue( 'Mock' ) );
938 $provider->expects( $this->any() )->method( 'mergeMetadata' )
939 ->will( $this->returnCallback( function ( $a, $b ) {
940 if ( $b === [ 'Throw' ] ) {
941 throw new MetadataMergeException( 'no merge!' );
942 }
943 return [ 'Merged' ];
944 } ) );
945
946 $provider2 = $builder->getMockForAbstractClass();
947 $provider2->setManager( $manager );
948 $provider2->expects( $this->any() )->method( 'persistsSessionId' )
949 ->will( $this->returnValue( false ) );
950 $provider2->expects( $this->any() )->method( 'canChangeUser' )
951 ->will( $this->returnValue( false ) );
952 $provider2->expects( $this->any() )->method( '__toString' )
953 ->will( $this->returnValue( 'Mock2' ) );
954 $provider2->expects( $this->any() )->method( 'refreshSessionInfo' )
955 ->will( $this->returnCallback( function ( $info, $request, &$metadata ) {
956 $metadata['changed'] = true;
957 return true;
958 } ) );
959
960 $provider3 = $builder->getMockForAbstractClass();
961 $provider3->setManager( $manager );
962 $provider3->expects( $this->any() )->method( 'persistsSessionId' )
963 ->will( $this->returnValue( true ) );
964 $provider3->expects( $this->any() )->method( 'canChangeUser' )
965 ->will( $this->returnValue( true ) );
966 $provider3->expects( $this->once() )->method( 'refreshSessionInfo' )
967 ->will( $this->returnValue( false ) );
968 $provider3->expects( $this->any() )->method( '__toString' )
969 ->will( $this->returnValue( 'Mock3' ) );
970
971 TestingAccessWrapper::newFromObject( $manager )->sessionProviders = [
972 (string)$provider => $provider,
973 (string)$provider2 => $provider2,
974 (string)$provider3 => $provider3,
975 ];
976
977 // No metadata, basic usage
978 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
979 'provider' => $provider,
980 'id' => $id,
981 'userInfo' => $userInfo
982 ] );
983 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
984 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
985 $this->assertFalse( $info->isIdSafe() );
986 $this->assertSame( [], $logger->getBuffer() );
987
988 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
989 'provider' => $provider,
990 'userInfo' => $userInfo
991 ] );
992 $this->assertTrue( $info->isIdSafe(), 'sanity check' );
993 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
994 $this->assertTrue( $info->isIdSafe() );
995 $this->assertSame( [], $logger->getBuffer() );
996
997 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
998 'provider' => $provider2,
999 'id' => $id,
1000 'userInfo' => $userInfo
1001 ] );
1002 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1003 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1004 $this->assertTrue( $info->isIdSafe() );
1005 $this->assertSame( [], $logger->getBuffer() );
1006
1007 // Unverified user, no metadata
1008 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1009 'provider' => $provider,
1010 'id' => $id,
1011 'userInfo' => $unverifiedUserInfo
1012 ] );
1013 $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() );
1014 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1015 $this->assertSame( [
1016 [
1017 LogLevel::INFO,
1018 'Session "{session}": Unverified user provided and no metadata to auth it',
1019 ]
1020 ], $logger->getBuffer() );
1021 $logger->clearBuffer();
1022
1023 // No metadata, missing data
1024 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1025 'id' => $id,
1026 'userInfo' => $userInfo
1027 ] );
1028 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1029 $this->assertSame( [
1030 [ LogLevel::WARNING, 'Session "{session}": Null provider and no metadata' ],
1031 ], $logger->getBuffer() );
1032 $logger->clearBuffer();
1033
1034 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1035 'provider' => $provider,
1036 'id' => $id,
1037 ] );
1038 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1039 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1040 $this->assertInstanceOf( UserInfo::class, $info->getUserInfo() );
1041 $this->assertTrue( $info->getUserInfo()->isVerified() );
1042 $this->assertTrue( $info->getUserInfo()->isAnon() );
1043 $this->assertFalse( $info->isIdSafe() );
1044 $this->assertSame( [], $logger->getBuffer() );
1045
1046 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1047 'provider' => $provider2,
1048 'id' => $id,
1049 ] );
1050 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1051 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1052 $this->assertSame( [
1053 [ LogLevel::INFO, 'Session "{session}": No user provided and provider cannot set user' ]
1054 ], $logger->getBuffer() );
1055 $logger->clearBuffer();
1056
1057 // Incomplete/bad metadata
1058 $this->store->setRawSession( $id, true );
1059 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1060 $this->assertSame( [
1061 [ LogLevel::WARNING, 'Session "{session}": Bad data' ],
1062 ], $logger->getBuffer() );
1063 $logger->clearBuffer();
1064
1065 $this->store->setRawSession( $id, [ 'data' => [] ] );
1066 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1067 $this->assertSame( [
1068 [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1069 ], $logger->getBuffer() );
1070 $logger->clearBuffer();
1071
1072 $this->store->deleteSession( $id );
1073 $this->store->setRawSession( $id, [ 'metadata' => $metadata ] );
1074 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1075 $this->assertSame( [
1076 [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1077 ], $logger->getBuffer() );
1078 $logger->clearBuffer();
1079
1080 $this->store->setRawSession( $id, [ 'metadata' => $metadata, 'data' => true ] );
1081 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1082 $this->assertSame( [
1083 [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1084 ], $logger->getBuffer() );
1085 $logger->clearBuffer();
1086
1087 $this->store->setRawSession( $id, [ 'metadata' => true, 'data' => [] ] );
1088 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1089 $this->assertSame( [
1090 [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1091 ], $logger->getBuffer() );
1092 $logger->clearBuffer();
1093
1094 foreach ( $metadata as $key => $dummy ) {
1095 $tmp = $metadata;
1096 unset( $tmp[$key] );
1097 $this->store->setRawSession( $id, [ 'metadata' => $tmp, 'data' => [] ] );
1098 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1099 $this->assertSame( [
1100 [ LogLevel::WARNING, 'Session "{session}": Bad metadata' ],
1101 ], $logger->getBuffer() );
1102 $logger->clearBuffer();
1103 }
1104
1105 // Basic usage with metadata
1106 $this->store->setRawSession( $id, [ 'metadata' => $metadata, 'data' => [] ] );
1107 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1108 'provider' => $provider,
1109 'id' => $id,
1110 'userInfo' => $userInfo
1111 ] );
1112 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1113 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1114 $this->assertTrue( $info->isIdSafe() );
1115 $this->assertSame( [], $logger->getBuffer() );
1116
1117 // Mismatched provider
1118 $this->store->setSessionMeta( $id, [ 'provider' => 'Bad' ] + $metadata );
1119 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1120 'provider' => $provider,
1121 'id' => $id,
1122 'userInfo' => $userInfo
1123 ] );
1124 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1125 $this->assertSame( [
1126 [ LogLevel::WARNING, 'Session "{session}": Wrong provider Bad !== Mock' ],
1127 ], $logger->getBuffer() );
1128 $logger->clearBuffer();
1129
1130 // Unknown provider
1131 $this->store->setSessionMeta( $id, [ 'provider' => 'Bad' ] + $metadata );
1132 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1133 'id' => $id,
1134 'userInfo' => $userInfo
1135 ] );
1136 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1137 $this->assertSame( [
1138 [ LogLevel::WARNING, 'Session "{session}": Unknown provider Bad' ],
1139 ], $logger->getBuffer() );
1140 $logger->clearBuffer();
1141
1142 // Fill in provider
1143 $this->store->setSessionMeta( $id, $metadata );
1144 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1145 'id' => $id,
1146 'userInfo' => $userInfo
1147 ] );
1148 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1149 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1150 $this->assertTrue( $info->isIdSafe() );
1151 $this->assertSame( [], $logger->getBuffer() );
1152
1153 // Bad user metadata
1154 $this->store->setSessionMeta( $id, [ 'userId' => -1, 'userToken' => null ] + $metadata );
1155 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1156 'provider' => $provider,
1157 'id' => $id,
1158 ] );
1159 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1160 $this->assertSame( [
1161 [ LogLevel::ERROR, 'Session "{session}": {exception}' ],
1162 ], $logger->getBuffer() );
1163 $logger->clearBuffer();
1164
1165 $this->store->setSessionMeta(
1166 $id, [ 'userId' => 0, 'userName' => '<X>', 'userToken' => null ] + $metadata
1167 );
1168 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1169 'provider' => $provider,
1170 'id' => $id,
1171 ] );
1172 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1173 $this->assertSame( [
1174 [ LogLevel::ERROR, 'Session "{session}": {exception}', ],
1175 ], $logger->getBuffer() );
1176 $logger->clearBuffer();
1177
1178 // Mismatched user by ID
1179 $this->store->setSessionMeta(
1180 $id, [ 'userId' => $userInfo->getId() + 1, 'userToken' => null ] + $metadata
1181 );
1182 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1183 'provider' => $provider,
1184 'id' => $id,
1185 'userInfo' => $userInfo
1186 ] );
1187 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1188 $this->assertSame( [
1189 [ LogLevel::WARNING, 'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}' ],
1190 ], $logger->getBuffer() );
1191 $logger->clearBuffer();
1192
1193 // Mismatched user by name
1194 $this->store->setSessionMeta(
1195 $id, [ 'userId' => 0, 'userName' => 'X', 'userToken' => null ] + $metadata
1196 );
1197 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1198 'provider' => $provider,
1199 'id' => $id,
1200 'userInfo' => $userInfo
1201 ] );
1202 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1203 $this->assertSame( [
1204 [ LogLevel::WARNING, 'Session "{session}": User name mismatch, {uname_a} !== {uname_b}' ],
1205 ], $logger->getBuffer() );
1206 $logger->clearBuffer();
1207
1208 // ID matches, name doesn't
1209 $this->store->setSessionMeta(
1210 $id, [ 'userId' => $userInfo->getId(), 'userName' => 'X', 'userToken' => null ] + $metadata
1211 );
1212 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1213 'provider' => $provider,
1214 'id' => $id,
1215 'userInfo' => $userInfo
1216 ] );
1217 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1218 $this->assertSame( [
1219 [
1220 LogLevel::WARNING,
1221 'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}'
1222 ],
1223 ], $logger->getBuffer() );
1224 $logger->clearBuffer();
1225
1226 // Mismatched anon user
1227 $this->store->setSessionMeta(
1228 $id, [ 'userId' => 0, 'userName' => null, 'userToken' => null ] + $metadata
1229 );
1230 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1231 'provider' => $provider,
1232 'id' => $id,
1233 'userInfo' => $userInfo
1234 ] );
1235 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1236 $this->assertSame( [
1237 [
1238 LogLevel::WARNING,
1239 'Session "{session}": Metadata has an anonymous user, ' .
1240 'but a non-anon user was provided',
1241 ],
1242 ], $logger->getBuffer() );
1243 $logger->clearBuffer();
1244
1245 // Lookup user by ID
1246 $this->store->setSessionMeta( $id, [ 'userToken' => null ] + $metadata );
1247 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1248 'provider' => $provider,
1249 'id' => $id,
1250 ] );
1251 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1252 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1253 $this->assertSame( $userInfo->getId(), $info->getUserInfo()->getId() );
1254 $this->assertTrue( $info->isIdSafe() );
1255 $this->assertSame( [], $logger->getBuffer() );
1256
1257 // Lookup user by name
1258 $this->store->setSessionMeta(
1259 $id, [ 'userId' => 0, 'userName' => 'UTSysop', 'userToken' => null ] + $metadata
1260 );
1261 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1262 'provider' => $provider,
1263 'id' => $id,
1264 ] );
1265 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1266 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1267 $this->assertSame( $userInfo->getId(), $info->getUserInfo()->getId() );
1268 $this->assertTrue( $info->isIdSafe() );
1269 $this->assertSame( [], $logger->getBuffer() );
1270
1271 // Lookup anonymous user
1272 $this->store->setSessionMeta(
1273 $id, [ 'userId' => 0, 'userName' => null, 'userToken' => null ] + $metadata
1274 );
1275 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1276 'provider' => $provider,
1277 'id' => $id,
1278 ] );
1279 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1280 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1281 $this->assertTrue( $info->getUserInfo()->isAnon() );
1282 $this->assertTrue( $info->isIdSafe() );
1283 $this->assertSame( [], $logger->getBuffer() );
1284
1285 // Unverified user with metadata
1286 $this->store->setSessionMeta( $id, $metadata );
1287 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1288 'provider' => $provider,
1289 'id' => $id,
1290 'userInfo' => $unverifiedUserInfo
1291 ] );
1292 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1293 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1294 $this->assertTrue( $info->getUserInfo()->isVerified() );
1295 $this->assertSame( $unverifiedUserInfo->getId(), $info->getUserInfo()->getId() );
1296 $this->assertSame( $unverifiedUserInfo->getName(), $info->getUserInfo()->getName() );
1297 $this->assertTrue( $info->isIdSafe() );
1298 $this->assertSame( [], $logger->getBuffer() );
1299
1300 // Unverified user with metadata
1301 $this->store->setSessionMeta( $id, $metadata );
1302 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1303 'provider' => $provider,
1304 'id' => $id,
1305 'userInfo' => $unverifiedUserInfo
1306 ] );
1307 $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1308 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1309 $this->assertTrue( $info->getUserInfo()->isVerified() );
1310 $this->assertSame( $unverifiedUserInfo->getId(), $info->getUserInfo()->getId() );
1311 $this->assertSame( $unverifiedUserInfo->getName(), $info->getUserInfo()->getName() );
1312 $this->assertTrue( $info->isIdSafe() );
1313 $this->assertSame( [], $logger->getBuffer() );
1314
1315 // Wrong token
1316 $this->store->setSessionMeta( $id, [ 'userToken' => 'Bad' ] + $metadata );
1317 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1318 'provider' => $provider,
1319 'id' => $id,
1320 'userInfo' => $userInfo
1321 ] );
1322 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1323 $this->assertSame( [
1324 [ LogLevel::WARNING, 'Session "{session}": User token mismatch' ],
1325 ], $logger->getBuffer() );
1326 $logger->clearBuffer();
1327
1328 // Provider metadata
1329 $this->store->setSessionMeta( $id, [ 'provider' => 'Mock2' ] + $metadata );
1330 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1331 'provider' => $provider2,
1332 'id' => $id,
1333 'userInfo' => $userInfo,
1334 'metadata' => [ 'Info' ],
1335 ] );
1336 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1337 $this->assertSame( [ 'Info', 'changed' => true ], $info->getProviderMetadata() );
1338 $this->assertSame( [], $logger->getBuffer() );
1339
1340 $this->store->setSessionMeta( $id, [ 'providerMetadata' => [ 'Saved' ] ] + $metadata );
1341 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1342 'provider' => $provider,
1343 'id' => $id,
1344 'userInfo' => $userInfo,
1345 ] );
1346 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1347 $this->assertSame( [ 'Saved' ], $info->getProviderMetadata() );
1348 $this->assertSame( [], $logger->getBuffer() );
1349
1350 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1351 'provider' => $provider,
1352 'id' => $id,
1353 'userInfo' => $userInfo,
1354 'metadata' => [ 'Info' ],
1355 ] );
1356 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1357 $this->assertSame( [ 'Merged' ], $info->getProviderMetadata() );
1358 $this->assertSame( [], $logger->getBuffer() );
1359
1360 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1361 'provider' => $provider,
1362 'id' => $id,
1363 'userInfo' => $userInfo,
1364 'metadata' => [ 'Throw' ],
1365 ] );
1366 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1367 $this->assertSame( [
1368 [
1369 LogLevel::WARNING,
1370 'Session "{session}": Metadata merge failed: {exception}',
1371 ],
1372 ], $logger->getBuffer() );
1373 $logger->clearBuffer();
1374
1375 // Remember from session
1376 $this->store->setSessionMeta( $id, $metadata );
1377 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1378 'provider' => $provider,
1379 'id' => $id,
1380 ] );
1381 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1382 $this->assertFalse( $info->wasRemembered() );
1383 $this->assertSame( [], $logger->getBuffer() );
1384
1385 $this->store->setSessionMeta( $id, [ 'remember' => true ] + $metadata );
1386 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1387 'provider' => $provider,
1388 'id' => $id,
1389 ] );
1390 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1391 $this->assertTrue( $info->wasRemembered() );
1392 $this->assertSame( [], $logger->getBuffer() );
1393
1394 $this->store->setSessionMeta( $id, [ 'remember' => false ] + $metadata );
1395 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1396 'provider' => $provider,
1397 'id' => $id,
1398 'userInfo' => $userInfo
1399 ] );
1400 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1401 $this->assertTrue( $info->wasRemembered() );
1402 $this->assertSame( [], $logger->getBuffer() );
1403
1404 // forceHTTPS from session
1405 $this->store->setSessionMeta( $id, $metadata );
1406 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1407 'provider' => $provider,
1408 'id' => $id,
1409 'userInfo' => $userInfo
1410 ] );
1411 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1412 $this->assertFalse( $info->forceHTTPS() );
1413 $this->assertSame( [], $logger->getBuffer() );
1414
1415 $this->store->setSessionMeta( $id, [ 'forceHTTPS' => true ] + $metadata );
1416 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1417 'provider' => $provider,
1418 'id' => $id,
1419 'userInfo' => $userInfo
1420 ] );
1421 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1422 $this->assertTrue( $info->forceHTTPS() );
1423 $this->assertSame( [], $logger->getBuffer() );
1424
1425 $this->store->setSessionMeta( $id, [ 'forceHTTPS' => false ] + $metadata );
1426 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1427 'provider' => $provider,
1428 'id' => $id,
1429 'userInfo' => $userInfo,
1430 'forceHTTPS' => true
1431 ] );
1432 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1433 $this->assertTrue( $info->forceHTTPS() );
1434 $this->assertSame( [], $logger->getBuffer() );
1435
1436 // "Persist" flag from session
1437 $this->store->setSessionMeta( $id, $metadata );
1438 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1439 'provider' => $provider,
1440 'id' => $id,
1441 'userInfo' => $userInfo
1442 ] );
1443 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1444 $this->assertFalse( $info->wasPersisted() );
1445 $this->assertSame( [], $logger->getBuffer() );
1446
1447 $this->store->setSessionMeta( $id, [ 'persisted' => true ] + $metadata );
1448 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1449 'provider' => $provider,
1450 'id' => $id,
1451 'userInfo' => $userInfo
1452 ] );
1453 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1454 $this->assertTrue( $info->wasPersisted() );
1455 $this->assertSame( [], $logger->getBuffer() );
1456
1457 $this->store->setSessionMeta( $id, [ 'persisted' => false ] + $metadata );
1458 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1459 'provider' => $provider,
1460 'id' => $id,
1461 'userInfo' => $userInfo,
1462 'persisted' => true
1463 ] );
1464 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1465 $this->assertTrue( $info->wasPersisted() );
1466 $this->assertSame( [], $logger->getBuffer() );
1467
1468 // Provider refreshSessionInfo() returning false
1469 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1470 'provider' => $provider3,
1471 ] );
1472 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1473 $this->assertSame( [], $logger->getBuffer() );
1474
1475 // Hook
1476 $called = false;
1477 $data = [ 'foo' => 1 ];
1478 $this->store->setSession( $id, [ 'metadata' => $metadata, 'data' => $data ] );
1479 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1480 'provider' => $provider,
1481 'id' => $id,
1482 'userInfo' => $userInfo
1483 ] );
1484 $this->mergeMwGlobalArrayValue( 'wgHooks', [
1485 'SessionCheckInfo' => [ function ( &$reason, $i, $r, $m, $d ) use (
1486 $info, $metadata, $data, $request, &$called
1487 ) {
1488 $this->assertSame( $info->getId(), $i->getId() );
1489 $this->assertSame( $info->getProvider(), $i->getProvider() );
1490 $this->assertSame( $info->getUserInfo(), $i->getUserInfo() );
1491 $this->assertSame( $request, $r );
1492 $this->assertEquals( $metadata, $m );
1493 $this->assertEquals( $data, $d );
1494 $called = true;
1495 return false;
1496 } ]
1497 ] );
1498 $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1499 $this->assertTrue( $called );
1500 $this->assertSame( [
1501 [ LogLevel::WARNING, 'Session "{session}": Hook aborted' ],
1502 ], $logger->getBuffer() );
1503 $logger->clearBuffer();
1504 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionCheckInfo' => [] ] );
1505
1506 // forceUse deletes bad backend data
1507 $this->store->setSessionMeta( $id, [ 'userToken' => 'Bad' ] + $metadata );
1508 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1509 'provider' => $provider,
1510 'id' => $id,
1511 'userInfo' => $userInfo,
1512 'forceUse' => true,
1513 ] );
1514 $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1515 $this->assertFalse( $this->store->getSession( $id ) );
1516 $this->assertSame( [
1517 [ LogLevel::WARNING, 'Session "{session}": User token mismatch' ],
1518 ], $logger->getBuffer() );
1519 $logger->clearBuffer();
1520 }
1521 }