MediaWiki  master
SessionManagerTest.php
Go to the documentation of this file.
1 <?php
2 
3 namespace MediaWiki\Session;
4 
7 use Psr\Log\LogLevel;
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 \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 testAutoCreateUser() {
872 
873  if ( !$wgDisableAuthManager ) {
874  $this->markTestSkipped( 'AuthManager is not disabled' );
875  }
876 
877  \ObjectCache::$instances[__METHOD__] = new TestBagOStuff();
878  $this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] );
879  $this->setMwGlobals( [
880  'wgAuth' => new AuthPlugin,
881  ] );
882 
883  $this->stashMwGlobals( [ 'wgGroupPermissions' ] );
884  $wgGroupPermissions['*']['createaccount'] = true;
885  $wgGroupPermissions['*']['autocreateaccount'] = false;
886 
887  // Replace the global singleton with one configured for testing
888  $manager = $this->getManager();
889  $reset = TestUtils::setSessionManagerSingleton( $manager );
890 
891  $logger = new \TestLogger( true, function ( $m ) {
892  if ( substr( $m, 0, 15 ) === 'SessionBackend ' ) {
893  // Don't care.
894  return null;
895  }
896  $m = str_replace( 'MediaWiki\Session\SessionManager::autoCreateUser: ', '', $m );
897  return $m;
898  } );
899  $manager->setLogger( $logger );
900 
902 
903  // Can't create an already-existing user
904  $user = User::newFromName( 'UTSysop' );
905  $id = $user->getId();
906  $this->assertFalse( $manager->autoCreateUser( $user ) );
907  $this->assertSame( $id, $user->getId() );
908  $this->assertSame( 'UTSysop', $user->getName() );
909  $this->assertSame( [], $logger->getBuffer() );
910  $logger->clearBuffer();
911 
912  // Sanity check that creation works at all
913  $user = User::newFromName( 'UTSessionAutoCreate1' );
914  $this->assertSame( 0, $user->getId(), 'sanity check' );
915  $this->assertTrue( $manager->autoCreateUser( $user ) );
916  $this->assertNotEquals( 0, $user->getId() );
917  $this->assertSame( 'UTSessionAutoCreate1', $user->getName() );
918  $this->assertEquals(
919  $user->getId(), User::idFromName( 'UTSessionAutoCreate1', User::READ_LATEST )
920  );
921  $this->assertSame( [
922  [ LogLevel::INFO, 'creating new user ({username}) - from: {url}' ],
923  ], $logger->getBuffer() );
924  $logger->clearBuffer();
925 
926  // Check lack of permissions
927  $wgGroupPermissions['*']['createaccount'] = false;
928  $wgGroupPermissions['*']['autocreateaccount'] = false;
929  $user = User::newFromName( 'UTDoesNotExist' );
930  $this->assertFalse( $manager->autoCreateUser( $user ) );
931  $this->assertSame( 0, $user->getId() );
932  $this->assertNotSame( 'UTDoesNotExist', $user->getName() );
933  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
934  $session->clear();
935  $this->assertSame( [
936  [
937  LogLevel::DEBUG,
938  'user is blocked from this wiki, blacklisting',
939  ],
940  ], $logger->getBuffer() );
941  $logger->clearBuffer();
942 
943  // Check other permission
944  $wgGroupPermissions['*']['createaccount'] = false;
945  $wgGroupPermissions['*']['autocreateaccount'] = true;
946  $user = User::newFromName( 'UTSessionAutoCreate2' );
947  $this->assertSame( 0, $user->getId(), 'sanity check' );
948  $this->assertTrue( $manager->autoCreateUser( $user ) );
949  $this->assertNotEquals( 0, $user->getId() );
950  $this->assertSame( 'UTSessionAutoCreate2', $user->getName() );
951  $this->assertEquals(
952  $user->getId(), User::idFromName( 'UTSessionAutoCreate2', User::READ_LATEST )
953  );
954  $this->assertSame( [
955  [ LogLevel::INFO, 'creating new user ({username}) - from: {url}' ],
956  ], $logger->getBuffer() );
957  $logger->clearBuffer();
958 
959  // Test account-creation block
960  $anon = new User;
961  $block = new \Block( [
962  'address' => $anon->getName(),
963  'user' => $id,
964  'reason' => __METHOD__,
965  'expiry' => time() + 100500,
966  'createAccount' => true,
967  ] );
968  $block->insert();
969  $this->assertInstanceOf( 'Block', $anon->isBlockedFromCreateAccount(), 'sanity check' );
970  $reset2 = new \ScopedCallback( [ $block, 'delete' ] );
971  $user = User::newFromName( 'UTDoesNotExist' );
972  $this->assertFalse( $manager->autoCreateUser( $user ) );
973  $this->assertSame( 0, $user->getId() );
974  $this->assertNotSame( 'UTDoesNotExist', $user->getName() );
975  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
976  \ScopedCallback::consume( $reset2 );
977  $session->clear();
978  $this->assertSame( [
979  [ LogLevel::DEBUG, 'user is blocked from this wiki, blacklisting' ],
980  ], $logger->getBuffer() );
981  $logger->clearBuffer();
982 
983  // Sanity check that creation still works
984  $user = User::newFromName( 'UTSessionAutoCreate3' );
985  $this->assertSame( 0, $user->getId(), 'sanity check' );
986  $this->assertTrue( $manager->autoCreateUser( $user ) );
987  $this->assertNotEquals( 0, $user->getId() );
988  $this->assertSame( 'UTSessionAutoCreate3', $user->getName() );
989  $this->assertEquals(
990  $user->getId(), User::idFromName( 'UTSessionAutoCreate3', User::READ_LATEST )
991  );
992  $this->assertSame( [
993  [ LogLevel::INFO, 'creating new user ({username}) - from: {url}' ],
994  ], $logger->getBuffer() );
995  $logger->clearBuffer();
996 
997  // Test prevention by AuthPlugin
998  global $wgAuth;
999  $oldWgAuth = $wgAuth;
1000  $mockWgAuth = $this->getMock( 'AuthPlugin', [ 'autoCreate' ] );
1001  $mockWgAuth->expects( $this->once() )->method( 'autoCreate' )
1002  ->will( $this->returnValue( false ) );
1003  $this->setMwGlobals( [
1004  'wgAuth' => $mockWgAuth,
1005  ] );
1006  $user = User::newFromName( 'UTDoesNotExist' );
1007  $this->assertFalse( $manager->autoCreateUser( $user ) );
1008  $this->assertSame( 0, $user->getId() );
1009  $this->assertNotSame( 'UTDoesNotExist', $user->getName() );
1010  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
1011  $this->setMwGlobals( [
1012  'wgAuth' => $oldWgAuth,
1013  ] );
1014  $session->clear();
1015  $this->assertSame( [
1016  [ LogLevel::DEBUG, 'denied by AuthPlugin' ],
1017  ], $logger->getBuffer() );
1018  $logger->clearBuffer();
1019 
1020  // Test prevention by wfReadOnly()
1021  $this->setMwGlobals( [
1022  'wgReadOnly' => 'Because',
1023  ] );
1024  $user = User::newFromName( 'UTDoesNotExist' );
1025  $this->assertFalse( $manager->autoCreateUser( $user ) );
1026  $this->assertSame( 0, $user->getId() );
1027  $this->assertNotSame( 'UTDoesNotExist', $user->getName() );
1028  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
1029  $this->setMwGlobals( [
1030  'wgReadOnly' => false,
1031  ] );
1032  $session->clear();
1033  $this->assertSame( [
1034  [ LogLevel::DEBUG, 'denied by wfReadOnly()' ],
1035  ], $logger->getBuffer() );
1036  $logger->clearBuffer();
1037 
1038  // Test prevention by a previous session
1039  $session->set( 'MWSession::AutoCreateBlacklist', 'test' );
1040  $user = User::newFromName( 'UTDoesNotExist' );
1041  $this->assertFalse( $manager->autoCreateUser( $user ) );
1042  $this->assertSame( 0, $user->getId() );
1043  $this->assertNotSame( 'UTDoesNotExist', $user->getName() );
1044  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
1045  $session->clear();
1046  $this->assertSame( [
1047  [ LogLevel::DEBUG, 'blacklisted in session (test)' ],
1048  ], $logger->getBuffer() );
1049  $logger->clearBuffer();
1050 
1051  // Test uncreatable name
1052  $user = User::newFromName( 'UTDoesNotExist@' );
1053  $this->assertFalse( $manager->autoCreateUser( $user ) );
1054  $this->assertSame( 0, $user->getId() );
1055  $this->assertNotSame( 'UTDoesNotExist@', $user->getName() );
1056  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
1057  $session->clear();
1058  $this->assertSame( [
1059  [ LogLevel::DEBUG, 'Invalid username, blacklisting' ],
1060  ], $logger->getBuffer() );
1061  $logger->clearBuffer();
1062 
1063  // Test AbortAutoAccount hook
1064  $mock = $this->getMock( __CLASS__, [ 'onAbortAutoAccount' ] );
1065  $mock->expects( $this->once() )->method( 'onAbortAutoAccount' )
1066  ->will( $this->returnCallback( function ( User $user, &$msg ) {
1067  $msg = 'No way!';
1068  return false;
1069  } ) );
1070  $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'AbortAutoAccount' => [ $mock ] ] );
1071  $user = User::newFromName( 'UTDoesNotExist' );
1072  $this->assertFalse( $manager->autoCreateUser( $user ) );
1073  $this->assertSame( 0, $user->getId() );
1074  $this->assertNotSame( 'UTDoesNotExist', $user->getName() );
1075  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
1076  $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'AbortAutoAccount' => [] ] );
1077  $session->clear();
1078  $this->assertSame( [
1079  [ LogLevel::DEBUG, 'denied by hook: No way!' ],
1080  ], $logger->getBuffer() );
1081  $logger->clearBuffer();
1082 
1083  // Test AbortAutoAccount hook screwing up the name
1084  $mock = $this->getMock( 'stdClass', [ 'onAbortAutoAccount' ] );
1085  $mock->expects( $this->once() )->method( 'onAbortAutoAccount' )
1086  ->will( $this->returnCallback( function ( User $user ) {
1087  $user->setName( 'UTDoesNotExistEither' );
1088  } ) );
1089  $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'AbortAutoAccount' => [ $mock ] ] );
1090  try {
1091  $user = User::newFromName( 'UTDoesNotExist' );
1092  $manager->autoCreateUser( $user );
1093  $this->fail( 'Expected exception not thrown' );
1094  } catch ( \UnexpectedValueException $ex ) {
1095  $this->assertSame(
1096  'AbortAutoAccount hook tried to change the user name',
1097  $ex->getMessage()
1098  );
1099  }
1100  $this->assertSame( 0, $user->getId() );
1101  $this->assertNotSame( 'UTDoesNotExist', $user->getName() );
1102  $this->assertNotSame( 'UTDoesNotExistEither', $user->getName() );
1103  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
1104  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExistEither', User::READ_LATEST ) );
1105  $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'AbortAutoAccount' => [] ] );
1106  $session->clear();
1107  $this->assertSame( [], $logger->getBuffer() );
1108  $logger->clearBuffer();
1109 
1110  // Test for "exception backoff"
1111  $user = User::newFromName( 'UTDoesNotExist' );
1113  $backoffKey = wfMemcKey( 'MWSession', 'autocreate-failed', md5( $user->getName() ) );
1114  $cache->set( $backoffKey, 1, 60 * 10 );
1115  $this->assertFalse( $manager->autoCreateUser( $user ) );
1116  $this->assertSame( 0, $user->getId() );
1117  $this->assertNotSame( 'UTDoesNotExist', $user->getName() );
1118  $this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
1119  $cache->delete( $backoffKey );
1120  $session->clear();
1121  $this->assertSame( [
1122  [ LogLevel::DEBUG, 'denied by prior creation attempt failures' ],
1123  ], $logger->getBuffer() );
1124  $logger->clearBuffer();
1125 
1126  // Sanity check that creation still works, and test completion hook
1127  $cb = $this->callback( function ( User $user ) {
1128  $this->assertNotEquals( 0, $user->getId() );
1129  $this->assertSame( 'UTSessionAutoCreate4', $user->getName() );
1130  $this->assertEquals(
1131  $user->getId(), User::idFromName( 'UTSessionAutoCreate4', User::READ_LATEST )
1132  );
1133  return true;
1134  } );
1135  $mock = $this->getMock( 'stdClass',
1136  [ 'onAuthPluginAutoCreate', 'onLocalUserCreated' ] );
1137  $mock->expects( $this->once() )->method( 'onAuthPluginAutoCreate' )
1138  ->with( $cb );
1139  $mock->expects( $this->once() )->method( 'onLocalUserCreated' )
1140  ->with( $cb, $this->identicalTo( true ) );
1141  $this->mergeMwGlobalArrayValue( 'wgHooks', [
1142  'AuthPluginAutoCreate' => [ $mock ],
1143  'LocalUserCreated' => [ $mock ],
1144  ] );
1145  $user = User::newFromName( 'UTSessionAutoCreate4' );
1146  $this->assertSame( 0, $user->getId(), 'sanity check' );
1147  $this->assertTrue( $manager->autoCreateUser( $user ) );
1148  $this->assertNotEquals( 0, $user->getId() );
1149  $this->assertSame( 'UTSessionAutoCreate4', $user->getName() );
1150  $this->assertEquals(
1151  $user->getId(),
1152  User::idFromName( 'UTSessionAutoCreate4', User::READ_LATEST )
1153  );
1154  $this->mergeMwGlobalArrayValue( 'wgHooks', [
1155  'AuthPluginAutoCreate' => [],
1156  'LocalUserCreated' => [],
1157  ] );
1158  $this->assertSame( [
1159  [ LogLevel::INFO, 'creating new user ({username}) - from: {url}' ],
1160  ], $logger->getBuffer() );
1161  $logger->clearBuffer();
1162  }
1163 
1164  public function onAbortAutoAccount( User $user, &$msg ) {
1165  }
1166 
1167  public function testPreventSessionsForUser() {
1168  $manager = $this->getManager();
1169 
1170  $providerBuilder = $this->getMockBuilder( 'DummySessionProvider' )
1171  ->setMethods( [ 'preventSessionsForUser', '__toString' ] );
1172 
1173  $provider1 = $providerBuilder->getMock();
1174  $provider1->expects( $this->once() )->method( 'preventSessionsForUser' )
1175  ->with( $this->equalTo( 'UTSysop' ) );
1176  $provider1->expects( $this->any() )->method( '__toString' )
1177  ->will( $this->returnValue( 'MockProvider1' ) );
1178 
1179  $this->config->set( 'SessionProviders', [
1180  $this->objectCacheDef( $provider1 ),
1181  ] );
1182 
1183  $this->assertFalse( $manager->isUserSessionPrevented( 'UTSysop' ) );
1184  $manager->preventSessionsForUser( 'UTSysop' );
1185  $this->assertTrue( $manager->isUserSessionPrevented( 'UTSysop' ) );
1186  }
1187 
1188  public function testLoadSessionInfoFromStore() {
1189  $manager = $this->getManager();
1190  $logger = new \TestLogger( true );
1191  $manager->setLogger( $logger );
1192  $request = new \FauxRequest();
1193 
1194  // TestingAccessWrapper can't handle methods with reference arguments, sigh.
1195  $rClass = new \ReflectionClass( $manager );
1196  $rMethod = $rClass->getMethod( 'loadSessionInfoFromStore' );
1197  $rMethod->setAccessible( true );
1198  $loadSessionInfoFromStore = function ( &$info ) use ( $rMethod, $manager, $request ) {
1199  return $rMethod->invokeArgs( $manager, [ &$info, $request ] );
1200  };
1201 
1202  $userInfo = UserInfo::newFromName( 'UTSysop', true );
1203  $unverifiedUserInfo = UserInfo::newFromName( 'UTSysop', false );
1204 
1205  $id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
1206  $metadata = [
1207  'userId' => $userInfo->getId(),
1208  'userName' => $userInfo->getName(),
1209  'userToken' => $userInfo->getToken( true ),
1210  'provider' => 'Mock',
1211  ];
1212 
1213  $builder = $this->getMockBuilder( SessionProvider::class )
1214  ->setMethods( [ '__toString', 'mergeMetadata', 'refreshSessionInfo' ] );
1215 
1216  $provider = $builder->getMockForAbstractClass();
1217  $provider->setManager( $manager );
1218  $provider->expects( $this->any() )->method( 'persistsSessionId' )
1219  ->will( $this->returnValue( true ) );
1220  $provider->expects( $this->any() )->method( 'canChangeUser' )
1221  ->will( $this->returnValue( true ) );
1222  $provider->expects( $this->any() )->method( 'refreshSessionInfo' )
1223  ->will( $this->returnValue( true ) );
1224  $provider->expects( $this->any() )->method( '__toString' )
1225  ->will( $this->returnValue( 'Mock' ) );
1226  $provider->expects( $this->any() )->method( 'mergeMetadata' )
1227  ->will( $this->returnCallback( function ( $a, $b ) {
1228  if ( $b === [ 'Throw' ] ) {
1229  throw new MetadataMergeException( 'no merge!' );
1230  }
1231  return [ 'Merged' ];
1232  } ) );
1233 
1234  $provider2 = $builder->getMockForAbstractClass();
1235  $provider2->setManager( $manager );
1236  $provider2->expects( $this->any() )->method( 'persistsSessionId' )
1237  ->will( $this->returnValue( false ) );
1238  $provider2->expects( $this->any() )->method( 'canChangeUser' )
1239  ->will( $this->returnValue( false ) );
1240  $provider2->expects( $this->any() )->method( '__toString' )
1241  ->will( $this->returnValue( 'Mock2' ) );
1242  $provider2->expects( $this->any() )->method( 'refreshSessionInfo' )
1243  ->will( $this->returnCallback( function ( $info, $request, &$metadata ) {
1244  $metadata['changed'] = true;
1245  return true;
1246  } ) );
1247 
1248  $provider3 = $builder->getMockForAbstractClass();
1249  $provider3->setManager( $manager );
1250  $provider3->expects( $this->any() )->method( 'persistsSessionId' )
1251  ->will( $this->returnValue( true ) );
1252  $provider3->expects( $this->any() )->method( 'canChangeUser' )
1253  ->will( $this->returnValue( true ) );
1254  $provider3->expects( $this->once() )->method( 'refreshSessionInfo' )
1255  ->will( $this->returnValue( false ) );
1256  $provider3->expects( $this->any() )->method( '__toString' )
1257  ->will( $this->returnValue( 'Mock3' ) );
1258 
1259  \TestingAccessWrapper::newFromObject( $manager )->sessionProviders = [
1260  (string)$provider => $provider,
1261  (string)$provider2 => $provider2,
1262  (string)$provider3 => $provider3,
1263  ];
1264 
1265  // No metadata, basic usage
1266  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1267  'provider' => $provider,
1268  'id' => $id,
1269  'userInfo' => $userInfo
1270  ] );
1271  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1272  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1273  $this->assertFalse( $info->isIdSafe() );
1274  $this->assertSame( [], $logger->getBuffer() );
1275 
1276  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1277  'provider' => $provider,
1278  'userInfo' => $userInfo
1279  ] );
1280  $this->assertTrue( $info->isIdSafe(), 'sanity check' );
1281  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1282  $this->assertTrue( $info->isIdSafe() );
1283  $this->assertSame( [], $logger->getBuffer() );
1284 
1285  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1286  'provider' => $provider2,
1287  'id' => $id,
1288  'userInfo' => $userInfo
1289  ] );
1290  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1291  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1292  $this->assertTrue( $info->isIdSafe() );
1293  $this->assertSame( [], $logger->getBuffer() );
1294 
1295  // Unverified user, no metadata
1296  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1297  'provider' => $provider,
1298  'id' => $id,
1299  'userInfo' => $unverifiedUserInfo
1300  ] );
1301  $this->assertSame( $unverifiedUserInfo, $info->getUserInfo() );
1302  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1303  $this->assertSame( [
1304  [
1305  LogLevel::WARNING,
1306  'Session "{session}": Unverified user provided and no metadata to auth it',
1307  ]
1308  ], $logger->getBuffer() );
1309  $logger->clearBuffer();
1310 
1311  // No metadata, missing data
1312  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1313  'id' => $id,
1314  'userInfo' => $userInfo
1315  ] );
1316  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1317  $this->assertSame( [
1318  [ LogLevel::WARNING, 'Session "{session}": Null provider and no metadata' ],
1319  ], $logger->getBuffer() );
1320  $logger->clearBuffer();
1321 
1322  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1323  'provider' => $provider,
1324  'id' => $id,
1325  ] );
1326  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1327  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1328  $this->assertInstanceOf( UserInfo::class, $info->getUserInfo() );
1329  $this->assertTrue( $info->getUserInfo()->isVerified() );
1330  $this->assertTrue( $info->getUserInfo()->isAnon() );
1331  $this->assertFalse( $info->isIdSafe() );
1332  $this->assertSame( [], $logger->getBuffer() );
1333 
1334  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1335  'provider' => $provider2,
1336  'id' => $id,
1337  ] );
1338  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1339  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1340  $this->assertSame( [
1341  [ LogLevel::INFO, 'Session "{session}": No user provided and provider cannot set user' ]
1342  ], $logger->getBuffer() );
1343  $logger->clearBuffer();
1344 
1345  // Incomplete/bad metadata
1346  $this->store->setRawSession( $id, true );
1347  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1348  $this->assertSame( [
1349  [ LogLevel::WARNING, 'Session "{session}": Bad data' ],
1350  ], $logger->getBuffer() );
1351  $logger->clearBuffer();
1352 
1353  $this->store->setRawSession( $id, [ 'data' => [] ] );
1354  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1355  $this->assertSame( [
1356  [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1357  ], $logger->getBuffer() );
1358  $logger->clearBuffer();
1359 
1360  $this->store->deleteSession( $id );
1361  $this->store->setRawSession( $id, [ 'metadata' => $metadata ] );
1362  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1363  $this->assertSame( [
1364  [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1365  ], $logger->getBuffer() );
1366  $logger->clearBuffer();
1367 
1368  $this->store->setRawSession( $id, [ 'metadata' => $metadata, 'data' => true ] );
1369  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1370  $this->assertSame( [
1371  [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1372  ], $logger->getBuffer() );
1373  $logger->clearBuffer();
1374 
1375  $this->store->setRawSession( $id, [ 'metadata' => true, 'data' => [] ] );
1376  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1377  $this->assertSame( [
1378  [ LogLevel::WARNING, 'Session "{session}": Bad data structure' ],
1379  ], $logger->getBuffer() );
1380  $logger->clearBuffer();
1381 
1382  foreach ( $metadata as $key => $dummy ) {
1383  $tmp = $metadata;
1384  unset( $tmp[$key] );
1385  $this->store->setRawSession( $id, [ 'metadata' => $tmp, 'data' => [] ] );
1386  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1387  $this->assertSame( [
1388  [ LogLevel::WARNING, 'Session "{session}": Bad metadata' ],
1389  ], $logger->getBuffer() );
1390  $logger->clearBuffer();
1391  }
1392 
1393  // Basic usage with metadata
1394  $this->store->setRawSession( $id, [ 'metadata' => $metadata, 'data' => [] ] );
1395  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1396  'provider' => $provider,
1397  'id' => $id,
1398  'userInfo' => $userInfo
1399  ] );
1400  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1401  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1402  $this->assertTrue( $info->isIdSafe() );
1403  $this->assertSame( [], $logger->getBuffer() );
1404 
1405  // Mismatched provider
1406  $this->store->setSessionMeta( $id, [ 'provider' => 'Bad' ] + $metadata );
1407  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1408  'provider' => $provider,
1409  'id' => $id,
1410  'userInfo' => $userInfo
1411  ] );
1412  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1413  $this->assertSame( [
1414  [ LogLevel::WARNING, 'Session "{session}": Wrong provider Bad !== Mock' ],
1415  ], $logger->getBuffer() );
1416  $logger->clearBuffer();
1417 
1418  // Unknown provider
1419  $this->store->setSessionMeta( $id, [ 'provider' => 'Bad' ] + $metadata );
1420  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1421  'id' => $id,
1422  'userInfo' => $userInfo
1423  ] );
1424  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1425  $this->assertSame( [
1426  [ LogLevel::WARNING, 'Session "{session}": Unknown provider Bad' ],
1427  ], $logger->getBuffer() );
1428  $logger->clearBuffer();
1429 
1430  // Fill in provider
1431  $this->store->setSessionMeta( $id, $metadata );
1432  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1433  'id' => $id,
1434  'userInfo' => $userInfo
1435  ] );
1436  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1437  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1438  $this->assertTrue( $info->isIdSafe() );
1439  $this->assertSame( [], $logger->getBuffer() );
1440 
1441  // Bad user metadata
1442  $this->store->setSessionMeta( $id, [ 'userId' => -1, 'userToken' => null ] + $metadata );
1443  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1444  'provider' => $provider,
1445  'id' => $id,
1446  ] );
1447  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1448  $this->assertSame( [
1449  [ LogLevel::ERROR, 'Session "{session}": {exception}' ],
1450  ], $logger->getBuffer() );
1451  $logger->clearBuffer();
1452 
1453  $this->store->setSessionMeta(
1454  $id, [ 'userId' => 0, 'userName' => '<X>', 'userToken' => null ] + $metadata
1455  );
1456  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1457  'provider' => $provider,
1458  'id' => $id,
1459  ] );
1460  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1461  $this->assertSame( [
1462  [ LogLevel::ERROR, 'Session "{session}": {exception}', ],
1463  ], $logger->getBuffer() );
1464  $logger->clearBuffer();
1465 
1466  // Mismatched user by ID
1467  $this->store->setSessionMeta(
1468  $id, [ 'userId' => $userInfo->getId() + 1, 'userToken' => null ] + $metadata
1469  );
1470  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1471  'provider' => $provider,
1472  'id' => $id,
1473  'userInfo' => $userInfo
1474  ] );
1475  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1476  $this->assertSame( [
1477  [ LogLevel::WARNING, 'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}' ],
1478  ], $logger->getBuffer() );
1479  $logger->clearBuffer();
1480 
1481  // Mismatched user by name
1482  $this->store->setSessionMeta(
1483  $id, [ 'userId' => 0, 'userName' => 'X', 'userToken' => null ] + $metadata
1484  );
1485  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1486  'provider' => $provider,
1487  'id' => $id,
1488  'userInfo' => $userInfo
1489  ] );
1490  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1491  $this->assertSame( [
1492  [ LogLevel::WARNING, 'Session "{session}": User name mismatch, {uname_a} !== {uname_b}' ],
1493  ], $logger->getBuffer() );
1494  $logger->clearBuffer();
1495 
1496  // ID matches, name doesn't
1497  $this->store->setSessionMeta(
1498  $id, [ 'userId' => $userInfo->getId(), 'userName' => 'X', 'userToken' => null ] + $metadata
1499  );
1500  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1501  'provider' => $provider,
1502  'id' => $id,
1503  'userInfo' => $userInfo
1504  ] );
1505  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1506  $this->assertSame( [
1507  [
1508  LogLevel::WARNING,
1509  'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}'
1510  ],
1511  ], $logger->getBuffer() );
1512  $logger->clearBuffer();
1513 
1514  // Mismatched anon user
1515  $this->store->setSessionMeta(
1516  $id, [ 'userId' => 0, 'userName' => null, 'userToken' => null ] + $metadata
1517  );
1518  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1519  'provider' => $provider,
1520  'id' => $id,
1521  'userInfo' => $userInfo
1522  ] );
1523  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1524  $this->assertSame( [
1525  [
1526  LogLevel::WARNING,
1527  'Session "{session}": Metadata has an anonymous user, ' .
1528  'but a non-anon user was provided',
1529  ],
1530  ], $logger->getBuffer() );
1531  $logger->clearBuffer();
1532 
1533  // Lookup user by ID
1534  $this->store->setSessionMeta( $id, [ 'userToken' => null ] + $metadata );
1535  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1536  'provider' => $provider,
1537  'id' => $id,
1538  ] );
1539  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1540  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1541  $this->assertSame( $userInfo->getId(), $info->getUserInfo()->getId() );
1542  $this->assertTrue( $info->isIdSafe() );
1543  $this->assertSame( [], $logger->getBuffer() );
1544 
1545  // Lookup user by name
1546  $this->store->setSessionMeta(
1547  $id, [ 'userId' => 0, 'userName' => 'UTSysop', 'userToken' => null ] + $metadata
1548  );
1549  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1550  'provider' => $provider,
1551  'id' => $id,
1552  ] );
1553  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1554  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1555  $this->assertSame( $userInfo->getId(), $info->getUserInfo()->getId() );
1556  $this->assertTrue( $info->isIdSafe() );
1557  $this->assertSame( [], $logger->getBuffer() );
1558 
1559  // Lookup anonymous user
1560  $this->store->setSessionMeta(
1561  $id, [ 'userId' => 0, 'userName' => null, 'userToken' => null ] + $metadata
1562  );
1563  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1564  'provider' => $provider,
1565  'id' => $id,
1566  ] );
1567  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1568  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1569  $this->assertTrue( $info->getUserInfo()->isAnon() );
1570  $this->assertTrue( $info->isIdSafe() );
1571  $this->assertSame( [], $logger->getBuffer() );
1572 
1573  // Unverified user with metadata
1574  $this->store->setSessionMeta( $id, $metadata );
1575  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1576  'provider' => $provider,
1577  'id' => $id,
1578  'userInfo' => $unverifiedUserInfo
1579  ] );
1580  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1581  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1582  $this->assertTrue( $info->getUserInfo()->isVerified() );
1583  $this->assertSame( $unverifiedUserInfo->getId(), $info->getUserInfo()->getId() );
1584  $this->assertSame( $unverifiedUserInfo->getName(), $info->getUserInfo()->getName() );
1585  $this->assertTrue( $info->isIdSafe() );
1586  $this->assertSame( [], $logger->getBuffer() );
1587 
1588  // Unverified user with metadata
1589  $this->store->setSessionMeta( $id, $metadata );
1590  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1591  'provider' => $provider,
1592  'id' => $id,
1593  'userInfo' => $unverifiedUserInfo
1594  ] );
1595  $this->assertFalse( $info->isIdSafe(), 'sanity check' );
1596  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1597  $this->assertTrue( $info->getUserInfo()->isVerified() );
1598  $this->assertSame( $unverifiedUserInfo->getId(), $info->getUserInfo()->getId() );
1599  $this->assertSame( $unverifiedUserInfo->getName(), $info->getUserInfo()->getName() );
1600  $this->assertTrue( $info->isIdSafe() );
1601  $this->assertSame( [], $logger->getBuffer() );
1602 
1603  // Wrong token
1604  $this->store->setSessionMeta( $id, [ 'userToken' => 'Bad' ] + $metadata );
1605  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1606  'provider' => $provider,
1607  'id' => $id,
1608  'userInfo' => $userInfo
1609  ] );
1610  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1611  $this->assertSame( [
1612  [ LogLevel::WARNING, 'Session "{session}": User token mismatch' ],
1613  ], $logger->getBuffer() );
1614  $logger->clearBuffer();
1615 
1616  // Provider metadata
1617  $this->store->setSessionMeta( $id, [ 'provider' => 'Mock2' ] + $metadata );
1618  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1619  'provider' => $provider2,
1620  'id' => $id,
1621  'userInfo' => $userInfo,
1622  'metadata' => [ 'Info' ],
1623  ] );
1624  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1625  $this->assertSame( [ 'Info', 'changed' => true ], $info->getProviderMetadata() );
1626  $this->assertSame( [], $logger->getBuffer() );
1627 
1628  $this->store->setSessionMeta( $id, [ 'providerMetadata' => [ 'Saved' ] ] + $metadata );
1629  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1630  'provider' => $provider,
1631  'id' => $id,
1632  'userInfo' => $userInfo,
1633  ] );
1634  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1635  $this->assertSame( [ 'Saved' ], $info->getProviderMetadata() );
1636  $this->assertSame( [], $logger->getBuffer() );
1637 
1638  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1639  'provider' => $provider,
1640  'id' => $id,
1641  'userInfo' => $userInfo,
1642  'metadata' => [ 'Info' ],
1643  ] );
1644  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1645  $this->assertSame( [ 'Merged' ], $info->getProviderMetadata() );
1646  $this->assertSame( [], $logger->getBuffer() );
1647 
1648  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1649  'provider' => $provider,
1650  'id' => $id,
1651  'userInfo' => $userInfo,
1652  'metadata' => [ 'Throw' ],
1653  ] );
1654  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1655  $this->assertSame( [
1656  [
1657  LogLevel::WARNING,
1658  'Session "{session}": Metadata merge failed: {exception}',
1659  ],
1660  ], $logger->getBuffer() );
1661  $logger->clearBuffer();
1662 
1663  // Remember from session
1664  $this->store->setSessionMeta( $id, $metadata );
1665  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1666  'provider' => $provider,
1667  'id' => $id,
1668  ] );
1669  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1670  $this->assertFalse( $info->wasRemembered() );
1671  $this->assertSame( [], $logger->getBuffer() );
1672 
1673  $this->store->setSessionMeta( $id, [ 'remember' => true ] + $metadata );
1674  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1675  'provider' => $provider,
1676  'id' => $id,
1677  ] );
1678  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1679  $this->assertTrue( $info->wasRemembered() );
1680  $this->assertSame( [], $logger->getBuffer() );
1681 
1682  $this->store->setSessionMeta( $id, [ 'remember' => false ] + $metadata );
1683  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1684  'provider' => $provider,
1685  'id' => $id,
1686  'userInfo' => $userInfo
1687  ] );
1688  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1689  $this->assertTrue( $info->wasRemembered() );
1690  $this->assertSame( [], $logger->getBuffer() );
1691 
1692  // forceHTTPS from session
1693  $this->store->setSessionMeta( $id, $metadata );
1694  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1695  'provider' => $provider,
1696  'id' => $id,
1697  'userInfo' => $userInfo
1698  ] );
1699  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1700  $this->assertFalse( $info->forceHTTPS() );
1701  $this->assertSame( [], $logger->getBuffer() );
1702 
1703  $this->store->setSessionMeta( $id, [ 'forceHTTPS' => true ] + $metadata );
1704  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1705  'provider' => $provider,
1706  'id' => $id,
1707  'userInfo' => $userInfo
1708  ] );
1709  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1710  $this->assertTrue( $info->forceHTTPS() );
1711  $this->assertSame( [], $logger->getBuffer() );
1712 
1713  $this->store->setSessionMeta( $id, [ 'forceHTTPS' => false ] + $metadata );
1714  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1715  'provider' => $provider,
1716  'id' => $id,
1717  'userInfo' => $userInfo,
1718  'forceHTTPS' => true
1719  ] );
1720  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1721  $this->assertTrue( $info->forceHTTPS() );
1722  $this->assertSame( [], $logger->getBuffer() );
1723 
1724  // "Persist" flag from session
1725  $this->store->setSessionMeta( $id, $metadata );
1726  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1727  'provider' => $provider,
1728  'id' => $id,
1729  'userInfo' => $userInfo
1730  ] );
1731  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1732  $this->assertFalse( $info->wasPersisted() );
1733  $this->assertSame( [], $logger->getBuffer() );
1734 
1735  $this->store->setSessionMeta( $id, [ 'persisted' => true ] + $metadata );
1736  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1737  'provider' => $provider,
1738  'id' => $id,
1739  'userInfo' => $userInfo
1740  ] );
1741  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1742  $this->assertTrue( $info->wasPersisted() );
1743  $this->assertSame( [], $logger->getBuffer() );
1744 
1745  $this->store->setSessionMeta( $id, [ 'persisted' => false ] + $metadata );
1746  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1747  'provider' => $provider,
1748  'id' => $id,
1749  'userInfo' => $userInfo,
1750  'persisted' => true
1751  ] );
1752  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1753  $this->assertTrue( $info->wasPersisted() );
1754  $this->assertSame( [], $logger->getBuffer() );
1755 
1756  // Provider refreshSessionInfo() returning false
1757  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1758  'provider' => $provider3,
1759  ] );
1760  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1761  $this->assertSame( [], $logger->getBuffer() );
1762 
1763  // Hook
1764  $called = false;
1765  $data = [ 'foo' => 1 ];
1766  $this->store->setSession( $id, [ 'metadata' => $metadata, 'data' => $data ] );
1767  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1768  'provider' => $provider,
1769  'id' => $id,
1770  'userInfo' => $userInfo
1771  ] );
1772  $this->mergeMwGlobalArrayValue( 'wgHooks', [
1773  'SessionCheckInfo' => [ function ( &$reason, $i, $r, $m, $d ) use (
1774  $info, $metadata, $data, $request, &$called
1775  ) {
1776  $this->assertSame( $info->getId(), $i->getId() );
1777  $this->assertSame( $info->getProvider(), $i->getProvider() );
1778  $this->assertSame( $info->getUserInfo(), $i->getUserInfo() );
1779  $this->assertSame( $request, $r );
1780  $this->assertEquals( $metadata, $m );
1781  $this->assertEquals( $data, $d );
1782  $called = true;
1783  return false;
1784  } ]
1785  ] );
1786  $this->assertFalse( $loadSessionInfoFromStore( $info ) );
1787  $this->assertTrue( $called );
1788  $this->assertSame( [
1789  [ LogLevel::WARNING, 'Session "{session}": Hook aborted' ],
1790  ], $logger->getBuffer() );
1791  $logger->clearBuffer();
1792  $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionCheckInfo' => [] ] );
1793 
1794  // forceUse deletes bad backend data
1795  $this->store->setSessionMeta( $id, [ 'userToken' => 'Bad' ] + $metadata );
1796  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
1797  'provider' => $provider,
1798  'id' => $id,
1799  'userInfo' => $userInfo,
1800  'forceUse' => true,
1801  ] );
1802  $this->assertTrue( $loadSessionInfoFromStore( $info ) );
1803  $this->assertFalse( $this->store->getSession( $id ) );
1804  $this->assertSame( [
1805  [ LogLevel::WARNING, 'Session "{session}": User token mismatch' ],
1806  ], $logger->getBuffer() );
1807  $logger->clearBuffer();
1808  }
1809 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:522
const MIN_PRIORITY
Minimum allowed priority.
Definition: SessionInfo.php:36
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
Authentication plugin interface.
Definition: AuthPlugin.php:38
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
static getLocalClusterInstance()
Get the main cluster-local cache object.
$wgAuth $wgAuth
Authentication plugin.
Session Database MediaWiki\Session\SessionManager.
setName($str)
Set the user name.
Definition: User.php:2166
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
getId()
Return the session ID.
Subclass of UnexpectedValueException that can be annotated with additional data for debug logging...
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2139
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
$wgGroupPermissions
Permission keys given to users in each group.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
static BagOStuff[] $instances
Map of (id => BagOStuff)
Definition: ObjectCache.php:83
BagOStuff with utility functions for MediaWiki\\Session\\* testing.
static getMain()
Static methods.
IContextSource $context
Definition: MediaWiki.php:32
static install(SessionManager $manager)
Install a session handler for the current web request.
$cache
Definition: mcc.php:33
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:2458
getId()
Get the user's ID.
Definition: User.php:2114
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.
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
$wgDisableAuthManager
Disable AuthManager.
static idFromName($name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:764
static consume(ScopedCallback &$sc=null)
Trigger a scoped callback and destroy it.
wfMemcKey()
Make a cache key for the local wiki.
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:776
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