MediaWiki  1.30.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 
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() {
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
200  $request->info1 = new SessionInfo( SessionInfo::MIN_PRIORITY + 1, [
201  'provider' => $provider1,
202  'id' => ( $id1 = $manager->generateSessionId() ),
203  'persisted' => true,
204  'idIsSafe' => true,
205  ] );
206  $request->info2 = new SessionInfo( SessionInfo::MIN_PRIORITY + 2, [
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 
218  $request->info1 = new SessionInfo( SessionInfo::MIN_PRIORITY + 2, [
219  'provider' => $provider1,
220  'id' => ( $id1 = $manager->generateSessionId() ),
221  'persisted' => true,
222  'idIsSafe' => true,
223  ] );
224  $request->info2 = new SessionInfo( SessionInfo::MIN_PRIORITY + 1, [
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;
479  $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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------------------------';
492  $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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';
505  $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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';
525  $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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;
543  $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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  ] );
568  $info2 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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;
597  $info1 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
598  'provider' => $provider1,
599  'id' => 'empty1--------------------------',
600  'persisted' => true,
601  'userInfo' => UserInfo::newAnonymous(),
602  'idIsSafe' => true,
603  ] );
604  $info2 = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 
645  public function testInvalidateSessionsForUser() {
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 
797  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 
892  public function testLoadSessionInfoFromStore() {
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
970  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 
980  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 
989  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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
1000  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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
1016  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 
1026  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 
1038  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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' => [] ] );
1099  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1111  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1124  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1136  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1147  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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  );
1160  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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  );
1174  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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  );
1189  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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  );
1204  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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  );
1222  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1239  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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  );
1253  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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  );
1267  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1279  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1294  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1309  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1322  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1333  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 
1342  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 
1352  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1369  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1378  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1387  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1398  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1408  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1418  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1430  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1440  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1450  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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
1461  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 ] );
1471  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 );
1500  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
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 }
MediaWiki\Session\SessionManagerTest\testConstructor
testConstructor()
Definition: SessionManagerTest.php:103
MediaWiki\Session\SessionManagerTest\testShutdown
testShutdown()
Definition: SessionManagerTest.php:779
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:807
MediaWiki\Session\SessionManagerTest\testInvalidateSessionsForUser
testInvalidateSessionsForUser()
Definition: SessionManagerTest.php:645
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
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:41
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:550
MediaWiki\Session\SessionManagerTest\getManager
getManager()
Definition: SessionManagerTest.php:19
User
User
Definition: All_system_messages.txt:425
MediaWiki\Session\SessionManagerTest\testGetGlobalSession
testGetGlobalSession()
Definition: SessionManagerTest.php:55
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:871
MediaWikiTestCase\$called
$called
$called tracks whether the setUp and tearDown method has been called.
Definition: MediaWikiTestCase.php:38
MediaWiki\Session\SessionManagerTest\testGetSessionById
testGetSessionById()
Definition: SessionManagerTest.php:349
MediaWiki\Session\SessionManager\validateSessionId
static validateSessionId( $id)
Validate a session ID.
Definition: SessionManager.php:370
MediaWiki\Session\SessionManagerTest\testGetProviders
testGetProviders()
Definition: SessionManagerTest.php:749
MediaWikiTestCase
Definition: MediaWikiTestCase.php:15
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:91
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:2581
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:106
MediaWiki\Session\SessionManagerTest\testSingleton
testSingleton()
Definition: SessionManagerTest.php:47
MediaWiki\Session\SessionManagerTest\testBackendRegistration
testBackendRegistration()
Definition: SessionManagerTest.php:822
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:49
$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:781
RequestContext\getMain
static getMain()
Static methods.
Definition: RequestContext.php:470
MediaWiki\Session\SessionManagerTest\testGetEmptySession
testGetEmptySession()
Definition: SessionManagerTest.php:420
MediaWiki\Session\SessionInfo
Value object returned by SessionProvider.
Definition: SessionInfo.php:34
MediaWiki\Session\SessionManagerTest\testGetVaryCookies
testGetVaryCookies()
Definition: SessionManagerTest.php:718
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:765
MediaWiki\Session\SessionManagerTest\$config
$config
Definition: SessionManagerTest.php:17
MediaWiki\Session\SessionManagerTest\testLoadSessionInfoFromStore
testLoadSessionInfoFromStore()
Definition: SessionManagerTest.php:892
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:37
MediaWiki\Session\SessionManagerTest\$store
$store
Definition: SessionManagerTest.php:17
MediaWiki\Session\SessionManagerTest\testGetSessionFromInfo
testGetSessionFromInfo()
Definition: SessionManagerTest.php:791
MediaWiki\Session\SessionInfo\MIN_PRIORITY
const MIN_PRIORITY
Minimum allowed priority.
Definition: SessionInfo.php:36
MediaWiki\Session\SessionManagerTest\testGenerateSessionId
testGenerateSessionId()
Definition: SessionManagerTest.php:864
MediaWiki\Session\SessionManagerTest\$logger
$logger
Definition: SessionManagerTest.php:17
MediaWiki\Session\SessionManagerTest\testGetSessionForRequest
testGetSessionForRequest()
Definition: SessionManagerTest.php:131
MediaWiki\Session\SessionManagerTest\testGetVaryHeaders
testGetVaryHeaders()
Definition: SessionManagerTest.php:674