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