MediaWiki  1.28.0
SessionManagerTest.php
Go to the documentation of this file.
1 <?php
2 
3 namespace MediaWiki\Session;
4 
8 use User;
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 
110  $this->assertSame( \RequestContext::getMain()->getConfig(), $manager->config );
111 
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->getMock( 'stdClass', [ 'shutdown' ] );
784  $mock->expects( $this->once() )->method( 'shutdown' );
785 
786  $manager->allSessionBackends = [ $mock ];
787  $manager->shutdown();
788  }
789 
790  public function testGetSessionFromInfo() {
791  $manager = \TestingAccessWrapper::newFromObject( $this->getManager() );
792  $request = new \FauxRequest();
793 
794  $id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
795 
796  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
797  'provider' => $manager->getProvider( 'DummySessionProvider' ),
798  'id' => $id,
799  'persisted' => true,
800  'userInfo' => UserInfo::newFromName( 'UTSysop', true ),
801  'idIsSafe' => true,
802  ] );
803  \TestingAccessWrapper::newFromObject( $info )->idIsSafe = true;
805  $manager->getSessionFromInfo( $info, $request )
806  );
808  $manager->getSessionFromInfo( $info, $request )
809  );
810 
811  $this->assertSame( $session1->backend, $session2->backend );
812  $this->assertNotEquals( $session1->index, $session2->index );
813  $this->assertSame( $session1->getSessionId(), $session2->getSessionId() );
814  $this->assertSame( $id, $session1->getId() );
815 
816  \TestingAccessWrapper::newFromObject( $info )->idIsSafe = false;
817  $session3 = $manager->getSessionFromInfo( $info, $request );
818  $this->assertNotSame( $id, $session3->getId() );
819  }
820 
821  public function testBackendRegistration() {
822  $manager = $this->getManager();
823 
824  $session = $manager->getSessionForRequest( new \FauxRequest );
825  $backend = \TestingAccessWrapper::newFromObject( $session )->backend;
826  $sessionId = $session->getSessionId();
827  $id = (string)$sessionId;
828 
829  $this->assertSame( $sessionId, $manager->getSessionById( $id, true )->getSessionId() );
830 
831  $manager->changeBackendId( $backend );
832  $this->assertSame( $sessionId, $session->getSessionId() );
833  $this->assertNotEquals( $id, (string)$sessionId );
834  $id = (string)$sessionId;
835 
836  $this->assertSame( $sessionId, $manager->getSessionById( $id, true )->getSessionId() );
837 
838  // Destruction of the session here causes the backend to be deregistered
839  $session = null;
840 
841  try {
842  $manager->changeBackendId( $backend );
843  $this->fail( 'Expected exception not thrown' );
844  } catch ( \InvalidArgumentException $ex ) {
845  $this->assertSame(
846  'Backend was not registered with this SessionManager', $ex->getMessage()
847  );
848  }
849 
850  try {
851  $manager->deregisterSessionBackend( $backend );
852  $this->fail( 'Expected exception not thrown' );
853  } catch ( \InvalidArgumentException $ex ) {
854  $this->assertSame(
855  'Backend was not registered with this SessionManager', $ex->getMessage()
856  );
857  }
858 
859  $session = $manager->getSessionById( $id, true );
860  $this->assertSame( $sessionId, $session->getSessionId() );
861  }
862 
863  public function testGenerateSessionId() {
864  $manager = $this->getManager();
865 
866  $id = $manager->generateSessionId();
867  $this->assertTrue( SessionManager::validateSessionId( $id ), "Generated ID: $id" );
868  }
869 
870  public function testPreventSessionsForUser() {
871  $manager = $this->getManager();
872 
873  $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
874  ->setMethods( [ 'preventSessionsForUser', '__toString' ] );
875 
876  $provider1 = $providerBuilder->getMock();
877  $provider1->expects( $this->once() )->method( 'preventSessionsForUser' )
878  ->with( $this->equalTo( 'UTSysop' ) );
879  $provider1->expects( $this->any() )->method( '__toString' )
880  ->will( $this->returnValue( 'MockProvider1' ) );
881 
882  $this->config->set( 'SessionProviders', [
883  $this->objectCacheDef( $provider1 ),
884  ] );
885 
886  $this->assertFalse( $manager->isUserSessionPrevented( 'UTSysop' ) );
887  $manager->preventSessionsForUser( 'UTSysop' );
888  $this->assertTrue( $manager->isUserSessionPrevented( 'UTSysop' ) );
889  }
890 
891  public function testLoadSessionInfoFromStore() {
892  $manager = $this->getManager();
893  $logger = new \TestLogger( true );
894  $manager->setLogger( $logger );
895  $request = new \FauxRequest();
896 
897  // TestingAccessWrapper can't handle methods with reference arguments, sigh.
898  $rClass = new \ReflectionClass( $manager );
899  $rMethod = $rClass->getMethod( 'loadSessionInfoFromStore' );
900  $rMethod->setAccessible( true );
901  $loadSessionInfoFromStore = function ( &$info ) use ( $rMethod, $manager, $request ) {
902  return $rMethod->invokeArgs( $manager, [ &$info, $request ] );
903  };
904 
905  $userInfo = UserInfo::newFromName( 'UTSysop', true );
906  $unverifiedUserInfo = UserInfo::newFromName( 'UTSysop', false );
907 
908  $id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
909  $metadata = [
910  'userId' => $userInfo->getId(),
911  'userName' => $userInfo->getName(),
912  'userToken' => $userInfo->getToken( true ),
913  'provider' => 'Mock',
914  ];
915 
916  $builder = $this->getMockBuilder( SessionProvider::class )
917  ->setMethods( [ '__toString', 'mergeMetadata', 'refreshSessionInfo' ] );
918 
919  $provider = $builder->getMockForAbstractClass();
920  $provider->setManager( $manager );
921  $provider->expects( $this->any() )->method( 'persistsSessionId' )
922  ->will( $this->returnValue( true ) );
923  $provider->expects( $this->any() )->method( 'canChangeUser' )
924  ->will( $this->returnValue( true ) );
925  $provider->expects( $this->any() )->method( 'refreshSessionInfo' )
926  ->will( $this->returnValue( true ) );
927  $provider->expects( $this->any() )->method( '__toString' )
928  ->will( $this->returnValue( 'Mock' ) );
929  $provider->expects( $this->any() )->method( 'mergeMetadata' )
930  ->will( $this->returnCallback( function ( $a, $b ) {
931  if ( $b === [ 'Throw' ] ) {
932  throw new MetadataMergeException( 'no merge!' );
933  }
934  return [ 'Merged' ];
935  } ) );
936 
937  $provider2 = $builder->getMockForAbstractClass();
938  $provider2->setManager( $manager );
939  $provider2->expects( $this->any() )->method( 'persistsSessionId' )
940  ->will( $this->returnValue( false ) );
941  $provider2->expects( $this->any() )->method( 'canChangeUser' )
942  ->will( $this->returnValue( false ) );
943  $provider2->expects( $this->any() )->method( '__toString' )
944  ->will( $this->returnValue( 'Mock2' ) );
945  $provider2->expects( $this->any() )->method( 'refreshSessionInfo' )
946  ->will( $this->returnCallback( function ( $info, $request, &$metadata ) {
947  $metadata['changed'] = true;
948  return true;
949  } ) );
950 
951  $provider3 = $builder->getMockForAbstractClass();
952  $provider3->setManager( $manager );
953  $provider3->expects( $this->any() )->method( 'persistsSessionId' )
954  ->will( $this->returnValue( true ) );
955  $provider3->expects( $this->any() )->method( 'canChangeUser' )
956  ->will( $this->returnValue( true ) );
957  $provider3->expects( $this->once() )->method( 'refreshSessionInfo' )
958  ->will( $this->returnValue( false ) );
959  $provider3->expects( $this->any() )->method( '__toString' )
960  ->will( $this->returnValue( 'Mock3' ) );
961 
962  \TestingAccessWrapper::newFromObject( $manager )->sessionProviders = [
963  (string)$provider => $provider,
964  (string)$provider2 => $provider2,
965  (string)$provider3 => $provider3,
966  ];
967 
968  // No metadata, basic usage
969  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
970  'provider' => $provider,
971  'id' => $id,
972  'userInfo' => $userInfo
973  ] );
974  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
975  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
976  $this->assertFalse( $info->isIdSafe() );
977  $this->assertSame( [], $logger->getBuffer() );
978 
979  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
980  'provider' => $provider,
981  'userInfo' => $userInfo
982  ] );
983  $this->assertTrue( $info->isIdSafe(), 'sanity check' );
984  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
985  $this->assertTrue( $info->isIdSafe() );
986  $this->assertSame( [], $logger->getBuffer() );
987 
988  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
989  'provider' => $provider2,
990  'id' => $id,
991  'userInfo' => $userInfo
992  ] );
993  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
994  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
995  $this->assertTrue( $info->isIdSafe() );
996  $this->assertSame( [], $logger->getBuffer() );
997 
998  // Unverified user, no metadata
999  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1000  'provider' => $provider,
1001  'id' => $id,
1002  'userInfo' => $unverifiedUserInfo
1003  ] );
1004  $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() );
1005  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1006  $this->assertSame( [
1007  [
1008  LogLevel::WARNING,
1009  'Session "{session}": Unverified user provided and no metadata to auth it',
1010  ]
1011  ], $logger->getBuffer() );
1012  $logger->clearBuffer();
1013 
1014  // No metadata, missing data
1015  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1016  'id' => $id,
1017  'userInfo' => $userInfo
1018  ] );
1019  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1020  $this->assertSame( [
1021  [ LogLevel::WARNING, 'Session "{session}": Null provider and no metadata' ],
1022  ], $logger->getBuffer() );
1023  $logger->clearBuffer();
1024 
1025  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1026  'provider' => $provider,
1027  'id' => $id,
1028  ] );
1029  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1030  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1031  $this->assertInstanceOf( UserInfo::class, $info->getUserInfo() );
1032  $this->assertTrue( $info->getUserInfo()->isVerified() );
1033  $this->assertTrue( $info->getUserInfo()->isAnon() );
1034  $this->assertFalse( $info->isIdSafe() );
1035  $this->assertSame( [], $logger->getBuffer() );
1036 
1037  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1038  'provider' => $provider2,
1039  'id' => $id,
1040  ] );
1041  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1042  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1043  $this->assertSame( [
1044  [ LogLevel::INFO, 'Session "{session}": No user provided and provider cannot set user' ]
1045  ], $logger->getBuffer() );
1046  $logger->clearBuffer();
1047 
1048  // Incomplete/bad metadata
1049  $this->store->setRawSession( $id, true );
1050  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1051  $this->assertSame( [
1052  [ LogLevel::WARNING, 'Session "{session}": Bad data' ],
1053  ], $logger->getBuffer() );
1054  $logger->clearBuffer();
1055 
1056  $this->store->setRawSession( $id, [ 'data' => [] ] );
1057  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1058  $this->assertSame( [
1059  [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1060  ], $logger->getBuffer() );
1061  $logger->clearBuffer();
1062 
1063  $this->store->deleteSession( $id );
1064  $this->store->setRawSession( $id, [ 'metadata' => $metadata ] );
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->setRawSession( $id, [ 'metadata' => $metadata, 'data' => true ] );
1072  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1073  $this->assertSame( [
1074  [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1075  ], $logger->getBuffer() );
1076  $logger->clearBuffer();
1077 
1078  $this->store->setRawSession( $id, [ 'metadata' => true, 'data' => [] ] );
1079  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1080  $this->assertSame( [
1081  [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1082  ], $logger->getBuffer() );
1083  $logger->clearBuffer();
1084 
1085  foreach ( $metadata as $key => $dummy ) {
1086  $tmp = $metadata;
1087  unset( $tmp[$key] );
1088  $this->store->setRawSession( $id, [ 'metadata' => $tmp, 'data' => [] ] );
1089  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1090  $this->assertSame( [
1091  [ LogLevel::WARNING, 'Session "{session}": Bad metadata' ],
1092  ], $logger->getBuffer() );
1093  $logger->clearBuffer();
1094  }
1095 
1096  // Basic usage with metadata
1097  $this->store->setRawSession( $id, [ 'metadata' => $metadata, 'data' => [] ] );
1098  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1099  'provider' => $provider,
1100  'id' => $id,
1101  'userInfo' => $userInfo
1102  ] );
1103  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1104  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1105  $this->assertTrue( $info->isIdSafe() );
1106  $this->assertSame( [], $logger->getBuffer() );
1107 
1108  // Mismatched provider
1109  $this->store->setSessionMeta( $id, [ 'provider' => 'Bad' ] + $metadata );
1110  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1111  'provider' => $provider,
1112  'id' => $id,
1113  'userInfo' => $userInfo
1114  ] );
1115  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1116  $this->assertSame( [
1117  [ LogLevel::WARNING, 'Session "{session}": Wrong provider Bad !== Mock' ],
1118  ], $logger->getBuffer() );
1119  $logger->clearBuffer();
1120 
1121  // Unknown provider
1122  $this->store->setSessionMeta( $id, [ 'provider' => 'Bad' ] + $metadata );
1123  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1124  'id' => $id,
1125  'userInfo' => $userInfo
1126  ] );
1127  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1128  $this->assertSame( [
1129  [ LogLevel::WARNING, 'Session "{session}": Unknown provider Bad' ],
1130  ], $logger->getBuffer() );
1131  $logger->clearBuffer();
1132 
1133  // Fill in provider
1134  $this->store->setSessionMeta( $id, $metadata );
1135  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1136  'id' => $id,
1137  'userInfo' => $userInfo
1138  ] );
1139  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1140  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1141  $this->assertTrue( $info->isIdSafe() );
1142  $this->assertSame( [], $logger->getBuffer() );
1143 
1144  // Bad user metadata
1145  $this->store->setSessionMeta( $id, [ 'userId' => -1, 'userToken' => null ] + $metadata );
1146  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1147  'provider' => $provider,
1148  'id' => $id,
1149  ] );
1150  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1151  $this->assertSame( [
1152  [ LogLevel::ERROR, 'Session "{session}": {exception}' ],
1153  ], $logger->getBuffer() );
1154  $logger->clearBuffer();
1155 
1156  $this->store->setSessionMeta(
1157  $id, [ 'userId' => 0, 'userName' => '<X>', 'userToken' => null ] + $metadata
1158  );
1159  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1160  'provider' => $provider,
1161  'id' => $id,
1162  ] );
1163  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1164  $this->assertSame( [
1165  [ LogLevel::ERROR, 'Session "{session}": {exception}', ],
1166  ], $logger->getBuffer() );
1167  $logger->clearBuffer();
1168 
1169  // Mismatched user by ID
1170  $this->store->setSessionMeta(
1171  $id, [ 'userId' => $userInfo->getId() + 1, 'userToken' => null ] + $metadata
1172  );
1173  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1174  'provider' => $provider,
1175  'id' => $id,
1176  'userInfo' => $userInfo
1177  ] );
1178  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1179  $this->assertSame( [
1180  [ LogLevel::WARNING, 'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}' ],
1181  ], $logger->getBuffer() );
1182  $logger->clearBuffer();
1183 
1184  // Mismatched user by name
1185  $this->store->setSessionMeta(
1186  $id, [ 'userId' => 0, 'userName' => 'X', 'userToken' => null ] + $metadata
1187  );
1188  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1189  'provider' => $provider,
1190  'id' => $id,
1191  'userInfo' => $userInfo
1192  ] );
1193  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1194  $this->assertSame( [
1195  [ LogLevel::WARNING, 'Session "{session}": User name mismatch, {uname_a} !== {uname_b}' ],
1196  ], $logger->getBuffer() );
1197  $logger->clearBuffer();
1198 
1199  // ID matches, name doesn't
1200  $this->store->setSessionMeta(
1201  $id, [ 'userId' => $userInfo->getId(), 'userName' => 'X', 'userToken' => null ] + $metadata
1202  );
1203  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1204  'provider' => $provider,
1205  'id' => $id,
1206  'userInfo' => $userInfo
1207  ] );
1208  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1209  $this->assertSame( [
1210  [
1211  LogLevel::WARNING,
1212  'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}'
1213  ],
1214  ], $logger->getBuffer() );
1215  $logger->clearBuffer();
1216 
1217  // Mismatched anon user
1218  $this->store->setSessionMeta(
1219  $id, [ 'userId' => 0, 'userName' => null, 'userToken' => null ] + $metadata
1220  );
1221  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1222  'provider' => $provider,
1223  'id' => $id,
1224  'userInfo' => $userInfo
1225  ] );
1226  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1227  $this->assertSame( [
1228  [
1229  LogLevel::WARNING,
1230  'Session "{session}": Metadata has an anonymous user, ' .
1231  'but a non-anon user was provided',
1232  ],
1233  ], $logger->getBuffer() );
1234  $logger->clearBuffer();
1235 
1236  // Lookup user by ID
1237  $this->store->setSessionMeta( $id, [ 'userToken' => null ] + $metadata );
1238  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1239  'provider' => $provider,
1240  'id' => $id,
1241  ] );
1242  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1243  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1244  $this->assertSame( $userInfo->getId(), $info->getUserInfo()->getId() );
1245  $this->assertTrue( $info->isIdSafe() );
1246  $this->assertSame( [], $logger->getBuffer() );
1247 
1248  // Lookup user by name
1249  $this->store->setSessionMeta(
1250  $id, [ 'userId' => 0, 'userName' => 'UTSysop', 'userToken' => null ] + $metadata
1251  );
1252  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1253  'provider' => $provider,
1254  'id' => $id,
1255  ] );
1256  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1257  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1258  $this->assertSame( $userInfo->getId(), $info->getUserInfo()->getId() );
1259  $this->assertTrue( $info->isIdSafe() );
1260  $this->assertSame( [], $logger->getBuffer() );
1261 
1262  // Lookup anonymous user
1263  $this->store->setSessionMeta(
1264  $id, [ 'userId' => 0, 'userName' => null, 'userToken' => null ] + $metadata
1265  );
1266  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1267  'provider' => $provider,
1268  'id' => $id,
1269  ] );
1270  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1271  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1272  $this->assertTrue( $info->getUserInfo()->isAnon() );
1273  $this->assertTrue( $info->isIdSafe() );
1274  $this->assertSame( [], $logger->getBuffer() );
1275 
1276  // Unverified user with metadata
1277  $this->store->setSessionMeta( $id, $metadata );
1278  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1279  'provider' => $provider,
1280  'id' => $id,
1281  'userInfo' => $unverifiedUserInfo
1282  ] );
1283  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1284  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1285  $this->assertTrue( $info->getUserInfo()->isVerified() );
1286  $this->assertSame( $unverifiedUserInfo->getId(), $info->getUserInfo()->getId() );
1287  $this->assertSame( $unverifiedUserInfo->getName(), $info->getUserInfo()->getName() );
1288  $this->assertTrue( $info->isIdSafe() );
1289  $this->assertSame( [], $logger->getBuffer() );
1290 
1291  // Unverified user with metadata
1292  $this->store->setSessionMeta( $id, $metadata );
1293  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1294  'provider' => $provider,
1295  'id' => $id,
1296  'userInfo' => $unverifiedUserInfo
1297  ] );
1298  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1299  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1300  $this->assertTrue( $info->getUserInfo()->isVerified() );
1301  $this->assertSame( $unverifiedUserInfo->getId(), $info->getUserInfo()->getId() );
1302  $this->assertSame( $unverifiedUserInfo->getName(), $info->getUserInfo()->getName() );
1303  $this->assertTrue( $info->isIdSafe() );
1304  $this->assertSame( [], $logger->getBuffer() );
1305 
1306  // Wrong token
1307  $this->store->setSessionMeta( $id, [ 'userToken' => 'Bad' ] + $metadata );
1308  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1309  'provider' => $provider,
1310  'id' => $id,
1311  'userInfo' => $userInfo
1312  ] );
1313  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1314  $this->assertSame( [
1315  [ LogLevel::WARNING, 'Session "{session}": User token mismatch' ],
1316  ], $logger->getBuffer() );
1317  $logger->clearBuffer();
1318 
1319  // Provider metadata
1320  $this->store->setSessionMeta( $id, [ 'provider' => 'Mock2' ] + $metadata );
1321  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1322  'provider' => $provider2,
1323  'id' => $id,
1324  'userInfo' => $userInfo,
1325  'metadata' => [ 'Info' ],
1326  ] );
1327  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1328  $this->assertSame( [ 'Info', 'changed' => true ], $info->getProviderMetadata() );
1329  $this->assertSame( [], $logger->getBuffer() );
1330 
1331  $this->store->setSessionMeta( $id, [ 'providerMetadata' => [ 'Saved' ] ] + $metadata );
1332  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1333  'provider' => $provider,
1334  'id' => $id,
1335  'userInfo' => $userInfo,
1336  ] );
1337  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1338  $this->assertSame( [ 'Saved' ], $info->getProviderMetadata() );
1339  $this->assertSame( [], $logger->getBuffer() );
1340 
1341  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1342  'provider' => $provider,
1343  'id' => $id,
1344  'userInfo' => $userInfo,
1345  'metadata' => [ 'Info' ],
1346  ] );
1347  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1348  $this->assertSame( [ 'Merged' ], $info->getProviderMetadata() );
1349  $this->assertSame( [], $logger->getBuffer() );
1350 
1351  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1352  'provider' => $provider,
1353  'id' => $id,
1354  'userInfo' => $userInfo,
1355  'metadata' => [ 'Throw' ],
1356  ] );
1357  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1358  $this->assertSame( [
1359  [
1360  LogLevel::WARNING,
1361  'Session "{session}": Metadata merge failed: {exception}',
1362  ],
1363  ], $logger->getBuffer() );
1364  $logger->clearBuffer();
1365 
1366  // Remember from session
1367  $this->store->setSessionMeta( $id, $metadata );
1368  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1369  'provider' => $provider,
1370  'id' => $id,
1371  ] );
1372  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1373  $this->assertFalse( $info->wasRemembered() );
1374  $this->assertSame( [], $logger->getBuffer() );
1375 
1376  $this->store->setSessionMeta( $id, [ 'remember' => true ] + $metadata );
1377  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1378  'provider' => $provider,
1379  'id' => $id,
1380  ] );
1381  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1382  $this->assertTrue( $info->wasRemembered() );
1383  $this->assertSame( [], $logger->getBuffer() );
1384 
1385  $this->store->setSessionMeta( $id, [ 'remember' => false ] + $metadata );
1386  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1387  'provider' => $provider,
1388  'id' => $id,
1389  'userInfo' => $userInfo
1390  ] );
1391  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1392  $this->assertTrue( $info->wasRemembered() );
1393  $this->assertSame( [], $logger->getBuffer() );
1394 
1395  // forceHTTPS from session
1396  $this->store->setSessionMeta( $id, $metadata );
1397  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1398  'provider' => $provider,
1399  'id' => $id,
1400  'userInfo' => $userInfo
1401  ] );
1402  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1403  $this->assertFalse( $info->forceHTTPS() );
1404  $this->assertSame( [], $logger->getBuffer() );
1405 
1406  $this->store->setSessionMeta( $id, [ 'forceHTTPS' => true ] + $metadata );
1407  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1408  'provider' => $provider,
1409  'id' => $id,
1410  'userInfo' => $userInfo
1411  ] );
1412  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1413  $this->assertTrue( $info->forceHTTPS() );
1414  $this->assertSame( [], $logger->getBuffer() );
1415 
1416  $this->store->setSessionMeta( $id, [ 'forceHTTPS' => false ] + $metadata );
1417  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1418  'provider' => $provider,
1419  'id' => $id,
1420  'userInfo' => $userInfo,
1421  'forceHTTPS' => true
1422  ] );
1423  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1424  $this->assertTrue( $info->forceHTTPS() );
1425  $this->assertSame( [], $logger->getBuffer() );
1426 
1427  // "Persist" flag from session
1428  $this->store->setSessionMeta( $id, $metadata );
1429  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1430  'provider' => $provider,
1431  'id' => $id,
1432  'userInfo' => $userInfo
1433  ] );
1434  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1435  $this->assertFalse( $info->wasPersisted() );
1436  $this->assertSame( [], $logger->getBuffer() );
1437 
1438  $this->store->setSessionMeta( $id, [ 'persisted' => true ] + $metadata );
1439  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1440  'provider' => $provider,
1441  'id' => $id,
1442  'userInfo' => $userInfo
1443  ] );
1444  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1445  $this->assertTrue( $info->wasPersisted() );
1446  $this->assertSame( [], $logger->getBuffer() );
1447 
1448  $this->store->setSessionMeta( $id, [ 'persisted' => false ] + $metadata );
1449  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1450  'provider' => $provider,
1451  'id' => $id,
1452  'userInfo' => $userInfo,
1453  'persisted' => true
1454  ] );
1455  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1456  $this->assertTrue( $info->wasPersisted() );
1457  $this->assertSame( [], $logger->getBuffer() );
1458 
1459  // Provider refreshSessionInfo() returning false
1460  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1461  'provider' => $provider3,
1462  ] );
1463  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1464  $this->assertSame( [], $logger->getBuffer() );
1465 
1466  // Hook
1467  $called = false;
1468  $data = [ 'foo' => 1 ];
1469  $this->store->setSession( $id, [ 'metadata' => $metadata, 'data' => $data ] );
1470  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1471  'provider' => $provider,
1472  'id' => $id,
1473  'userInfo' => $userInfo
1474  ] );
1475  $this->mergeMwGlobalArrayValue( 'wgHooks', [
1476  'SessionCheckInfo' => [ function ( &$reason, $i, $r, $m, $d ) use (
1477  $info, $metadata, $data, $request, &$called
1478  ) {
1479  $this->assertSame( $info->getId(), $i->getId() );
1480  $this->assertSame( $info->getProvider(), $i->getProvider() );
1481  $this->assertSame( $info->getUserInfo(), $i->getUserInfo() );
1482  $this->assertSame( $request, $r );
1483  $this->assertEquals( $metadata, $m );
1484  $this->assertEquals( $data, $d );
1485  $called = true;
1486  return false;
1487  } ]
1488  ] );
1489  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1490  $this->assertTrue( $called );
1491  $this->assertSame( [
1492  [ LogLevel::WARNING, 'Session "{session}": Hook aborted' ],
1493  ], $logger->getBuffer() );
1494  $logger->clearBuffer();
1495  $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionCheckInfo' => [] ] );
1496 
1497  // forceUse deletes bad backend data
1498  $this->store->setSessionMeta( $id, [ 'userToken' => 'Bad' ] + $metadata );
1499  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1500  'provider' => $provider,
1501  'id' => $id,
1502  'userInfo' => $userInfo,
1503  'forceUse' => true,
1504  ] );
1505  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1506  $this->assertFalse( $this->store->getSession( $id ) );
1507  $this->assertSame( [
1508  [ LogLevel::WARNING, 'Session "{session}": User token mismatch' ],
1509  ], $logger->getBuffer() );
1510  $logger->clearBuffer();
1511  }
1512 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:525
const MIN_PRIORITY
Minimum allowed priority.
Definition: SessionInfo.php:36
mergeMwGlobalArrayValue($name, $values)
Merges the given values into a MW global array variable.
$called
$called tracks whether the setUp and tearDown method has been called.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static setSessionManagerSingleton(SessionManager $manager=null)
Override the singleton for unit testing.
Definition: TestUtils.php:17
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:177
Session Database MediaWiki\Session\SessionManager.
getId()
Return the session ID.
Subclass of UnexpectedValueException that can be annotated with additional data for debug logging...
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
static newAnonymous()
Create an instance for an anonymous (i.e.
Definition: UserInfo.php:74
static BagOStuff[] $instances
Map of (id => BagOStuff)
Definition: ObjectCache.php:82
BagOStuff with utility functions for MediaWiki\\Session\\* testing.
static getMain()
Static methods.
IContextSource $context
Definition: MediaWiki.php:33
static install(SessionManager $manager)
Install a session handler for the current web request.
static isInstalled()
Test whether the handler is installed.
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
static singleton()
Get the global SessionManager.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
const MAX_PRIORITY
Maximum allowed priority.
Definition: SessionInfo.php:39
static getGlobalSession()
Get the "global" session.
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
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2573
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
static validateSessionId($id)
Validate a session ID.
static idFromName($name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:728
This serves as the entry point to the MediaWiki session handling system.
static newFromObject($object)
Return the same object, without access restrictions.
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:802
static newFromName($name, $verified=false)
Create an instance for a logged-in user by name.
Definition: UserInfo.php:102
Value object returned by SessionProvider.
Definition: SessionInfo.php:34