3 namespace MediaWiki\Session
;
11 * @covers MediaWiki\Session\SessionBackend
13 class SessionBackendTest
extends MediaWikiTestCase
{
14 const SESSIONID
= 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
21 protected $onSessionMetadataCalled = false;
24 * Returns a non-persistent backend that thinks it has at least one session active
25 * @param User|null $user
28 protected function getBackend( User
$user = null, $id = null ) {
29 if ( !$this->config
) {
30 $this->config
= new \
HashConfig();
31 $this->manager
= null;
33 if ( !$this->store
) {
34 $this->store
= new TestBagOStuff();
35 $this->manager
= null;
38 $logger = new \Psr\Log\
NullLogger();
39 if ( !$this->manager
) {
40 $this->manager
= new SessionManager( [
41 'store' => $this->store
,
43 'config' => $this->config
,
47 if ( !$this->provider
) {
48 $this->provider
= new \
DummySessionProvider();
50 $this->provider
->setLogger( $logger );
51 $this->provider
->setConfig( $this->config
);
52 $this->provider
->setManager( $this->manager
);
54 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, [
55 'provider' => $this->provider
,
56 'id' => $id ?
: self
::SESSIONID
,
58 'userInfo' => UserInfo
::newFromUser( $user ?
: new User
, true ),
61 $id = new SessionId( $info->getId() );
63 $backend = new SessionBackend( $id, $info, $this->store
, $logger, 10 );
64 $priv = \TestingAccessWrapper
::newFromObject( $backend );
65 $priv->persist
= false;
66 $priv->requests
= [ 100 => new \
FauxRequest() ];
67 $priv->requests
[100]->setSessionId( $id );
68 $priv->usePhpSessionHandling
= false;
70 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
71 $manager->allSessionBackends
= [ $backend->getId() => $backend ] +
$manager->allSessionBackends
;
72 $manager->allSessionIds
= [ $backend->getId() => $id ] +
$manager->allSessionIds
;
73 $manager->sessionProviders
= [ (string)$this->provider
=> $this->provider
];
78 public function testConstructor() {
82 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, [
83 'provider' => $this->provider
,
84 'id' => self
::SESSIONID
,
86 'userInfo' => UserInfo
::newFromName( 'UTSysop', false ),
89 $id = new SessionId( $info->getId() );
90 $logger = new \Psr\Log\
NullLogger();
92 new SessionBackend( $id, $info, $this->store
, $logger, 10 );
93 $this->fail( 'Expected exception not thrown' );
94 } catch ( \InvalidArgumentException
$ex ) {
96 "Refusing to create session for unverified user {$info->getUserInfo()}",
101 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, [
102 'id' => self
::SESSIONID
,
103 'userInfo' => UserInfo
::newFromName( 'UTSysop', true ),
106 $id = new SessionId( $info->getId() );
108 new SessionBackend( $id, $info, $this->store
, $logger, 10 );
109 $this->fail( 'Expected exception not thrown' );
110 } catch ( \InvalidArgumentException
$ex ) {
111 $this->assertSame( 'Cannot create session without a provider', $ex->getMessage() );
114 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, [
115 'provider' => $this->provider
,
116 'id' => self
::SESSIONID
,
118 'userInfo' => UserInfo
::newFromName( 'UTSysop', true ),
121 $id = new SessionId( '!' . $info->getId() );
123 new SessionBackend( $id, $info, $this->store
, $logger, 10 );
124 $this->fail( 'Expected exception not thrown' );
125 } catch ( \InvalidArgumentException
$ex ) {
127 'SessionId and SessionInfo don\'t match',
132 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, [
133 'provider' => $this->provider
,
134 'id' => self
::SESSIONID
,
136 'userInfo' => UserInfo
::newFromName( 'UTSysop', true ),
139 $id = new SessionId( $info->getId() );
140 $backend = new SessionBackend( $id, $info, $this->store
, $logger, 10 );
141 $this->assertSame( self
::SESSIONID
, $backend->getId() );
142 $this->assertSame( $id, $backend->getSessionId() );
143 $this->assertSame( $this->provider
, $backend->getProvider() );
144 $this->assertInstanceOf( 'User', $backend->getUser() );
145 $this->assertSame( 'UTSysop', $backend->getUser()->getName() );
146 $this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
147 $this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
148 $this->assertSame( $info->forceHTTPS(), $backend->shouldForceHTTPS() );
150 $expire = time() +
100;
151 $this->store
->setSessionMeta( self
::SESSIONID
, [ 'expires' => $expire ], 2 );
153 $info = new SessionInfo( SessionInfo
::MIN_PRIORITY
, [
154 'provider' => $this->provider
,
155 'id' => self
::SESSIONID
,
157 'forceHTTPS' => true,
158 'metadata' => [ 'foo' ],
161 $id = new SessionId( $info->getId() );
162 $backend = new SessionBackend( $id, $info, $this->store
, $logger, 10 );
163 $this->assertSame( self
::SESSIONID
, $backend->getId() );
164 $this->assertSame( $id, $backend->getSessionId() );
165 $this->assertSame( $this->provider
, $backend->getProvider() );
166 $this->assertInstanceOf( 'User', $backend->getUser() );
167 $this->assertTrue( $backend->getUser()->isAnon() );
168 $this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
169 $this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
170 $this->assertSame( $info->forceHTTPS(), $backend->shouldForceHTTPS() );
171 $this->assertSame( $expire, \TestingAccessWrapper
::newFromObject( $backend )->expires
);
172 $this->assertSame( [ 'foo' ], $backend->getProviderMetadata() );
175 public function testSessionStuff() {
176 $backend = $this->getBackend();
177 $priv = \TestingAccessWrapper
::newFromObject( $backend );
178 $priv->requests
= []; // Remove dummy session
180 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
182 $request1 = new \
FauxRequest();
183 $session1 = $backend->getSession( $request1 );
184 $request2 = new \
FauxRequest();
185 $session2 = $backend->getSession( $request2 );
187 $this->assertInstanceOf( Session
::class, $session1 );
188 $this->assertInstanceOf( Session
::class, $session2 );
189 $this->assertSame( 2, count( $priv->requests
) );
191 $index = \TestingAccessWrapper
::newFromObject( $session1 )->index
;
193 $this->assertSame( $request1, $backend->getRequest( $index ) );
194 $this->assertSame( null, $backend->suggestLoginUsername( $index ) );
195 $request1->setCookie( 'UserName', 'Example' );
196 $this->assertSame( 'Example', $backend->suggestLoginUsername( $index ) );
199 $this->assertSame( 1, count( $priv->requests
) );
200 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionBackends
);
201 $this->assertSame( $backend, $manager->allSessionBackends
[$backend->getId()] );
203 $backend->getRequest( $index );
204 $this->fail( 'Expected exception not thrown' );
205 } catch ( \InvalidArgumentException
$ex ) {
206 $this->assertSame( 'Invalid session index', $ex->getMessage() );
209 $backend->suggestLoginUsername( $index );
210 $this->fail( 'Expected exception not thrown' );
211 } catch ( \InvalidArgumentException
$ex ) {
212 $this->assertSame( 'Invalid session index', $ex->getMessage() );
216 $this->assertSame( 0, count( $priv->requests
) );
217 $this->assertArrayNotHasKey( $backend->getId(), $manager->allSessionBackends
);
218 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionIds
);
221 public function testSetProviderMetadata() {
222 $backend = $this->getBackend();
223 $priv = \TestingAccessWrapper
::newFromObject( $backend );
224 $priv->providerMetadata
= [ 'dummy' ];
227 $backend->setProviderMetadata( 'foo' );
228 $this->fail( 'Expected exception not thrown' );
229 } catch ( \InvalidArgumentException
$ex ) {
230 $this->assertSame( '$metadata must be an array or null', $ex->getMessage() );
234 $backend->setProviderMetadata( (object)[] );
235 $this->fail( 'Expected exception not thrown' );
236 } catch ( \InvalidArgumentException
$ex ) {
237 $this->assertSame( '$metadata must be an array or null', $ex->getMessage() );
240 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'sanity check' );
241 $backend->setProviderMetadata( [ 'dummy' ] );
242 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
) );
244 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'sanity check' );
245 $backend->setProviderMetadata( [ 'test' ] );
246 $this->assertNotFalse( $this->store
->getSession( self
::SESSIONID
) );
247 $this->assertSame( [ 'test' ], $backend->getProviderMetadata() );
248 $this->store
->deleteSession( self
::SESSIONID
);
250 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'sanity check' );
251 $backend->setProviderMetadata( null );
252 $this->assertNotFalse( $this->store
->getSession( self
::SESSIONID
) );
253 $this->assertSame( null, $backend->getProviderMetadata() );
254 $this->store
->deleteSession( self
::SESSIONID
);
257 public function testResetId() {
260 $builder = $this->getMockBuilder( 'DummySessionProvider' )
261 ->setMethods( [ 'persistsSessionId', 'sessionIdWasReset' ] );
263 $this->provider
= $builder->getMock();
264 $this->provider
->expects( $this->any() )->method( 'persistsSessionId' )
265 ->will( $this->returnValue( false ) );
266 $this->provider
->expects( $this->never() )->method( 'sessionIdWasReset' );
267 $backend = $this->getBackend( User
::newFromName( 'UTSysop' ) );
268 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
269 $sessionId = $backend->getSessionId();
271 $this->assertSame( self
::SESSIONID
, $backend->getId() );
272 $this->assertSame( $backend->getId(), $sessionId->getId() );
273 $this->assertSame( $id, session_id() );
274 $this->assertSame( $backend, $manager->allSessionBackends
[self
::SESSIONID
] );
276 $this->provider
= $builder->getMock();
277 $this->provider
->expects( $this->any() )->method( 'persistsSessionId' )
278 ->will( $this->returnValue( true ) );
279 $backend = $this->getBackend();
280 $this->provider
->expects( $this->once() )->method( 'sessionIdWasReset' )
281 ->with( $this->identicalTo( $backend ), $this->identicalTo( self
::SESSIONID
) );
282 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
283 $sessionId = $backend->getSessionId();
285 $this->assertNotEquals( self
::SESSIONID
, $backend->getId() );
286 $this->assertSame( $backend->getId(), $sessionId->getId() );
287 $this->assertInternalType( 'array', $this->store
->getSession( $backend->getId() ) );
288 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
) );
289 $this->assertSame( $id, session_id() );
290 $this->assertArrayNotHasKey( self
::SESSIONID
, $manager->allSessionBackends
);
291 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionBackends
);
292 $this->assertSame( $backend, $manager->allSessionBackends
[$backend->getId()] );
295 public function testPersist() {
296 $this->provider
= $this->getMockBuilder( 'DummySessionProvider' )
297 ->setMethods( [ 'persistSession' ] )->getMock();
298 $this->provider
->expects( $this->once() )->method( 'persistSession' );
299 $backend = $this->getBackend();
300 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
301 $backend->save(); // This one shouldn't call $provider->persistSession()
304 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
306 $this->provider
= null;
307 $backend = $this->getBackend();
308 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
309 $wrap->persist
= true;
312 $this->assertNotEquals( 0, $wrap->expires
);
315 public function testUnpersist() {
316 $this->provider
= $this->getMockBuilder( 'DummySessionProvider' )
317 ->setMethods( [ 'unpersistSession' ] )->getMock();
318 $this->provider
->expects( $this->once() )->method( 'unpersistSession' );
319 $backend = $this->getBackend();
320 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
321 $wrap->store
= new \
CachedBagOStuff( $this->store
);
322 $wrap->persist
= true;
323 $wrap->dataDirty
= true;
325 $backend->save(); // This one shouldn't call $provider->persistSession(), but should save
326 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
327 $this->assertNotFalse( $this->store
->getSession( self
::SESSIONID
), 'sanity check' );
329 $backend->unpersist();
330 $this->assertFalse( $backend->isPersistent() );
331 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
) );
332 $this->assertNotFalse( $wrap->store
->get( wfMemcKey( 'MWSession', self
::SESSIONID
) ) );
335 public function testRememberUser() {
336 $backend = $this->getBackend();
338 $remembered = $backend->shouldRememberUser();
339 $backend->setRememberUser( !$remembered );
340 $this->assertNotEquals( $remembered, $backend->shouldRememberUser() );
341 $backend->setRememberUser( $remembered );
342 $this->assertEquals( $remembered, $backend->shouldRememberUser() );
345 public function testForceHTTPS() {
346 $backend = $this->getBackend();
348 $force = $backend->shouldForceHTTPS();
349 $backend->setForceHTTPS( !$force );
350 $this->assertNotEquals( $force, $backend->shouldForceHTTPS() );
351 $backend->setForceHTTPS( $force );
352 $this->assertEquals( $force, $backend->shouldForceHTTPS() );
355 public function testLoggedOutTimestamp() {
356 $backend = $this->getBackend();
358 $backend->setLoggedOutTimestamp( 42 );
359 $this->assertSame( 42, $backend->getLoggedOutTimestamp() );
360 $backend->setLoggedOutTimestamp( '123' );
361 $this->assertSame( 123, $backend->getLoggedOutTimestamp() );
364 public function testSetUser() {
365 $user = static::getTestSysop()->getUser();
367 $this->provider
= $this->getMockBuilder( 'DummySessionProvider' )
368 ->setMethods( [ 'canChangeUser' ] )->getMock();
369 $this->provider
->expects( $this->any() )->method( 'canChangeUser' )
370 ->will( $this->returnValue( false ) );
371 $backend = $this->getBackend();
372 $this->assertFalse( $backend->canSetUser() );
374 $backend->setUser( $user );
375 $this->fail( 'Expected exception not thrown' );
376 } catch ( \BadMethodCallException
$ex ) {
378 'Cannot set user on this session; check $session->canSetUser() first',
382 $this->assertNotSame( $user, $backend->getUser() );
384 $this->provider
= null;
385 $backend = $this->getBackend();
386 $this->assertTrue( $backend->canSetUser() );
387 $this->assertNotSame( $user, $backend->getUser(), 'sanity check' );
388 $backend->setUser( $user );
389 $this->assertSame( $user, $backend->getUser() );
392 public function testDirty() {
393 $backend = $this->getBackend();
394 $priv = \TestingAccessWrapper
::newFromObject( $backend );
395 $priv->dataDirty
= false;
397 $this->assertTrue( $priv->dataDirty
);
400 public function testGetData() {
401 $backend = $this->getBackend();
402 $data = $backend->getData();
403 $this->assertSame( [], $data );
404 $this->assertTrue( \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
);
405 $data['???'] = '!!!';
406 $this->assertSame( [ '???' => '!!!' ], $data );
408 $testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
409 $this->store
->setSessionData( self
::SESSIONID
, $testData );
410 $backend = $this->getBackend();
411 $this->assertSame( $testData, $backend->getData() );
412 $this->assertFalse( \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
);
415 public function testAddData() {
416 $backend = $this->getBackend();
417 $priv = \TestingAccessWrapper
::newFromObject( $backend );
419 $priv->data
= [ 'foo' => 1 ];
420 $priv->dataDirty
= false;
421 $backend->addData( [ 'foo' => 1 ] );
422 $this->assertSame( [ 'foo' => 1 ], $priv->data
);
423 $this->assertFalse( $priv->dataDirty
);
425 $priv->data
= [ 'foo' => 1 ];
426 $priv->dataDirty
= false;
427 $backend->addData( [ 'foo' => '1' ] );
428 $this->assertSame( [ 'foo' => '1' ], $priv->data
);
429 $this->assertTrue( $priv->dataDirty
);
431 $priv->data
= [ 'foo' => 1 ];
432 $priv->dataDirty
= false;
433 $backend->addData( [ 'bar' => 2 ] );
434 $this->assertSame( [ 'foo' => 1, 'bar' => 2 ], $priv->data
);
435 $this->assertTrue( $priv->dataDirty
);
438 public function testDelaySave() {
439 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
440 $backend = $this->getBackend();
441 $priv = \TestingAccessWrapper
::newFromObject( $backend );
442 $priv->persist
= true;
444 // Saves happen normally when no delay is in effect
445 $this->onSessionMetadataCalled
= false;
446 $priv->metaDirty
= true;
448 $this->assertTrue( $this->onSessionMetadataCalled
, 'sanity check' );
450 $this->onSessionMetadataCalled
= false;
451 $priv->metaDirty
= true;
453 $this->assertTrue( $this->onSessionMetadataCalled
, 'sanity check' );
455 $delay = $backend->delaySave();
457 // Autosave doesn't happen when no delay is in effect
458 $this->onSessionMetadataCalled
= false;
459 $priv->metaDirty
= true;
461 $this->assertFalse( $this->onSessionMetadataCalled
);
463 // Save still does happen when no delay is in effect
465 $this->assertTrue( $this->onSessionMetadataCalled
);
467 // Save happens when delay is consumed
468 $this->onSessionMetadataCalled
= false;
469 $priv->metaDirty
= true;
470 \Wikimedia\ScopedCallback
::consume( $delay );
471 $this->assertTrue( $this->onSessionMetadataCalled
);
473 // Test multiple delays
474 $delay1 = $backend->delaySave();
475 $delay2 = $backend->delaySave();
476 $delay3 = $backend->delaySave();
477 $this->onSessionMetadataCalled
= false;
478 $priv->metaDirty
= true;
480 $this->assertFalse( $this->onSessionMetadataCalled
);
481 \Wikimedia\ScopedCallback
::consume( $delay3 );
482 $this->assertFalse( $this->onSessionMetadataCalled
);
483 \Wikimedia\ScopedCallback
::consume( $delay1 );
484 $this->assertFalse( $this->onSessionMetadataCalled
);
485 \Wikimedia\ScopedCallback
::consume( $delay2 );
486 $this->assertTrue( $this->onSessionMetadataCalled
);
489 public function testSave() {
490 $user = static::getTestSysop()->getUser();
491 $this->store
= new TestBagOStuff();
492 $testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
494 $neverHook = $this->getMockBuilder( __CLASS__
)
495 ->setMethods( [ 'onSessionMetadata' ] )->getMock();
496 $neverHook->expects( $this->never() )->method( 'onSessionMetadata' );
498 $builder = $this->getMockBuilder( 'DummySessionProvider' )
499 ->setMethods( [ 'persistSession', 'unpersistSession' ] );
501 $neverProvider = $builder->getMock();
502 $neverProvider->expects( $this->never() )->method( 'persistSession' );
503 $neverProvider->expects( $this->never() )->method( 'unpersistSession' );
505 // Not persistent or dirty
506 $this->provider
= $neverProvider;
507 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
508 $this->store
->setSessionData( self
::SESSIONID
, $testData );
509 $backend = $this->getBackend( $user );
510 $this->store
->deleteSession( self
::SESSIONID
);
511 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
512 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
513 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
515 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
517 // (but does unpersist if forced)
518 $this->provider
= $builder->getMock();
519 $this->provider
->expects( $this->never() )->method( 'persistSession' );
520 $this->provider
->expects( $this->atLeastOnce() )->method( 'unpersistSession' );
521 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
522 $this->store
->setSessionData( self
::SESSIONID
, $testData );
523 $backend = $this->getBackend( $user );
524 $this->store
->deleteSession( self
::SESSIONID
);
525 \TestingAccessWrapper
::newFromObject( $backend )->persist
= false;
526 \TestingAccessWrapper
::newFromObject( $backend )->forcePersist
= true;
527 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
528 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
529 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
531 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
533 // (but not to a WebRequest associated with a different session)
534 $this->provider
= $neverProvider;
535 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
536 $this->store
->setSessionData( self
::SESSIONID
, $testData );
537 $backend = $this->getBackend( $user );
538 \TestingAccessWrapper
::newFromObject( $backend )->requests
[100]
539 ->setSessionId( new SessionId( 'x' ) );
540 $this->store
->deleteSession( self
::SESSIONID
);
541 \TestingAccessWrapper
::newFromObject( $backend )->persist
= false;
542 \TestingAccessWrapper
::newFromObject( $backend )->forcePersist
= true;
543 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
544 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
545 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
547 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
549 // Not persistent, but dirty
550 $this->provider
= $neverProvider;
551 $this->onSessionMetadataCalled
= false;
552 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
553 $this->store
->setSessionData( self
::SESSIONID
, $testData );
554 $backend = $this->getBackend( $user );
555 $this->store
->deleteSession( self
::SESSIONID
);
556 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
557 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
558 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= true;
560 $this->assertTrue( $this->onSessionMetadataCalled
);
561 $blob = $this->store
->getSession( self
::SESSIONID
);
562 $this->assertInternalType( 'array', $blob );
563 $this->assertArrayHasKey( 'metadata', $blob );
564 $metadata = $blob['metadata'];
565 $this->assertInternalType( 'array', $metadata );
566 $this->assertArrayHasKey( '???', $metadata );
567 $this->assertSame( '!!!', $metadata['???'] );
568 $this->assertFalse( $this->store
->getSessionFromBackend( self
::SESSIONID
),
569 'making sure it didn\'t save to backend' );
571 // Persistent, not dirty
572 $this->provider
= $neverProvider;
573 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
574 $this->store
->setSessionData( self
::SESSIONID
, $testData );
575 $backend = $this->getBackend( $user );
576 $this->store
->deleteSession( self
::SESSIONID
);
577 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
578 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
579 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
580 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
582 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
584 // (but will persist if forced)
585 $this->provider
= $builder->getMock();
586 $this->provider
->expects( $this->atLeastOnce() )->method( 'persistSession' );
587 $this->provider
->expects( $this->never() )->method( 'unpersistSession' );
588 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
589 $this->store
->setSessionData( self
::SESSIONID
, $testData );
590 $backend = $this->getBackend( $user );
591 $this->store
->deleteSession( self
::SESSIONID
);
592 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
593 \TestingAccessWrapper
::newFromObject( $backend )->forcePersist
= true;
594 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
595 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
596 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
598 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
600 // Persistent and dirty
601 $this->provider
= $neverProvider;
602 $this->onSessionMetadataCalled
= false;
603 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
604 $this->store
->setSessionData( self
::SESSIONID
, $testData );
605 $backend = $this->getBackend( $user );
606 $this->store
->deleteSession( self
::SESSIONID
);
607 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
608 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
609 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
610 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= true;
612 $this->assertTrue( $this->onSessionMetadataCalled
);
613 $blob = $this->store
->getSession( self
::SESSIONID
);
614 $this->assertInternalType( 'array', $blob );
615 $this->assertArrayHasKey( 'metadata', $blob );
616 $metadata = $blob['metadata'];
617 $this->assertInternalType( 'array', $metadata );
618 $this->assertArrayHasKey( '???', $metadata );
619 $this->assertSame( '!!!', $metadata['???'] );
620 $this->assertNotSame( false, $this->store
->getSessionFromBackend( self
::SESSIONID
),
621 'making sure it did save to backend' );
623 // (also persists if forced)
624 $this->provider
= $builder->getMock();
625 $this->provider
->expects( $this->atLeastOnce() )->method( 'persistSession' );
626 $this->provider
->expects( $this->never() )->method( 'unpersistSession' );
627 $this->onSessionMetadataCalled
= false;
628 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
629 $this->store
->setSessionData( self
::SESSIONID
, $testData );
630 $backend = $this->getBackend( $user );
631 $this->store
->deleteSession( self
::SESSIONID
);
632 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
633 \TestingAccessWrapper
::newFromObject( $backend )->forcePersist
= true;
634 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
635 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
636 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= true;
638 $this->assertTrue( $this->onSessionMetadataCalled
);
639 $blob = $this->store
->getSession( self
::SESSIONID
);
640 $this->assertInternalType( 'array', $blob );
641 $this->assertArrayHasKey( 'metadata', $blob );
642 $metadata = $blob['metadata'];
643 $this->assertInternalType( 'array', $metadata );
644 $this->assertArrayHasKey( '???', $metadata );
645 $this->assertSame( '!!!', $metadata['???'] );
646 $this->assertNotSame( false, $this->store
->getSessionFromBackend( self
::SESSIONID
),
647 'making sure it did save to backend' );
649 // (also persists if metadata dirty)
650 $this->provider
= $builder->getMock();
651 $this->provider
->expects( $this->atLeastOnce() )->method( 'persistSession' );
652 $this->provider
->expects( $this->never() )->method( 'unpersistSession' );
653 $this->onSessionMetadataCalled
= false;
654 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
655 $this->store
->setSessionData( self
::SESSIONID
, $testData );
656 $backend = $this->getBackend( $user );
657 $this->store
->deleteSession( self
::SESSIONID
);
658 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
659 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
660 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= true;
661 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
663 $this->assertTrue( $this->onSessionMetadataCalled
);
664 $blob = $this->store
->getSession( self
::SESSIONID
);
665 $this->assertInternalType( 'array', $blob );
666 $this->assertArrayHasKey( 'metadata', $blob );
667 $metadata = $blob['metadata'];
668 $this->assertInternalType( 'array', $metadata );
669 $this->assertArrayHasKey( '???', $metadata );
670 $this->assertSame( '!!!', $metadata['???'] );
671 $this->assertNotSame( false, $this->store
->getSessionFromBackend( self
::SESSIONID
),
672 'making sure it did save to backend' );
674 // Not marked dirty, but dirty data
675 // (e.g. indirect modification from ArrayAccess::offsetGet)
676 $this->provider
= $neverProvider;
677 $this->onSessionMetadataCalled
= false;
678 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
679 $this->store
->setSessionData( self
::SESSIONID
, $testData );
680 $backend = $this->getBackend( $user );
681 $this->store
->deleteSession( self
::SESSIONID
);
682 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
683 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
684 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= false;
685 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= false;
686 \TestingAccessWrapper
::newFromObject( $backend )->dataHash
= 'Doesn\'t match';
688 $this->assertTrue( $this->onSessionMetadataCalled
);
689 $blob = $this->store
->getSession( self
::SESSIONID
);
690 $this->assertInternalType( 'array', $blob );
691 $this->assertArrayHasKey( 'metadata', $blob );
692 $metadata = $blob['metadata'];
693 $this->assertInternalType( 'array', $metadata );
694 $this->assertArrayHasKey( '???', $metadata );
695 $this->assertSame( '!!!', $metadata['???'] );
696 $this->assertNotSame( false, $this->store
->getSessionFromBackend( self
::SESSIONID
),
697 'making sure it did save to backend' );
700 $this->provider
= null;
701 $mockHook = $this->getMockBuilder( __CLASS__
)
702 ->setMethods( [ 'onSessionMetadata' ] )->getMock();
703 $mockHook->expects( $this->any() )->method( 'onSessionMetadata' )
704 ->will( $this->returnCallback(
705 function ( SessionBackend
$backend, array &$metadata, array $requests ) {
706 $metadata['userId']++
;
709 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $mockHook ] ] );
710 $this->store
->setSessionData( self
::SESSIONID
, $testData );
711 $backend = $this->getBackend( $user );
715 $this->fail( 'Expected exception not thrown' );
716 } catch ( \UnexpectedValueException
$ex ) {
718 'SessionMetadata hook changed metadata key "userId"',
723 // SessionManager::preventSessionsForUser
724 \TestingAccessWrapper
::newFromObject( $this->manager
)->preventUsers
= [
725 $user->getName() => true,
727 $this->provider
= $neverProvider;
728 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
729 $this->store
->setSessionData( self
::SESSIONID
, $testData );
730 $backend = $this->getBackend( $user );
731 $this->store
->deleteSession( self
::SESSIONID
);
732 \TestingAccessWrapper
::newFromObject( $backend )->persist
= true;
733 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
734 \TestingAccessWrapper
::newFromObject( $backend )->metaDirty
= true;
735 \TestingAccessWrapper
::newFromObject( $backend )->dataDirty
= true;
737 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
740 public function testRenew() {
741 $user = static::getTestSysop()->getUser();
742 $this->store
= new TestBagOStuff();
743 $testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
746 $this->provider
= $this->getMockBuilder( 'DummySessionProvider' )
747 ->setMethods( [ 'persistSession' ] )->getMock();
748 $this->provider
->expects( $this->never() )->method( 'persistSession' );
749 $this->onSessionMetadataCalled
= false;
750 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
751 $this->store
->setSessionData( self
::SESSIONID
, $testData );
752 $backend = $this->getBackend( $user );
753 $this->store
->deleteSession( self
::SESSIONID
);
754 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
755 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
756 $wrap->metaDirty
= false;
757 $wrap->dataDirty
= false;
758 $wrap->forcePersist
= false;
761 $this->assertTrue( $this->onSessionMetadataCalled
);
762 $blob = $this->store
->getSession( self
::SESSIONID
);
763 $this->assertInternalType( 'array', $blob );
764 $this->assertArrayHasKey( 'metadata', $blob );
765 $metadata = $blob['metadata'];
766 $this->assertInternalType( 'array', $metadata );
767 $this->assertArrayHasKey( '???', $metadata );
768 $this->assertSame( '!!!', $metadata['???'] );
769 $this->assertNotEquals( 0, $wrap->expires
);
772 $this->provider
= $this->getMockBuilder( 'DummySessionProvider' )
773 ->setMethods( [ 'persistSession' ] )->getMock();
774 $this->provider
->expects( $this->atLeastOnce() )->method( 'persistSession' );
775 $this->onSessionMetadataCalled
= false;
776 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
777 $this->store
->setSessionData( self
::SESSIONID
, $testData );
778 $backend = $this->getBackend( $user );
779 $this->store
->deleteSession( self
::SESSIONID
);
780 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
781 $wrap->persist
= true;
782 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
783 $wrap->metaDirty
= false;
784 $wrap->dataDirty
= false;
785 $wrap->forcePersist
= false;
788 $this->assertTrue( $this->onSessionMetadataCalled
);
789 $blob = $this->store
->getSession( self
::SESSIONID
);
790 $this->assertInternalType( 'array', $blob );
791 $this->assertArrayHasKey( 'metadata', $blob );
792 $metadata = $blob['metadata'];
793 $this->assertInternalType( 'array', $metadata );
794 $this->assertArrayHasKey( '???', $metadata );
795 $this->assertSame( '!!!', $metadata['???'] );
796 $this->assertNotEquals( 0, $wrap->expires
);
798 // Not persistent, not expiring
799 $this->provider
= $this->getMockBuilder( 'DummySessionProvider' )
800 ->setMethods( [ 'persistSession' ] )->getMock();
801 $this->provider
->expects( $this->never() )->method( 'persistSession' );
802 $this->onSessionMetadataCalled
= false;
803 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
804 $this->store
->setSessionData( self
::SESSIONID
, $testData );
805 $backend = $this->getBackend( $user );
806 $this->store
->deleteSession( self
::SESSIONID
);
807 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
808 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
809 $wrap->metaDirty
= false;
810 $wrap->dataDirty
= false;
811 $wrap->forcePersist
= false;
812 $expires = time() +
$wrap->lifetime +
100;
813 $wrap->expires
= $expires;
815 $this->assertFalse( $this->onSessionMetadataCalled
);
816 $this->assertFalse( $this->store
->getSession( self
::SESSIONID
), 'making sure it didn\'t save' );
817 $this->assertEquals( $expires, $wrap->expires
);
820 public function onSessionMetadata( SessionBackend
$backend, array &$metadata, array $requests ) {
821 $this->onSessionMetadataCalled
= true;
822 $metadata['???'] = '!!!';
825 public function testTakeOverGlobalSession() {
826 if ( !PHPSessionHandler
::isInstalled() ) {
827 PHPSessionHandler
::install( SessionManager
::singleton() );
829 if ( !PHPSessionHandler
::isEnabled() ) {
830 $rProp = new \
ReflectionProperty( PHPSessionHandler
::class, 'instance' );
831 $rProp->setAccessible( true );
832 $handler = \TestingAccessWrapper
::newFromObject( $rProp->getValue() );
833 $resetHandler = new \Wikimedia\
ScopedCallback( function () use ( $handler ) {
834 session_write_close();
835 $handler->enable
= false;
837 $handler->enable
= true;
840 $backend = $this->getBackend( static::getTestSysop()->getUser() );
841 \TestingAccessWrapper
::newFromObject( $backend )->usePhpSessionHandling
= true;
843 $resetSingleton = TestUtils
::setSessionManagerSingleton( $this->manager
);
845 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
846 $request = \RequestContext
::getMain()->getRequest();
847 $manager->globalSession
= $backend->getSession( $request );
848 $manager->globalSessionRequest
= $request;
851 \TestingAccessWrapper
::newFromObject( $backend )->checkPHPSession();
852 $this->assertSame( $backend->getId(), session_id() );
853 session_write_close();
855 $backend2 = $this->getBackend(
856 User
::newFromName( 'UTSysop' ), 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
858 \TestingAccessWrapper
::newFromObject( $backend2 )->usePhpSessionHandling
= true;
861 \TestingAccessWrapper
::newFromObject( $backend2 )->checkPHPSession();
862 $this->assertSame( '', session_id() );
865 public function testResetIdOfGlobalSession() {
866 if ( !PHPSessionHandler
::isInstalled() ) {
867 PHPSessionHandler
::install( SessionManager
::singleton() );
869 if ( !PHPSessionHandler
::isEnabled() ) {
870 $rProp = new \
ReflectionProperty( PHPSessionHandler
::class, 'instance' );
871 $rProp->setAccessible( true );
872 $handler = \TestingAccessWrapper
::newFromObject( $rProp->getValue() );
873 $resetHandler = new \Wikimedia\
ScopedCallback( function () use ( $handler ) {
874 session_write_close();
875 $handler->enable
= false;
877 $handler->enable
= true;
880 $backend = $this->getBackend( User
::newFromName( 'UTSysop' ) );
881 \TestingAccessWrapper
::newFromObject( $backend )->usePhpSessionHandling
= true;
883 $resetSingleton = TestUtils
::setSessionManagerSingleton( $this->manager
);
885 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
886 $request = \RequestContext
::getMain()->getRequest();
887 $manager->globalSession
= $backend->getSession( $request );
888 $manager->globalSessionRequest
= $request;
890 session_id( self
::SESSIONID
);
891 \MediaWiki\
quietCall( 'session_start' );
892 $_SESSION['foo'] = __METHOD__
;
894 $this->assertNotEquals( self
::SESSIONID
, $backend->getId() );
895 $this->assertSame( $backend->getId(), session_id() );
896 $this->assertArrayHasKey( 'foo', $_SESSION );
897 $this->assertSame( __METHOD__
, $_SESSION['foo'] );
898 session_write_close();
901 public function testUnpersistOfGlobalSession() {
902 if ( !PHPSessionHandler
::isInstalled() ) {
903 PHPSessionHandler
::install( SessionManager
::singleton() );
905 if ( !PHPSessionHandler
::isEnabled() ) {
906 $rProp = new \
ReflectionProperty( PHPSessionHandler
::class, 'instance' );
907 $rProp->setAccessible( true );
908 $handler = \TestingAccessWrapper
::newFromObject( $rProp->getValue() );
909 $resetHandler = new \Wikimedia\
ScopedCallback( function () use ( $handler ) {
910 session_write_close();
911 $handler->enable
= false;
913 $handler->enable
= true;
916 $backend = $this->getBackend( User
::newFromName( 'UTSysop' ) );
917 $wrap = \TestingAccessWrapper
::newFromObject( $backend );
918 $wrap->usePhpSessionHandling
= true;
919 $wrap->persist
= true;
921 $resetSingleton = TestUtils
::setSessionManagerSingleton( $this->manager
);
923 $manager = \TestingAccessWrapper
::newFromObject( $this->manager
);
924 $request = \RequestContext
::getMain()->getRequest();
925 $manager->globalSession
= $backend->getSession( $request );
926 $manager->globalSessionRequest
= $request;
928 session_id( self
::SESSIONID
. 'x' );
929 \MediaWiki\
quietCall( 'session_start' );
930 $backend->unpersist();
931 $this->assertSame( self
::SESSIONID
. 'x', session_id() );
933 session_id( self
::SESSIONID
);
934 $wrap->persist
= true;
935 $backend->unpersist();
936 $this->assertSame( '', session_id() );
939 public function testGetAllowedUserRights() {
940 $this->provider
= $this->getMockBuilder( 'DummySessionProvider' )
941 ->setMethods( [ 'getAllowedUserRights' ] )
943 $this->provider
->expects( $this->any() )->method( 'getAllowedUserRights' )
944 ->will( $this->returnValue( [ 'foo', 'bar' ] ) );
946 $backend = $this->getBackend();
947 $this->assertSame( [ 'foo', 'bar' ], $backend->getAllowedUserRights() );