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