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