MediaWiki REL1_33
SessionBackendTest.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Session;
4
8use Wikimedia\TestingAccessWrapper;
9
16 const SESSIONID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
17
19 protected $manager;
20
22 protected $config;
23
25 protected $provider;
26
28 protected $store;
29
30 protected $onSessionMetadataCalled = false;
31
38 protected function getBackend( User $user = null, $id = null ) {
39 if ( !$this->config ) {
40 $this->config = new \HashConfig();
41 $this->manager = null;
42 }
43 if ( !$this->store ) {
44 $this->store = new TestBagOStuff();
45 $this->manager = null;
46 }
47
48 $logger = new \Psr\Log\NullLogger();
49 if ( !$this->manager ) {
50 $this->manager = new SessionManager( [
51 'store' => $this->store,
52 'logger' => $logger,
53 'config' => $this->config,
54 ] );
55 }
56
57 if ( !$this->provider ) {
58 $this->provider = new \DummySessionProvider();
59 }
60 $this->provider->setLogger( $logger );
61 $this->provider->setConfig( $this->config );
62 $this->provider->setManager( $this->manager );
63
65 'provider' => $this->provider,
66 'id' => $id ?: self::SESSIONID,
67 'persisted' => true,
68 'userInfo' => UserInfo::newFromUser( $user ?: new User, true ),
69 'idIsSafe' => true,
70 ] );
71 $id = new SessionId( $info->getId() );
72
73 $backend = new SessionBackend( $id, $info, $this->store, $logger, 10 );
74 $priv = TestingAccessWrapper::newFromObject( $backend );
75 $priv->persist = false;
76 $priv->requests = [ 100 => new \FauxRequest() ];
77 $priv->requests[100]->setSessionId( $id );
78 $priv->usePhpSessionHandling = false;
79
80 $manager = TestingAccessWrapper::newFromObject( $this->manager );
81 $manager->allSessionBackends = [ $backend->getId() => $backend ] + $manager->allSessionBackends;
82 $manager->allSessionIds = [ $backend->getId() => $id ] + $manager->allSessionIds;
83 $manager->sessionProviders = [ (string)$this->provider => $this->provider ];
84
85 return $backend;
86 }
87
88 public function testConstructor() {
89 // Set variables
90 $this->getBackend();
91
93 'provider' => $this->provider,
94 'id' => self::SESSIONID,
95 'persisted' => true,
96 'userInfo' => UserInfo::newFromName( 'UTSysop', false ),
97 'idIsSafe' => true,
98 ] );
99 $id = new SessionId( $info->getId() );
100 $logger = new \Psr\Log\NullLogger();
101 try {
102 new SessionBackend( $id, $info, $this->store, $logger, 10 );
103 $this->fail( 'Expected exception not thrown' );
104 } catch ( \InvalidArgumentException $ex ) {
105 $this->assertSame(
106 "Refusing to create session for unverified user {$info->getUserInfo()}",
107 $ex->getMessage()
108 );
109 }
110
112 'id' => self::SESSIONID,
113 'userInfo' => UserInfo::newFromName( 'UTSysop', true ),
114 'idIsSafe' => true,
115 ] );
116 $id = new SessionId( $info->getId() );
117 try {
118 new SessionBackend( $id, $info, $this->store, $logger, 10 );
119 $this->fail( 'Expected exception not thrown' );
120 } catch ( \InvalidArgumentException $ex ) {
121 $this->assertSame( 'Cannot create session without a provider', $ex->getMessage() );
122 }
123
125 'provider' => $this->provider,
126 'id' => self::SESSIONID,
127 'persisted' => true,
128 'userInfo' => UserInfo::newFromName( 'UTSysop', true ),
129 'idIsSafe' => true,
130 ] );
131 $id = new SessionId( '!' . $info->getId() );
132 try {
133 new SessionBackend( $id, $info, $this->store, $logger, 10 );
134 $this->fail( 'Expected exception not thrown' );
135 } catch ( \InvalidArgumentException $ex ) {
136 $this->assertSame(
137 'SessionId and SessionInfo don\'t match',
138 $ex->getMessage()
139 );
140 }
141
143 'provider' => $this->provider,
144 'id' => self::SESSIONID,
145 'persisted' => true,
146 'userInfo' => UserInfo::newFromName( 'UTSysop', true ),
147 'idIsSafe' => true,
148 ] );
149 $id = new SessionId( $info->getId() );
150 $backend = new SessionBackend( $id, $info, $this->store, $logger, 10 );
151 $this->assertSame( self::SESSIONID, $backend->getId() );
152 $this->assertSame( $id, $backend->getSessionId() );
153 $this->assertSame( $this->provider, $backend->getProvider() );
154 $this->assertInstanceOf( User::class, $backend->getUser() );
155 $this->assertSame( 'UTSysop', $backend->getUser()->getName() );
156 $this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
157 $this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
158 $this->assertSame( $info->forceHTTPS(), $backend->shouldForceHTTPS() );
159
160 $expire = time() + 100;
161 $this->store->setSessionMeta( self::SESSIONID, [ 'expires' => $expire ] );
162
164 'provider' => $this->provider,
165 'id' => self::SESSIONID,
166 'persisted' => true,
167 'forceHTTPS' => true,
168 'metadata' => [ 'foo' ],
169 'idIsSafe' => true,
170 ] );
171 $id = new SessionId( $info->getId() );
172 $backend = new SessionBackend( $id, $info, $this->store, $logger, 10 );
173 $this->assertSame( self::SESSIONID, $backend->getId() );
174 $this->assertSame( $id, $backend->getSessionId() );
175 $this->assertSame( $this->provider, $backend->getProvider() );
176 $this->assertInstanceOf( User::class, $backend->getUser() );
177 $this->assertTrue( $backend->getUser()->isAnon() );
178 $this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
179 $this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
180 $this->assertSame( $info->forceHTTPS(), $backend->shouldForceHTTPS() );
181 $this->assertSame( $expire, TestingAccessWrapper::newFromObject( $backend )->expires );
182 $this->assertSame( [ 'foo' ], $backend->getProviderMetadata() );
183 }
184
185 public function testSessionStuff() {
186 $backend = $this->getBackend();
187 $priv = TestingAccessWrapper::newFromObject( $backend );
188 $priv->requests = []; // Remove dummy session
189
190 $manager = TestingAccessWrapper::newFromObject( $this->manager );
191
192 $request1 = new \FauxRequest();
193 $session1 = $backend->getSession( $request1 );
194 $request2 = new \FauxRequest();
195 $session2 = $backend->getSession( $request2 );
196
197 $this->assertInstanceOf( Session::class, $session1 );
198 $this->assertInstanceOf( Session::class, $session2 );
199 $this->assertSame( 2, count( $priv->requests ) );
200
201 $index = TestingAccessWrapper::newFromObject( $session1 )->index;
202
203 $this->assertSame( $request1, $backend->getRequest( $index ) );
204 $this->assertSame( null, $backend->suggestLoginUsername( $index ) );
205 $request1->setCookie( 'UserName', 'Example' );
206 $this->assertSame( 'Example', $backend->suggestLoginUsername( $index ) );
207
208 $session1 = null;
209 $this->assertSame( 1, count( $priv->requests ) );
210 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionBackends );
211 $this->assertSame( $backend, $manager->allSessionBackends[$backend->getId()] );
212 try {
213 $backend->getRequest( $index );
214 $this->fail( 'Expected exception not thrown' );
215 } catch ( \InvalidArgumentException $ex ) {
216 $this->assertSame( 'Invalid session index', $ex->getMessage() );
217 }
218 try {
219 $backend->suggestLoginUsername( $index );
220 $this->fail( 'Expected exception not thrown' );
221 } catch ( \InvalidArgumentException $ex ) {
222 $this->assertSame( 'Invalid session index', $ex->getMessage() );
223 }
224
225 $session2 = null;
226 $this->assertSame( 0, count( $priv->requests ) );
227 $this->assertArrayNotHasKey( $backend->getId(), $manager->allSessionBackends );
228 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionIds );
229 }
230
231 public function testSetProviderMetadata() {
232 $backend = $this->getBackend();
233 $priv = TestingAccessWrapper::newFromObject( $backend );
234 $priv->providerMetadata = [ 'dummy' ];
235
236 try {
237 $backend->setProviderMetadata( 'foo' );
238 $this->fail( 'Expected exception not thrown' );
239 } catch ( \InvalidArgumentException $ex ) {
240 $this->assertSame( '$metadata must be an array or null', $ex->getMessage() );
241 }
242
243 try {
244 $backend->setProviderMetadata( (object)[] );
245 $this->fail( 'Expected exception not thrown' );
246 } catch ( \InvalidArgumentException $ex ) {
247 $this->assertSame( '$metadata must be an array or null', $ex->getMessage() );
248 }
249
250 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'sanity check' );
251 $backend->setProviderMetadata( [ 'dummy' ] );
252 $this->assertFalse( $this->store->getSession( self::SESSIONID ) );
253
254 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'sanity check' );
255 $backend->setProviderMetadata( [ 'test' ] );
256 $this->assertNotFalse( $this->store->getSession( self::SESSIONID ) );
257 $this->assertSame( [ 'test' ], $backend->getProviderMetadata() );
258 $this->store->deleteSession( self::SESSIONID );
259
260 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'sanity check' );
261 $backend->setProviderMetadata( null );
262 $this->assertNotFalse( $this->store->getSession( self::SESSIONID ) );
263 $this->assertSame( null, $backend->getProviderMetadata() );
264 $this->store->deleteSession( self::SESSIONID );
265 }
266
267 public function testResetId() {
268 $id = session_id();
269
270 $builder = $this->getMockBuilder( \DummySessionProvider::class )
271 ->setMethods( [ 'persistsSessionId', 'sessionIdWasReset' ] );
272
273 $this->provider = $builder->getMock();
274 $this->provider->expects( $this->any() )->method( 'persistsSessionId' )
275 ->will( $this->returnValue( false ) );
276 $this->provider->expects( $this->never() )->method( 'sessionIdWasReset' );
277 $backend = $this->getBackend( User::newFromName( 'UTSysop' ) );
278 $manager = TestingAccessWrapper::newFromObject( $this->manager );
279 $sessionId = $backend->getSessionId();
280 $backend->resetId();
281 $this->assertSame( self::SESSIONID, $backend->getId() );
282 $this->assertSame( $backend->getId(), $sessionId->getId() );
283 $this->assertSame( $id, session_id() );
284 $this->assertSame( $backend, $manager->allSessionBackends[self::SESSIONID] );
285
286 $this->provider = $builder->getMock();
287 $this->provider->expects( $this->any() )->method( 'persistsSessionId' )
288 ->will( $this->returnValue( true ) );
289 $backend = $this->getBackend();
290 $this->provider->expects( $this->once() )->method( 'sessionIdWasReset' )
291 ->with( $this->identicalTo( $backend ), $this->identicalTo( self::SESSIONID ) );
292 $manager = TestingAccessWrapper::newFromObject( $this->manager );
293 $sessionId = $backend->getSessionId();
294 $backend->resetId();
295 $this->assertNotEquals( self::SESSIONID, $backend->getId() );
296 $this->assertSame( $backend->getId(), $sessionId->getId() );
297 $this->assertInternalType( 'array', $this->store->getSession( $backend->getId() ) );
298 $this->assertFalse( $this->store->getSession( self::SESSIONID ) );
299 $this->assertSame( $id, session_id() );
300 $this->assertArrayNotHasKey( self::SESSIONID, $manager->allSessionBackends );
301 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionBackends );
302 $this->assertSame( $backend, $manager->allSessionBackends[$backend->getId()] );
303 }
304
305 public function testPersist() {
306 $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
307 ->setMethods( [ 'persistSession' ] )->getMock();
308 $this->provider->expects( $this->once() )->method( 'persistSession' );
309 $backend = $this->getBackend();
310 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
311 $backend->save(); // This one shouldn't call $provider->persistSession()
312
313 $backend->persist();
314 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
315
316 $this->provider = null;
317 $backend = $this->getBackend();
318 $wrap = TestingAccessWrapper::newFromObject( $backend );
319 $wrap->persist = true;
320 $wrap->expires = 0;
321 $backend->persist();
322 $this->assertNotEquals( 0, $wrap->expires );
323 }
324
325 public function testUnpersist() {
326 $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
327 ->setMethods( [ 'unpersistSession' ] )->getMock();
328 $this->provider->expects( $this->once() )->method( 'unpersistSession' );
329 $backend = $this->getBackend();
330 $wrap = TestingAccessWrapper::newFromObject( $backend );
331 $wrap->store = new \CachedBagOStuff( $this->store );
332 $wrap->persist = true;
333 $wrap->dataDirty = true;
334
335 $backend->save(); // This one shouldn't call $provider->persistSession(), but should save
336 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
337 $this->assertNotFalse( $this->store->getSession( self::SESSIONID ), 'sanity check' );
338
339 $backend->unpersist();
340 $this->assertFalse( $backend->isPersistent() );
341 $this->assertFalse( $this->store->getSession( self::SESSIONID ) );
342 $this->assertNotFalse(
343 $wrap->store->get( $wrap->store->makeKey( 'MWSession', self::SESSIONID ) )
344 );
345 }
346
347 public function testRememberUser() {
348 $backend = $this->getBackend();
349
350 $remembered = $backend->shouldRememberUser();
351 $backend->setRememberUser( !$remembered );
352 $this->assertNotEquals( $remembered, $backend->shouldRememberUser() );
353 $backend->setRememberUser( $remembered );
354 $this->assertEquals( $remembered, $backend->shouldRememberUser() );
355 }
356
357 public function testForceHTTPS() {
358 $backend = $this->getBackend();
359
360 $force = $backend->shouldForceHTTPS();
361 $backend->setForceHTTPS( !$force );
362 $this->assertNotEquals( $force, $backend->shouldForceHTTPS() );
363 $backend->setForceHTTPS( $force );
364 $this->assertEquals( $force, $backend->shouldForceHTTPS() );
365 }
366
367 public function testLoggedOutTimestamp() {
368 $backend = $this->getBackend();
369
370 $backend->setLoggedOutTimestamp( 42 );
371 $this->assertSame( 42, $backend->getLoggedOutTimestamp() );
372 $backend->setLoggedOutTimestamp( '123' );
373 $this->assertSame( 123, $backend->getLoggedOutTimestamp() );
374 }
375
376 public function testSetUser() {
377 $user = static::getTestSysop()->getUser();
378
379 $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
380 ->setMethods( [ 'canChangeUser' ] )->getMock();
381 $this->provider->expects( $this->any() )->method( 'canChangeUser' )
382 ->will( $this->returnValue( false ) );
383 $backend = $this->getBackend();
384 $this->assertFalse( $backend->canSetUser() );
385 try {
386 $backend->setUser( $user );
387 $this->fail( 'Expected exception not thrown' );
388 } catch ( \BadMethodCallException $ex ) {
389 $this->assertSame(
390 'Cannot set user on this session; check $session->canSetUser() first',
391 $ex->getMessage()
392 );
393 }
394 $this->assertNotSame( $user, $backend->getUser() );
395
396 $this->provider = null;
397 $backend = $this->getBackend();
398 $this->assertTrue( $backend->canSetUser() );
399 $this->assertNotSame( $user, $backend->getUser(), 'sanity check' );
400 $backend->setUser( $user );
401 $this->assertSame( $user, $backend->getUser() );
402 }
403
404 public function testDirty() {
405 $backend = $this->getBackend();
406 $priv = TestingAccessWrapper::newFromObject( $backend );
407 $priv->dataDirty = false;
408 $backend->dirty();
409 $this->assertTrue( $priv->dataDirty );
410 }
411
412 public function testGetData() {
413 $backend = $this->getBackend();
414 $data = $backend->getData();
415 $this->assertSame( [], $data );
416 $this->assertTrue( TestingAccessWrapper::newFromObject( $backend )->dataDirty );
417 $data['???'] = '!!!';
418 $this->assertSame( [ '???' => '!!!' ], $data );
419
420 $testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
421 $this->store->setSessionData( self::SESSIONID, $testData );
422 $backend = $this->getBackend();
423 $this->assertSame( $testData, $backend->getData() );
424 $this->assertFalse( TestingAccessWrapper::newFromObject( $backend )->dataDirty );
425 }
426
427 public function testAddData() {
428 $backend = $this->getBackend();
429 $priv = TestingAccessWrapper::newFromObject( $backend );
430
431 $priv->data = [ 'foo' => 1 ];
432 $priv->dataDirty = false;
433 $backend->addData( [ 'foo' => 1 ] );
434 $this->assertSame( [ 'foo' => 1 ], $priv->data );
435 $this->assertFalse( $priv->dataDirty );
436
437 $priv->data = [ 'foo' => 1 ];
438 $priv->dataDirty = false;
439 $backend->addData( [ 'foo' => '1' ] );
440 $this->assertSame( [ 'foo' => '1' ], $priv->data );
441 $this->assertTrue( $priv->dataDirty );
442
443 $priv->data = [ 'foo' => 1 ];
444 $priv->dataDirty = false;
445 $backend->addData( [ 'bar' => 2 ] );
446 $this->assertSame( [ 'foo' => 1, 'bar' => 2 ], $priv->data );
447 $this->assertTrue( $priv->dataDirty );
448 }
449
450 public function testDelaySave() {
451 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
452 $backend = $this->getBackend();
453 $priv = TestingAccessWrapper::newFromObject( $backend );
454 $priv->persist = true;
455
456 // Saves happen normally when no delay is in effect
457 $this->onSessionMetadataCalled = false;
458 $priv->metaDirty = true;
459 $backend->save();
460 $this->assertTrue( $this->onSessionMetadataCalled, 'sanity check' );
461
462 $this->onSessionMetadataCalled = false;
463 $priv->metaDirty = true;
464 $priv->autosave();
465 $this->assertTrue( $this->onSessionMetadataCalled, 'sanity check' );
466
467 $delay = $backend->delaySave();
468
469 // Autosave doesn't happen when no delay is in effect
470 $this->onSessionMetadataCalled = false;
471 $priv->metaDirty = true;
472 $priv->autosave();
473 $this->assertFalse( $this->onSessionMetadataCalled );
474
475 // Save still does happen when no delay is in effect
476 $priv->save();
477 $this->assertTrue( $this->onSessionMetadataCalled );
478
479 // Save happens when delay is consumed
480 $this->onSessionMetadataCalled = false;
481 $priv->metaDirty = true;
482 \Wikimedia\ScopedCallback::consume( $delay );
483 $this->assertTrue( $this->onSessionMetadataCalled );
484
485 // Test multiple delays
486 $delay1 = $backend->delaySave();
487 $delay2 = $backend->delaySave();
488 $delay3 = $backend->delaySave();
489 $this->onSessionMetadataCalled = false;
490 $priv->metaDirty = true;
491 $priv->autosave();
492 $this->assertFalse( $this->onSessionMetadataCalled );
493 \Wikimedia\ScopedCallback::consume( $delay3 );
494 $this->assertFalse( $this->onSessionMetadataCalled );
495 \Wikimedia\ScopedCallback::consume( $delay1 );
496 $this->assertFalse( $this->onSessionMetadataCalled );
497 \Wikimedia\ScopedCallback::consume( $delay2 );
498 $this->assertTrue( $this->onSessionMetadataCalled );
499 }
500
501 public function testSave() {
502 $user = static::getTestSysop()->getUser();
503 $this->store = new TestBagOStuff();
504 $testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
505
506 $neverHook = $this->getMockBuilder( __CLASS__ )
507 ->setMethods( [ 'onSessionMetadata' ] )->getMock();
508 $neverHook->expects( $this->never() )->method( 'onSessionMetadata' );
509
510 $builder = $this->getMockBuilder( \DummySessionProvider::class )
511 ->setMethods( [ 'persistSession', 'unpersistSession' ] );
512
513 $neverProvider = $builder->getMock();
514 $neverProvider->expects( $this->never() )->method( 'persistSession' );
515 $neverProvider->expects( $this->never() )->method( 'unpersistSession' );
516
517 // Not persistent or dirty
518 $this->provider = $neverProvider;
519 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
520 $this->store->setSessionData( self::SESSIONID, $testData );
521 $backend = $this->getBackend( $user );
522 $this->store->deleteSession( self::SESSIONID );
523 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
524 TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
525 TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
526 $backend->save();
527 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
528
529 // (but does unpersist if forced)
530 $this->provider = $builder->getMock();
531 $this->provider->expects( $this->never() )->method( 'persistSession' );
532 $this->provider->expects( $this->atLeastOnce() )->method( 'unpersistSession' );
533 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
534 $this->store->setSessionData( self::SESSIONID, $testData );
535 $backend = $this->getBackend( $user );
536 $this->store->deleteSession( self::SESSIONID );
537 TestingAccessWrapper::newFromObject( $backend )->persist = false;
538 TestingAccessWrapper::newFromObject( $backend )->forcePersist = true;
539 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
540 TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
541 TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
542 $backend->save();
543 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
544
545 // (but not to a WebRequest associated with a different session)
546 $this->provider = $neverProvider;
547 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
548 $this->store->setSessionData( self::SESSIONID, $testData );
549 $backend = $this->getBackend( $user );
550 TestingAccessWrapper::newFromObject( $backend )->requests[100]
551 ->setSessionId( new SessionId( 'x' ) );
552 $this->store->deleteSession( self::SESSIONID );
553 TestingAccessWrapper::newFromObject( $backend )->persist = false;
554 TestingAccessWrapper::newFromObject( $backend )->forcePersist = true;
555 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
556 TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
557 TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
558 $backend->save();
559 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
560
561 // Not persistent, but dirty
562 $this->provider = $neverProvider;
563 $this->onSessionMetadataCalled = false;
564 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
565 $this->store->setSessionData( self::SESSIONID, $testData );
566 $backend = $this->getBackend( $user );
567 $this->store->deleteSession( self::SESSIONID );
568 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
569 TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
570 TestingAccessWrapper::newFromObject( $backend )->dataDirty = true;
571 $backend->save();
572 $this->assertTrue( $this->onSessionMetadataCalled );
573 $blob = $this->store->getSession( self::SESSIONID );
574 $this->assertInternalType( 'array', $blob );
575 $this->assertArrayHasKey( 'metadata', $blob );
576 $metadata = $blob['metadata'];
577 $this->assertInternalType( 'array', $metadata );
578 $this->assertArrayHasKey( '???', $metadata );
579 $this->assertSame( '!!!', $metadata['???'] );
580 $this->assertFalse( $this->store->getSessionFromBackend( self::SESSIONID ),
581 'making sure it didn\'t save to backend' );
582
583 // Persistent, not dirty
584 $this->provider = $neverProvider;
585 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
586 $this->store->setSessionData( self::SESSIONID, $testData );
587 $backend = $this->getBackend( $user );
588 $this->store->deleteSession( self::SESSIONID );
589 TestingAccessWrapper::newFromObject( $backend )->persist = true;
590 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
591 TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
592 TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
593 $backend->save();
594 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
595
596 // (but will persist if forced)
597 $this->provider = $builder->getMock();
598 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
599 $this->provider->expects( $this->never() )->method( 'unpersistSession' );
600 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
601 $this->store->setSessionData( self::SESSIONID, $testData );
602 $backend = $this->getBackend( $user );
603 $this->store->deleteSession( self::SESSIONID );
604 TestingAccessWrapper::newFromObject( $backend )->persist = true;
605 TestingAccessWrapper::newFromObject( $backend )->forcePersist = true;
606 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
607 TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
608 TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
609 $backend->save();
610 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
611
612 // Persistent and dirty
613 $this->provider = $neverProvider;
614 $this->onSessionMetadataCalled = false;
615 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
616 $this->store->setSessionData( self::SESSIONID, $testData );
617 $backend = $this->getBackend( $user );
618 $this->store->deleteSession( self::SESSIONID );
619 TestingAccessWrapper::newFromObject( $backend )->persist = true;
620 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
621 TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
622 TestingAccessWrapper::newFromObject( $backend )->dataDirty = true;
623 $backend->save();
624 $this->assertTrue( $this->onSessionMetadataCalled );
625 $blob = $this->store->getSession( self::SESSIONID );
626 $this->assertInternalType( 'array', $blob );
627 $this->assertArrayHasKey( 'metadata', $blob );
628 $metadata = $blob['metadata'];
629 $this->assertInternalType( 'array', $metadata );
630 $this->assertArrayHasKey( '???', $metadata );
631 $this->assertSame( '!!!', $metadata['???'] );
632 $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
633 'making sure it did save to backend' );
634
635 // (also persists if forced)
636 $this->provider = $builder->getMock();
637 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
638 $this->provider->expects( $this->never() )->method( 'unpersistSession' );
639 $this->onSessionMetadataCalled = false;
640 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
641 $this->store->setSessionData( self::SESSIONID, $testData );
642 $backend = $this->getBackend( $user );
643 $this->store->deleteSession( self::SESSIONID );
644 TestingAccessWrapper::newFromObject( $backend )->persist = true;
645 TestingAccessWrapper::newFromObject( $backend )->forcePersist = true;
646 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
647 TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
648 TestingAccessWrapper::newFromObject( $backend )->dataDirty = true;
649 $backend->save();
650 $this->assertTrue( $this->onSessionMetadataCalled );
651 $blob = $this->store->getSession( self::SESSIONID );
652 $this->assertInternalType( 'array', $blob );
653 $this->assertArrayHasKey( 'metadata', $blob );
654 $metadata = $blob['metadata'];
655 $this->assertInternalType( 'array', $metadata );
656 $this->assertArrayHasKey( '???', $metadata );
657 $this->assertSame( '!!!', $metadata['???'] );
658 $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
659 'making sure it did save to backend' );
660
661 // (also persists if metadata dirty)
662 $this->provider = $builder->getMock();
663 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
664 $this->provider->expects( $this->never() )->method( 'unpersistSession' );
665 $this->onSessionMetadataCalled = false;
666 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
667 $this->store->setSessionData( self::SESSIONID, $testData );
668 $backend = $this->getBackend( $user );
669 $this->store->deleteSession( self::SESSIONID );
670 TestingAccessWrapper::newFromObject( $backend )->persist = true;
671 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
672 TestingAccessWrapper::newFromObject( $backend )->metaDirty = true;
673 TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
674 $backend->save();
675 $this->assertTrue( $this->onSessionMetadataCalled );
676 $blob = $this->store->getSession( self::SESSIONID );
677 $this->assertInternalType( 'array', $blob );
678 $this->assertArrayHasKey( 'metadata', $blob );
679 $metadata = $blob['metadata'];
680 $this->assertInternalType( 'array', $metadata );
681 $this->assertArrayHasKey( '???', $metadata );
682 $this->assertSame( '!!!', $metadata['???'] );
683 $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
684 'making sure it did save to backend' );
685
686 // Not marked dirty, but dirty data
687 // (e.g. indirect modification from ArrayAccess::offsetGet)
688 $this->provider = $neverProvider;
689 $this->onSessionMetadataCalled = false;
690 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
691 $this->store->setSessionData( self::SESSIONID, $testData );
692 $backend = $this->getBackend( $user );
693 $this->store->deleteSession( self::SESSIONID );
694 TestingAccessWrapper::newFromObject( $backend )->persist = true;
695 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
696 TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
697 TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
698 TestingAccessWrapper::newFromObject( $backend )->dataHash = 'Doesn\'t match';
699 $backend->save();
700 $this->assertTrue( $this->onSessionMetadataCalled );
701 $blob = $this->store->getSession( self::SESSIONID );
702 $this->assertInternalType( 'array', $blob );
703 $this->assertArrayHasKey( 'metadata', $blob );
704 $metadata = $blob['metadata'];
705 $this->assertInternalType( 'array', $metadata );
706 $this->assertArrayHasKey( '???', $metadata );
707 $this->assertSame( '!!!', $metadata['???'] );
708 $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
709 'making sure it did save to backend' );
710
711 // Bad hook
712 $this->provider = null;
713 $mockHook = $this->getMockBuilder( __CLASS__ )
714 ->setMethods( [ 'onSessionMetadata' ] )->getMock();
715 $mockHook->expects( $this->any() )->method( 'onSessionMetadata' )
716 ->will( $this->returnCallback(
717 function ( SessionBackend $backend, array &$metadata, array $requests ) {
718 $metadata['userId']++;
719 }
720 ) );
721 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $mockHook ] ] );
722 $this->store->setSessionData( self::SESSIONID, $testData );
723 $backend = $this->getBackend( $user );
724 $backend->dirty();
725 try {
726 $backend->save();
727 $this->fail( 'Expected exception not thrown' );
728 } catch ( \UnexpectedValueException $ex ) {
729 $this->assertSame(
730 'SessionMetadata hook changed metadata key "userId"',
731 $ex->getMessage()
732 );
733 }
734
735 // SessionManager::preventSessionsForUser
736 TestingAccessWrapper::newFromObject( $this->manager )->preventUsers = [
737 $user->getName() => true,
738 ];
739 $this->provider = $neverProvider;
740 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
741 $this->store->setSessionData( self::SESSIONID, $testData );
742 $backend = $this->getBackend( $user );
743 $this->store->deleteSession( self::SESSIONID );
744 TestingAccessWrapper::newFromObject( $backend )->persist = true;
745 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
746 TestingAccessWrapper::newFromObject( $backend )->metaDirty = true;
747 TestingAccessWrapper::newFromObject( $backend )->dataDirty = true;
748 $backend->save();
749 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
750 }
751
752 public function testRenew() {
753 $user = static::getTestSysop()->getUser();
754 $this->store = new TestBagOStuff();
755 $testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
756
757 // Not persistent
758 $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
759 ->setMethods( [ 'persistSession' ] )->getMock();
760 $this->provider->expects( $this->never() )->method( 'persistSession' );
761 $this->onSessionMetadataCalled = false;
762 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
763 $this->store->setSessionData( self::SESSIONID, $testData );
764 $backend = $this->getBackend( $user );
765 $this->store->deleteSession( self::SESSIONID );
766 $wrap = TestingAccessWrapper::newFromObject( $backend );
767 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
768 $wrap->metaDirty = false;
769 $wrap->dataDirty = false;
770 $wrap->forcePersist = false;
771 $wrap->expires = 0;
772 $backend->renew();
773 $this->assertTrue( $this->onSessionMetadataCalled );
774 $blob = $this->store->getSession( self::SESSIONID );
775 $this->assertInternalType( 'array', $blob );
776 $this->assertArrayHasKey( 'metadata', $blob );
777 $metadata = $blob['metadata'];
778 $this->assertInternalType( 'array', $metadata );
779 $this->assertArrayHasKey( '???', $metadata );
780 $this->assertSame( '!!!', $metadata['???'] );
781 $this->assertNotEquals( 0, $wrap->expires );
782
783 // Persistent
784 $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
785 ->setMethods( [ 'persistSession' ] )->getMock();
786 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
787 $this->onSessionMetadataCalled = false;
788 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
789 $this->store->setSessionData( self::SESSIONID, $testData );
790 $backend = $this->getBackend( $user );
791 $this->store->deleteSession( self::SESSIONID );
792 $wrap = TestingAccessWrapper::newFromObject( $backend );
793 $wrap->persist = true;
794 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
795 $wrap->metaDirty = false;
796 $wrap->dataDirty = false;
797 $wrap->forcePersist = false;
798 $wrap->expires = 0;
799 $backend->renew();
800 $this->assertTrue( $this->onSessionMetadataCalled );
801 $blob = $this->store->getSession( self::SESSIONID );
802 $this->assertInternalType( 'array', $blob );
803 $this->assertArrayHasKey( 'metadata', $blob );
804 $metadata = $blob['metadata'];
805 $this->assertInternalType( 'array', $metadata );
806 $this->assertArrayHasKey( '???', $metadata );
807 $this->assertSame( '!!!', $metadata['???'] );
808 $this->assertNotEquals( 0, $wrap->expires );
809
810 // Not persistent, not expiring
811 $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
812 ->setMethods( [ 'persistSession' ] )->getMock();
813 $this->provider->expects( $this->never() )->method( 'persistSession' );
814 $this->onSessionMetadataCalled = false;
815 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
816 $this->store->setSessionData( self::SESSIONID, $testData );
817 $backend = $this->getBackend( $user );
818 $this->store->deleteSession( self::SESSIONID );
819 $wrap = TestingAccessWrapper::newFromObject( $backend );
820 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
821 $wrap->metaDirty = false;
822 $wrap->dataDirty = false;
823 $wrap->forcePersist = false;
824 $expires = time() + $wrap->lifetime + 100;
825 $wrap->expires = $expires;
826 $backend->renew();
827 $this->assertFalse( $this->onSessionMetadataCalled );
828 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
829 $this->assertEquals( $expires, $wrap->expires );
830 }
831
832 public function onSessionMetadata( SessionBackend $backend, array &$metadata, array $requests ) {
833 $this->onSessionMetadataCalled = true;
834 $metadata['???'] = '!!!';
835 }
836
837 public function testTakeOverGlobalSession() {
840 }
842 $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' );
843 $rProp->setAccessible( true );
844 $handler = TestingAccessWrapper::newFromObject( $rProp->getValue() );
845 $resetHandler = new \Wikimedia\ScopedCallback( function () use ( $handler ) {
846 session_write_close();
847 $handler->enable = false;
848 } );
849 $handler->enable = true;
850 }
851
852 $backend = $this->getBackend( static::getTestSysop()->getUser() );
853 TestingAccessWrapper::newFromObject( $backend )->usePhpSessionHandling = true;
854
855 $resetSingleton = TestUtils::setSessionManagerSingleton( $this->manager );
856
857 $manager = TestingAccessWrapper::newFromObject( $this->manager );
858 $request = \RequestContext::getMain()->getRequest();
859 $manager->globalSession = $backend->getSession( $request );
860 $manager->globalSessionRequest = $request;
861
862 session_id( '' );
863 TestingAccessWrapper::newFromObject( $backend )->checkPHPSession();
864 $this->assertSame( $backend->getId(), session_id() );
865 session_write_close();
866
867 $backend2 = $this->getBackend(
868 User::newFromName( 'UTSysop' ), 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
869 );
870 TestingAccessWrapper::newFromObject( $backend2 )->usePhpSessionHandling = true;
871
872 session_id( '' );
873 TestingAccessWrapper::newFromObject( $backend2 )->checkPHPSession();
874 $this->assertSame( '', session_id() );
875 }
876
877 public function testResetIdOfGlobalSession() {
880 }
882 $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' );
883 $rProp->setAccessible( true );
884 $handler = TestingAccessWrapper::newFromObject( $rProp->getValue() );
885 $resetHandler = new \Wikimedia\ScopedCallback( function () use ( $handler ) {
886 session_write_close();
887 $handler->enable = false;
888 } );
889 $handler->enable = true;
890 }
891
892 $backend = $this->getBackend( User::newFromName( 'UTSysop' ) );
893 TestingAccessWrapper::newFromObject( $backend )->usePhpSessionHandling = true;
894
895 $resetSingleton = TestUtils::setSessionManagerSingleton( $this->manager );
896
897 $manager = TestingAccessWrapper::newFromObject( $this->manager );
898 $request = \RequestContext::getMain()->getRequest();
899 $manager->globalSession = $backend->getSession( $request );
900 $manager->globalSessionRequest = $request;
901
902 session_id( self::SESSIONID );
903 \Wikimedia\quietCall( 'session_start' );
904 $_SESSION['foo'] = __METHOD__;
905 $backend->resetId();
906 $this->assertNotEquals( self::SESSIONID, $backend->getId() );
907 $this->assertSame( $backend->getId(), session_id() );
908 $this->assertArrayHasKey( 'foo', $_SESSION );
909 $this->assertSame( __METHOD__, $_SESSION['foo'] );
910 session_write_close();
911 }
912
916 }
918 $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' );
919 $rProp->setAccessible( true );
920 $handler = TestingAccessWrapper::newFromObject( $rProp->getValue() );
921 $resetHandler = new \Wikimedia\ScopedCallback( function () use ( $handler ) {
922 session_write_close();
923 $handler->enable = false;
924 } );
925 $handler->enable = true;
926 }
927
928 $backend = $this->getBackend( User::newFromName( 'UTSysop' ) );
929 $wrap = TestingAccessWrapper::newFromObject( $backend );
930 $wrap->usePhpSessionHandling = true;
931 $wrap->persist = true;
932
933 $resetSingleton = TestUtils::setSessionManagerSingleton( $this->manager );
934
935 $manager = TestingAccessWrapper::newFromObject( $this->manager );
936 $request = \RequestContext::getMain()->getRequest();
937 $manager->globalSession = $backend->getSession( $request );
938 $manager->globalSessionRequest = $request;
939
940 session_id( self::SESSIONID . 'x' );
941 \Wikimedia\quietCall( 'session_start' );
942 $backend->unpersist();
943 $this->assertSame( self::SESSIONID . 'x', session_id() );
944 session_write_close();
945
946 session_id( self::SESSIONID );
947 $wrap->persist = true;
948 $backend->unpersist();
949 $this->assertSame( '', session_id() );
950 }
951
952 public function testGetAllowedUserRights() {
953 $this->provider = $this->getMockBuilder( \DummySessionProvider::class )
954 ->setMethods( [ 'getAllowedUserRights' ] )
955 ->getMock();
956 $this->provider->expects( $this->any() )->method( 'getAllowedUserRights' )
957 ->will( $this->returnValue( [ 'foo', 'bar' ] ) );
958
959 $backend = $this->getBackend();
960 $this->assertSame( [ 'foo', 'bar' ], $backend->getAllowedUserRights() );
961 }
962
963}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
mergeMwGlobalArrayValue( $name, $values)
Merges the given values into a MW global array variable.
static isInstalled()
Test whether the handler is installed.
static isEnabled()
Test whether the handler is installed and enabled.
static install(SessionManager $manager)
Install a session handler for the current web request.
Session Database MediaWiki\Session\SessionBackend.
getBackend(User $user=null, $id=null)
Returns a non-persistent backend that thinks it has at least one session active.
onSessionMetadata(SessionBackend $backend, array &$metadata, array $requests)
This is the actual workhorse for Session.
isPersistent()
Indicate whether this session is persisted across requests.
save( $closing=false)
Save the session.
Value object holding the session ID in a manner that can be globally updated.
Definition SessionId.php:38
Value object returned by SessionProvider.
const MIN_PRIORITY
Minimum allowed priority.
This serves as the entry point to the MediaWiki session handling system.
static singleton()
Get the global SessionManager.
A SessionProvider provides SessionInfo and support for Session.
BagOStuff with utility functions for MediaWiki\\Session\\* testing.
static setSessionManagerSingleton(SessionManager $manager=null)
Override the singleton for unit testing.
Definition TestUtils.php:18
static newFromName( $name, $verified=false)
Create an instance for a logged-in user by name.
Definition UserInfo.php:103
static newFromUser(User $user, $verified=false)
Create an instance from an existing User object.
Definition UserInfo.php:117
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:585
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2843
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:181
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:894
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition hooks.txt:783
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:2004
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead. & $feedLinks hooks can tweak the array to change how login etc forms should look $requests
Definition hooks.txt:273
return true to allow those checks to and false if checking is done & $user
Definition hooks.txt:1510
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:37
Interface for configuration instances.
Definition Config.php:28
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))