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