Convert all array() syntax to []
[lhc/web/wiklou.git] / tests / phpunit / includes / session / SessionBackendTest.php
1 <?php
2
3 namespace MediaWiki\Session;
4
5 use MediaWikiTestCase;
6 use User;
7
8 /**
9 * @group Session
10 * @group Database
11 * @covers MediaWiki\Session\SessionBackend
12 */
13 class SessionBackendTest extends MediaWikiTestCase {
14 const SESSIONID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
15
16 protected $manager;
17 protected $config;
18 protected $provider;
19 protected $store;
20
21 protected $onSessionMetadataCalled = false;
22
23 /**
24 * Returns a non-persistent backend that thinks it has at least one session active
25 * @param User|null $user
26 */
27 protected function getBackend( User $user = null ) {
28 if ( !$this->config ) {
29 $this->config = new \HashConfig();
30 $this->manager = null;
31 }
32 if ( !$this->store ) {
33 $this->store = new TestBagOStuff();
34 $this->manager = null;
35 }
36
37 $logger = new \Psr\Log\NullLogger();
38 if ( !$this->manager ) {
39 $this->manager = new SessionManager( [
40 'store' => $this->store,
41 'logger' => $logger,
42 'config' => $this->config,
43 ] );
44 }
45
46 if ( !$this->provider ) {
47 $this->provider = new \DummySessionProvider();
48 }
49 $this->provider->setLogger( $logger );
50 $this->provider->setConfig( $this->config );
51 $this->provider->setManager( $this->manager );
52
53 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
54 'provider' => $this->provider,
55 'id' => self::SESSIONID,
56 'persisted' => true,
57 'userInfo' => UserInfo::newFromUser( $user ?: new User, true ),
58 'idIsSafe' => true,
59 ] );
60 $id = new SessionId( $info->getId() );
61
62 $backend = new SessionBackend( $id, $info, $this->store, $logger, 10 );
63 $priv = \TestingAccessWrapper::newFromObject( $backend );
64 $priv->persist = false;
65 $priv->requests = [ 100 => new \FauxRequest() ];
66 $priv->usePhpSessionHandling = false;
67
68 $manager = \TestingAccessWrapper::newFromObject( $this->manager );
69 $manager->allSessionBackends = [ $backend->getId() => $backend ];
70 $manager->allSessionIds = [ $backend->getId() => $id ];
71 $manager->sessionProviders = [ (string)$this->provider => $this->provider ];
72
73 return $backend;
74 }
75
76 public function testConstructor() {
77 // Set variables
78 $this->getBackend();
79
80 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
81 'provider' => $this->provider,
82 'id' => self::SESSIONID,
83 'persisted' => true,
84 'userInfo' => UserInfo::newFromName( 'UTSysop', false ),
85 'idIsSafe' => true,
86 ] );
87 $id = new SessionId( $info->getId() );
88 $logger = new \Psr\Log\NullLogger();
89 try {
90 new SessionBackend( $id, $info, $this->store, $logger, 10 );
91 $this->fail( 'Expected exception not thrown' );
92 } catch ( \InvalidArgumentException $ex ) {
93 $this->assertSame(
94 "Refusing to create session for unverified user {$info->getUserInfo()}",
95 $ex->getMessage()
96 );
97 }
98
99 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
100 'id' => self::SESSIONID,
101 'userInfo' => UserInfo::newFromName( 'UTSysop', true ),
102 'idIsSafe' => true,
103 ] );
104 $id = new SessionId( $info->getId() );
105 try {
106 new SessionBackend( $id, $info, $this->store, $logger, 10 );
107 $this->fail( 'Expected exception not thrown' );
108 } catch ( \InvalidArgumentException $ex ) {
109 $this->assertSame( 'Cannot create session without a provider', $ex->getMessage() );
110 }
111
112 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
113 'provider' => $this->provider,
114 'id' => self::SESSIONID,
115 'persisted' => true,
116 'userInfo' => UserInfo::newFromName( 'UTSysop', true ),
117 'idIsSafe' => true,
118 ] );
119 $id = new SessionId( '!' . $info->getId() );
120 try {
121 new SessionBackend( $id, $info, $this->store, $logger, 10 );
122 $this->fail( 'Expected exception not thrown' );
123 } catch ( \InvalidArgumentException $ex ) {
124 $this->assertSame(
125 'SessionId and SessionInfo don\'t match',
126 $ex->getMessage()
127 );
128 }
129
130 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
131 'provider' => $this->provider,
132 'id' => self::SESSIONID,
133 'persisted' => true,
134 'userInfo' => UserInfo::newFromName( 'UTSysop', true ),
135 'idIsSafe' => true,
136 ] );
137 $id = new SessionId( $info->getId() );
138 $backend = new SessionBackend( $id, $info, $this->store, $logger, 10 );
139 $this->assertSame( self::SESSIONID, $backend->getId() );
140 $this->assertSame( $id, $backend->getSessionId() );
141 $this->assertSame( $this->provider, $backend->getProvider() );
142 $this->assertInstanceOf( 'User', $backend->getUser() );
143 $this->assertSame( 'UTSysop', $backend->getUser()->getName() );
144 $this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
145 $this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
146 $this->assertSame( $info->forceHTTPS(), $backend->shouldForceHTTPS() );
147
148 $expire = time() + 100;
149 $this->store->setSessionMeta( self::SESSIONID, [ 'expires' => $expire ], 2 );
150
151 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [
152 'provider' => $this->provider,
153 'id' => self::SESSIONID,
154 'persisted' => true,
155 'forceHTTPS' => true,
156 'metadata' => [ 'foo' ],
157 'idIsSafe' => true,
158 ] );
159 $id = new SessionId( $info->getId() );
160 $backend = new SessionBackend( $id, $info, $this->store, $logger, 10 );
161 $this->assertSame( self::SESSIONID, $backend->getId() );
162 $this->assertSame( $id, $backend->getSessionId() );
163 $this->assertSame( $this->provider, $backend->getProvider() );
164 $this->assertInstanceOf( 'User', $backend->getUser() );
165 $this->assertTrue( $backend->getUser()->isAnon() );
166 $this->assertSame( $info->wasPersisted(), $backend->isPersistent() );
167 $this->assertSame( $info->wasRemembered(), $backend->shouldRememberUser() );
168 $this->assertSame( $info->forceHTTPS(), $backend->shouldForceHTTPS() );
169 $this->assertSame( $expire, \TestingAccessWrapper::newFromObject( $backend )->expires );
170 $this->assertSame( [ 'foo' ], $backend->getProviderMetadata() );
171 }
172
173 public function testSessionStuff() {
174 $backend = $this->getBackend();
175 $priv = \TestingAccessWrapper::newFromObject( $backend );
176 $priv->requests = []; // Remove dummy session
177
178 $manager = \TestingAccessWrapper::newFromObject( $this->manager );
179
180 $request1 = new \FauxRequest();
181 $session1 = $backend->getSession( $request1 );
182 $request2 = new \FauxRequest();
183 $session2 = $backend->getSession( $request2 );
184
185 $this->assertInstanceOf( 'MediaWiki\\Session\\Session', $session1 );
186 $this->assertInstanceOf( 'MediaWiki\\Session\\Session', $session2 );
187 $this->assertSame( 2, count( $priv->requests ) );
188
189 $index = \TestingAccessWrapper::newFromObject( $session1 )->index;
190
191 $this->assertSame( $request1, $backend->getRequest( $index ) );
192 $this->assertSame( null, $backend->suggestLoginUsername( $index ) );
193 $request1->setCookie( 'UserName', 'Example' );
194 $this->assertSame( 'Example', $backend->suggestLoginUsername( $index ) );
195
196 $session1 = null;
197 $this->assertSame( 1, count( $priv->requests ) );
198 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionBackends );
199 $this->assertSame( $backend, $manager->allSessionBackends[$backend->getId()] );
200 try {
201 $backend->getRequest( $index );
202 $this->fail( 'Expected exception not thrown' );
203 } catch ( \InvalidArgumentException $ex ) {
204 $this->assertSame( 'Invalid session index', $ex->getMessage() );
205 }
206 try {
207 $backend->suggestLoginUsername( $index );
208 $this->fail( 'Expected exception not thrown' );
209 } catch ( \InvalidArgumentException $ex ) {
210 $this->assertSame( 'Invalid session index', $ex->getMessage() );
211 }
212
213 $session2 = null;
214 $this->assertSame( 0, count( $priv->requests ) );
215 $this->assertArrayNotHasKey( $backend->getId(), $manager->allSessionBackends );
216 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionIds );
217 }
218
219 public function testResetId() {
220 $id = session_id();
221
222 $builder = $this->getMockBuilder( 'DummySessionProvider' )
223 ->setMethods( [ 'persistsSessionId', 'sessionIdWasReset' ] );
224
225 $this->provider = $builder->getMock();
226 $this->provider->expects( $this->any() )->method( 'persistsSessionId' )
227 ->will( $this->returnValue( false ) );
228 $this->provider->expects( $this->never() )->method( 'sessionIdWasReset' );
229 $backend = $this->getBackend( User::newFromName( 'UTSysop' ) );
230 $manager = \TestingAccessWrapper::newFromObject( $this->manager );
231 $sessionId = $backend->getSessionId();
232 $backend->resetId();
233 $this->assertSame( self::SESSIONID, $backend->getId() );
234 $this->assertSame( $backend->getId(), $sessionId->getId() );
235 $this->assertSame( $id, session_id() );
236 $this->assertSame( $backend, $manager->allSessionBackends[self::SESSIONID] );
237
238 $this->provider = $builder->getMock();
239 $this->provider->expects( $this->any() )->method( 'persistsSessionId' )
240 ->will( $this->returnValue( true ) );
241 $backend = $this->getBackend();
242 $this->provider->expects( $this->once() )->method( 'sessionIdWasReset' )
243 ->with( $this->identicalTo( $backend ), $this->identicalTo( self::SESSIONID ) );
244 $manager = \TestingAccessWrapper::newFromObject( $this->manager );
245 $sessionId = $backend->getSessionId();
246 $backend->resetId();
247 $this->assertNotEquals( self::SESSIONID, $backend->getId() );
248 $this->assertSame( $backend->getId(), $sessionId->getId() );
249 $this->assertInternalType( 'array', $this->store->getSession( $backend->getId() ) );
250 $this->assertFalse( $this->store->getSession( self::SESSIONID ) );
251 $this->assertSame( $id, session_id() );
252 $this->assertArrayNotHasKey( self::SESSIONID, $manager->allSessionBackends );
253 $this->assertArrayHasKey( $backend->getId(), $manager->allSessionBackends );
254 $this->assertSame( $backend, $manager->allSessionBackends[$backend->getId()] );
255 }
256
257 public function testPersist() {
258 $this->provider = $this->getMock( 'DummySessionProvider', [ 'persistSession' ] );
259 $this->provider->expects( $this->once() )->method( 'persistSession' );
260 $backend = $this->getBackend();
261 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
262 $backend->save(); // This one shouldn't call $provider->persistSession()
263
264 $backend->persist();
265 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
266
267 $this->provider = null;
268 $backend = $this->getBackend();
269 $wrap = \TestingAccessWrapper::newFromObject( $backend );
270 $wrap->persist = true;
271 $wrap->expires = 0;
272 $backend->persist();
273 $this->assertNotEquals( 0, $wrap->expires );
274 }
275
276 public function testRememberUser() {
277 $backend = $this->getBackend();
278
279 $remembered = $backend->shouldRememberUser();
280 $backend->setRememberUser( !$remembered );
281 $this->assertNotEquals( $remembered, $backend->shouldRememberUser() );
282 $backend->setRememberUser( $remembered );
283 $this->assertEquals( $remembered, $backend->shouldRememberUser() );
284 }
285
286 public function testForceHTTPS() {
287 $backend = $this->getBackend();
288
289 $force = $backend->shouldForceHTTPS();
290 $backend->setForceHTTPS( !$force );
291 $this->assertNotEquals( $force, $backend->shouldForceHTTPS() );
292 $backend->setForceHTTPS( $force );
293 $this->assertEquals( $force, $backend->shouldForceHTTPS() );
294 }
295
296 public function testLoggedOutTimestamp() {
297 $backend = $this->getBackend();
298
299 $backend->setLoggedOutTimestamp( 42 );
300 $this->assertSame( 42, $backend->getLoggedOutTimestamp() );
301 $backend->setLoggedOutTimestamp( '123' );
302 $this->assertSame( 123, $backend->getLoggedOutTimestamp() );
303 }
304
305 public function testSetUser() {
306 $user = User::newFromName( 'UTSysop' );
307
308 $this->provider = $this->getMock( 'DummySessionProvider', [ 'canChangeUser' ] );
309 $this->provider->expects( $this->any() )->method( 'canChangeUser' )
310 ->will( $this->returnValue( false ) );
311 $backend = $this->getBackend();
312 $this->assertFalse( $backend->canSetUser() );
313 try {
314 $backend->setUser( $user );
315 $this->fail( 'Expected exception not thrown' );
316 } catch ( \BadMethodCallException $ex ) {
317 $this->assertSame(
318 'Cannot set user on this session; check $session->canSetUser() first',
319 $ex->getMessage()
320 );
321 }
322 $this->assertNotSame( $user, $backend->getUser() );
323
324 $this->provider = null;
325 $backend = $this->getBackend();
326 $this->assertTrue( $backend->canSetUser() );
327 $this->assertNotSame( $user, $backend->getUser(), 'sanity check' );
328 $backend->setUser( $user );
329 $this->assertSame( $user, $backend->getUser() );
330 }
331
332 public function testDirty() {
333 $backend = $this->getBackend();
334 $priv = \TestingAccessWrapper::newFromObject( $backend );
335 $priv->dataDirty = false;
336 $backend->dirty();
337 $this->assertTrue( $priv->dataDirty );
338 }
339
340 public function testGetData() {
341 $backend = $this->getBackend();
342 $data = $backend->getData();
343 $this->assertSame( [], $data );
344 $this->assertTrue( \TestingAccessWrapper::newFromObject( $backend )->dataDirty );
345 $data['???'] = '!!!';
346 $this->assertSame( [ '???' => '!!!' ], $data );
347
348 $testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
349 $this->store->setSessionData( self::SESSIONID, $testData );
350 $backend = $this->getBackend();
351 $this->assertSame( $testData, $backend->getData() );
352 $this->assertFalse( \TestingAccessWrapper::newFromObject( $backend )->dataDirty );
353 }
354
355 public function testAddData() {
356 $backend = $this->getBackend();
357 $priv = \TestingAccessWrapper::newFromObject( $backend );
358
359 $priv->data = [ 'foo' => 1 ];
360 $priv->dataDirty = false;
361 $backend->addData( [ 'foo' => 1 ] );
362 $this->assertSame( [ 'foo' => 1 ], $priv->data );
363 $this->assertFalse( $priv->dataDirty );
364
365 $priv->data = [ 'foo' => 1 ];
366 $priv->dataDirty = false;
367 $backend->addData( [ 'foo' => '1' ] );
368 $this->assertSame( [ 'foo' => '1' ], $priv->data );
369 $this->assertTrue( $priv->dataDirty );
370
371 $priv->data = [ 'foo' => 1 ];
372 $priv->dataDirty = false;
373 $backend->addData( [ 'bar' => 2 ] );
374 $this->assertSame( [ 'foo' => 1, 'bar' => 2 ], $priv->data );
375 $this->assertTrue( $priv->dataDirty );
376 }
377
378 public function testDelaySave() {
379 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
380 $backend = $this->getBackend();
381 $priv = \TestingAccessWrapper::newFromObject( $backend );
382 $priv->persist = true;
383
384 // Saves happen normally when no delay is in effect
385 $this->onSessionMetadataCalled = false;
386 $priv->metaDirty = true;
387 $backend->save();
388 $this->assertTrue( $this->onSessionMetadataCalled, 'sanity check' );
389
390 $this->onSessionMetadataCalled = false;
391 $priv->metaDirty = true;
392 $priv->autosave();
393 $this->assertTrue( $this->onSessionMetadataCalled, 'sanity check' );
394
395 $delay = $backend->delaySave();
396
397 // Autosave doesn't happen when no delay is in effect
398 $this->onSessionMetadataCalled = false;
399 $priv->metaDirty = true;
400 $priv->autosave();
401 $this->assertFalse( $this->onSessionMetadataCalled );
402
403 // Save still does happen when no delay is in effect
404 $priv->save();
405 $this->assertTrue( $this->onSessionMetadataCalled );
406
407 // Save happens when delay is consumed
408 $this->onSessionMetadataCalled = false;
409 $priv->metaDirty = true;
410 \ScopedCallback::consume( $delay );
411 $this->assertTrue( $this->onSessionMetadataCalled );
412
413 // Test multiple delays
414 $delay1 = $backend->delaySave();
415 $delay2 = $backend->delaySave();
416 $delay3 = $backend->delaySave();
417 $this->onSessionMetadataCalled = false;
418 $priv->metaDirty = true;
419 $priv->autosave();
420 $this->assertFalse( $this->onSessionMetadataCalled );
421 \ScopedCallback::consume( $delay3 );
422 $this->assertFalse( $this->onSessionMetadataCalled );
423 \ScopedCallback::consume( $delay1 );
424 $this->assertFalse( $this->onSessionMetadataCalled );
425 \ScopedCallback::consume( $delay2 );
426 $this->assertTrue( $this->onSessionMetadataCalled );
427 }
428
429 public function testSave() {
430 $user = User::newFromName( 'UTSysop' );
431 $this->store = new TestBagOStuff();
432 $testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
433
434 $neverHook = $this->getMock( __CLASS__, [ 'onSessionMetadata' ] );
435 $neverHook->expects( $this->never() )->method( 'onSessionMetadata' );
436
437 $neverProvider = $this->getMock( 'DummySessionProvider', [ 'persistSession' ] );
438 $neverProvider->expects( $this->never() )->method( 'persistSession' );
439
440 // Not persistent or dirty
441 $this->provider = $neverProvider;
442 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
443 $this->store->setSessionData( self::SESSIONID, $testData );
444 $backend = $this->getBackend( $user );
445 $this->store->deleteSession( self::SESSIONID );
446 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
447 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
448 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
449 $backend->save();
450 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
451
452 // Not persistent, but dirty
453 $this->provider = $neverProvider;
454 $this->onSessionMetadataCalled = false;
455 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
456 $this->store->setSessionData( self::SESSIONID, $testData );
457 $backend = $this->getBackend( $user );
458 $this->store->deleteSession( self::SESSIONID );
459 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
460 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
461 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = true;
462 $backend->save();
463 $this->assertTrue( $this->onSessionMetadataCalled );
464 $blob = $this->store->getSession( self::SESSIONID );
465 $this->assertInternalType( 'array', $blob );
466 $this->assertArrayHasKey( 'metadata', $blob );
467 $metadata = $blob['metadata'];
468 $this->assertInternalType( 'array', $metadata );
469 $this->assertArrayHasKey( '???', $metadata );
470 $this->assertSame( '!!!', $metadata['???'] );
471 $this->assertFalse( $this->store->getSessionFromBackend( self::SESSIONID ),
472 'making sure it didn\'t save to backend' );
473
474 // Persistent, not dirty
475 $this->provider = $neverProvider;
476 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
477 $this->store->setSessionData( self::SESSIONID, $testData );
478 $backend = $this->getBackend( $user );
479 $this->store->deleteSession( self::SESSIONID );
480 \TestingAccessWrapper::newFromObject( $backend )->persist = true;
481 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
482 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
483 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
484 $backend->save();
485 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
486
487 $this->provider = $this->getMock( 'DummySessionProvider', [ 'persistSession' ] );
488 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
489 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
490 $this->store->setSessionData( self::SESSIONID, $testData );
491 $backend = $this->getBackend( $user );
492 $this->store->deleteSession( self::SESSIONID );
493 \TestingAccessWrapper::newFromObject( $backend )->persist = true;
494 \TestingAccessWrapper::newFromObject( $backend )->forcePersist = true;
495 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
496 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
497 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
498 $backend->save();
499 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
500
501 // Persistent and dirty
502 $this->provider = $neverProvider;
503 $this->onSessionMetadataCalled = false;
504 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
505 $this->store->setSessionData( self::SESSIONID, $testData );
506 $backend = $this->getBackend( $user );
507 $this->store->deleteSession( self::SESSIONID );
508 \TestingAccessWrapper::newFromObject( $backend )->persist = true;
509 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
510 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
511 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = true;
512 $backend->save();
513 $this->assertTrue( $this->onSessionMetadataCalled );
514 $blob = $this->store->getSession( self::SESSIONID );
515 $this->assertInternalType( 'array', $blob );
516 $this->assertArrayHasKey( 'metadata', $blob );
517 $metadata = $blob['metadata'];
518 $this->assertInternalType( 'array', $metadata );
519 $this->assertArrayHasKey( '???', $metadata );
520 $this->assertSame( '!!!', $metadata['???'] );
521 $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
522 'making sure it did save to backend' );
523
524 $this->provider = $this->getMock( 'DummySessionProvider', [ 'persistSession' ] );
525 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
526 $this->onSessionMetadataCalled = false;
527 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
528 $this->store->setSessionData( self::SESSIONID, $testData );
529 $backend = $this->getBackend( $user );
530 $this->store->deleteSession( self::SESSIONID );
531 \TestingAccessWrapper::newFromObject( $backend )->persist = true;
532 \TestingAccessWrapper::newFromObject( $backend )->forcePersist = true;
533 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
534 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
535 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = true;
536 $backend->save();
537 $this->assertTrue( $this->onSessionMetadataCalled );
538 $blob = $this->store->getSession( self::SESSIONID );
539 $this->assertInternalType( 'array', $blob );
540 $this->assertArrayHasKey( 'metadata', $blob );
541 $metadata = $blob['metadata'];
542 $this->assertInternalType( 'array', $metadata );
543 $this->assertArrayHasKey( '???', $metadata );
544 $this->assertSame( '!!!', $metadata['???'] );
545 $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
546 'making sure it did save to backend' );
547
548 $this->provider = $this->getMock( 'DummySessionProvider', [ 'persistSession' ] );
549 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
550 $this->onSessionMetadataCalled = false;
551 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
552 $this->store->setSessionData( self::SESSIONID, $testData );
553 $backend = $this->getBackend( $user );
554 $this->store->deleteSession( self::SESSIONID );
555 \TestingAccessWrapper::newFromObject( $backend )->persist = true;
556 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
557 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = true;
558 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
559 $backend->save();
560 $this->assertTrue( $this->onSessionMetadataCalled );
561 $blob = $this->store->getSession( self::SESSIONID );
562 $this->assertInternalType( 'array', $blob );
563 $this->assertArrayHasKey( 'metadata', $blob );
564 $metadata = $blob['metadata'];
565 $this->assertInternalType( 'array', $metadata );
566 $this->assertArrayHasKey( '???', $metadata );
567 $this->assertSame( '!!!', $metadata['???'] );
568 $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
569 'making sure it did save to backend' );
570
571 // Not marked dirty, but dirty data
572 // (e.g. indirect modification from ArrayAccess::offsetGet)
573 $this->provider = $neverProvider;
574 $this->onSessionMetadataCalled = false;
575 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
576 $this->store->setSessionData( self::SESSIONID, $testData );
577 $backend = $this->getBackend( $user );
578 $this->store->deleteSession( self::SESSIONID );
579 \TestingAccessWrapper::newFromObject( $backend )->persist = true;
580 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
581 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
582 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
583 \TestingAccessWrapper::newFromObject( $backend )->dataHash = 'Doesn\'t match';
584 $backend->save();
585 $this->assertTrue( $this->onSessionMetadataCalled );
586 $blob = $this->store->getSession( self::SESSIONID );
587 $this->assertInternalType( 'array', $blob );
588 $this->assertArrayHasKey( 'metadata', $blob );
589 $metadata = $blob['metadata'];
590 $this->assertInternalType( 'array', $metadata );
591 $this->assertArrayHasKey( '???', $metadata );
592 $this->assertSame( '!!!', $metadata['???'] );
593 $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
594 'making sure it did save to backend' );
595
596 // Bad hook
597 $this->provider = null;
598 $mockHook = $this->getMock( __CLASS__, [ 'onSessionMetadata' ] );
599 $mockHook->expects( $this->any() )->method( 'onSessionMetadata' )
600 ->will( $this->returnCallback(
601 function ( SessionBackend $backend, array &$metadata, array $requests ) {
602 $metadata['userId']++;
603 }
604 ) );
605 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $mockHook ] ] );
606 $this->store->setSessionData( self::SESSIONID, $testData );
607 $backend = $this->getBackend( $user );
608 $backend->dirty();
609 try {
610 $backend->save();
611 $this->fail( 'Expected exception not thrown' );
612 } catch ( \UnexpectedValueException $ex ) {
613 $this->assertSame(
614 'SessionMetadata hook changed metadata key "userId"',
615 $ex->getMessage()
616 );
617 }
618
619 // SessionManager::preventSessionsForUser
620 \TestingAccessWrapper::newFromObject( $this->manager )->preventUsers = [
621 $user->getName() => true,
622 ];
623 $this->provider = $neverProvider;
624 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $neverHook ] ] );
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 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
630 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = true;
631 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = true;
632 $backend->save();
633 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
634 }
635
636 public function testRenew() {
637 $user = User::newFromName( 'UTSysop' );
638 $this->store = new TestBagOStuff();
639 $testData = [ 'foo' => 'foo!', 'bar', [ 'baz', null ] ];
640
641 // Not persistent
642 $this->provider = $this->getMock( 'DummySessionProvider', [ 'persistSession' ] );
643 $this->provider->expects( $this->never() )->method( 'persistSession' );
644 $this->onSessionMetadataCalled = false;
645 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
646 $this->store->setSessionData( self::SESSIONID, $testData );
647 $backend = $this->getBackend( $user );
648 $this->store->deleteSession( self::SESSIONID );
649 $wrap = \TestingAccessWrapper::newFromObject( $backend );
650 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
651 $wrap->metaDirty = false;
652 $wrap->dataDirty = false;
653 $wrap->forcePersist = false;
654 $wrap->expires = 0;
655 $backend->renew();
656 $this->assertTrue( $this->onSessionMetadataCalled );
657 $blob = $this->store->getSession( self::SESSIONID );
658 $this->assertInternalType( 'array', $blob );
659 $this->assertArrayHasKey( 'metadata', $blob );
660 $metadata = $blob['metadata'];
661 $this->assertInternalType( 'array', $metadata );
662 $this->assertArrayHasKey( '???', $metadata );
663 $this->assertSame( '!!!', $metadata['???'] );
664 $this->assertNotEquals( 0, $wrap->expires );
665
666 // Persistent
667 $this->provider = $this->getMock( 'DummySessionProvider', [ 'persistSession' ] );
668 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
669 $this->onSessionMetadataCalled = false;
670 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
671 $this->store->setSessionData( self::SESSIONID, $testData );
672 $backend = $this->getBackend( $user );
673 $this->store->deleteSession( self::SESSIONID );
674 $wrap = \TestingAccessWrapper::newFromObject( $backend );
675 $wrap->persist = true;
676 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
677 $wrap->metaDirty = false;
678 $wrap->dataDirty = false;
679 $wrap->forcePersist = false;
680 $wrap->expires = 0;
681 $backend->renew();
682 $this->assertTrue( $this->onSessionMetadataCalled );
683 $blob = $this->store->getSession( self::SESSIONID );
684 $this->assertInternalType( 'array', $blob );
685 $this->assertArrayHasKey( 'metadata', $blob );
686 $metadata = $blob['metadata'];
687 $this->assertInternalType( 'array', $metadata );
688 $this->assertArrayHasKey( '???', $metadata );
689 $this->assertSame( '!!!', $metadata['???'] );
690 $this->assertNotEquals( 0, $wrap->expires );
691
692 // Not persistent, not expiring
693 $this->provider = $this->getMock( 'DummySessionProvider', [ 'persistSession' ] );
694 $this->provider->expects( $this->never() )->method( 'persistSession' );
695 $this->onSessionMetadataCalled = false;
696 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'SessionMetadata' => [ $this ] ] );
697 $this->store->setSessionData( self::SESSIONID, $testData );
698 $backend = $this->getBackend( $user );
699 $this->store->deleteSession( self::SESSIONID );
700 $wrap = \TestingAccessWrapper::newFromObject( $backend );
701 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
702 $wrap->metaDirty = false;
703 $wrap->dataDirty = false;
704 $wrap->forcePersist = false;
705 $expires = time() + $wrap->lifetime + 100;
706 $wrap->expires = $expires;
707 $backend->renew();
708 $this->assertFalse( $this->onSessionMetadataCalled );
709 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
710 $this->assertEquals( $expires, $wrap->expires );
711 }
712
713 public function onSessionMetadata( SessionBackend $backend, array &$metadata, array $requests ) {
714 $this->onSessionMetadataCalled = true;
715 $metadata['???'] = '!!!';
716 }
717
718 public function testResetIdOfGlobalSession() {
719 if ( !PHPSessionHandler::isInstalled() ) {
720 PHPSessionHandler::install( SessionManager::singleton() );
721 }
722 if ( !PHPSessionHandler::isEnabled() ) {
723 $rProp = new \ReflectionProperty( 'MediaWiki\\Session\\PHPSessionHandler', 'instance' );
724 $rProp->setAccessible( true );
725 $handler = \TestingAccessWrapper::newFromObject( $rProp->getValue() );
726 $resetHandler = new \ScopedCallback( function () use ( $handler ) {
727 session_write_close();
728 $handler->enable = false;
729 } );
730 $handler->enable = true;
731 }
732
733 $backend = $this->getBackend( User::newFromName( 'UTSysop' ) );
734 \TestingAccessWrapper::newFromObject( $backend )->usePhpSessionHandling = true;
735
736 TestUtils::setSessionManagerSingleton( $this->manager );
737
738 $manager = \TestingAccessWrapper::newFromObject( $this->manager );
739 $request = \RequestContext::getMain()->getRequest();
740 $manager->globalSession = $backend->getSession( $request );
741 $manager->globalSessionRequest = $request;
742
743 session_id( self::SESSIONID );
744 \MediaWiki\quietCall( 'session_start' );
745 $backend->resetId();
746 $this->assertNotEquals( self::SESSIONID, $backend->getId() );
747 $this->assertSame( $backend->getId(), session_id() );
748 session_write_close();
749
750 session_id( '' );
751 $this->assertNotSame( $backend->getId(), session_id(), 'sanity check' );
752 $backend->persist();
753 $this->assertSame( $backend->getId(), session_id() );
754 session_write_close();
755 }
756
757 public function testGetAllowedUserRights() {
758 $this->provider = $this->getMockBuilder( 'DummySessionProvider' )
759 ->setMethods( [ 'getAllowedUserRights' ] )
760 ->getMock();
761 $this->provider->expects( $this->any() )->method( 'getAllowedUserRights' )
762 ->will( $this->returnValue( [ 'foo', 'bar' ] ) );
763
764 $backend = $this->getBackend();
765 $this->assertSame( [ 'foo', 'bar' ], $backend->getAllowedUserRights() );
766 }
767
768 }