Merge "mediawiki.api.parse: Use formatversion=2 for API requests"
[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( array(
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, array(
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 = array( 100 => new \FauxRequest() );
66 $priv->usePhpSessionHandling = false;
67
68 $manager = \TestingAccessWrapper::newFromObject( $this->manager );
69 $manager->allSessionBackends = array( $backend->getId() => $backend );
70 $manager->allSessionIds = array( $backend->getId() => $id );
71 $manager->sessionProviders = array( (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, array(
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, array(
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, array(
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, array(
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, array( 'expires' => $expire ), 2 );
150
151 $info = new SessionInfo( SessionInfo::MIN_PRIORITY, array(
152 'provider' => $this->provider,
153 'id' => self::SESSIONID,
154 'persisted' => true,
155 'forceHTTPS' => true,
156 'metadata' => array( '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( array( 'foo' ), $backend->getProviderMetadata() );
171 }
172
173 public function testSessionStuff() {
174 $backend = $this->getBackend();
175 $priv = \TestingAccessWrapper::newFromObject( $backend );
176 $priv->requests = array(); // 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( array( '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', array( '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', array( '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( array(), $data );
344 $this->assertTrue( \TestingAccessWrapper::newFromObject( $backend )->dataDirty );
345 $data['???'] = '!!!';
346 $this->assertSame( array( '???' => '!!!' ), $data );
347
348 $testData = array( 'foo' => 'foo!', 'bar', array( '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 = array( 'foo' => 1 );
360 $priv->dataDirty = false;
361 $backend->addData( array( 'foo' => 1 ) );
362 $this->assertSame( array( 'foo' => 1 ), $priv->data );
363 $this->assertFalse( $priv->dataDirty );
364
365 $priv->data = array( 'foo' => 1 );
366 $priv->dataDirty = false;
367 $backend->addData( array( 'foo' => '1' ) );
368 $this->assertSame( array( 'foo' => '1' ), $priv->data );
369 $this->assertTrue( $priv->dataDirty );
370
371 $priv->data = array( 'foo' => 1 );
372 $priv->dataDirty = false;
373 $backend->addData( array( 'bar' => 2 ) );
374 $this->assertSame( array( 'foo' => 1, 'bar' => 2 ), $priv->data );
375 $this->assertTrue( $priv->dataDirty );
376 }
377
378 public function testDelaySave() {
379 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $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 = array( 'foo' => 'foo!', 'bar', array( 'baz', null ) );
433
434 $neverHook = $this->getMock( __CLASS__, array( 'onSessionMetadata' ) );
435 $neverHook->expects( $this->never() )->method( 'onSessionMetadata' );
436
437 $neverProvider = $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
438 $neverProvider->expects( $this->never() )->method( 'persistSession' );
439
440 // Not persistent or dirty
441 $this->provider = $neverProvider;
442 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $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', array( 'SessionMetadata' => array( $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', array( 'SessionMetadata' => array( $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', array( 'persistSession' ) );
488 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
489 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $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', array( 'SessionMetadata' => array( $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', array( 'persistSession' ) );
525 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
526 $this->onSessionMetadataCalled = false;
527 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $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', array( 'persistSession' ) );
549 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
550 $this->onSessionMetadataCalled = false;
551 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $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 $this->provider = $neverProvider;
573 $this->onSessionMetadataCalled = false;
574 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
575 $this->store->setSessionData( self::SESSIONID, $testData );
576 $backend = $this->getBackend( $user );
577 $this->store->deleteSession( self::SESSIONID );
578 \TestingAccessWrapper::newFromObject( $backend )->persist = true;
579 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
580 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = false;
581 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = false;
582 \TestingAccessWrapper::newFromObject( $backend )->dataHash = 'Doesn\'t match';
583 $backend->save();
584 $this->assertTrue( $this->onSessionMetadataCalled );
585 $blob = $this->store->getSession( self::SESSIONID );
586 $this->assertInternalType( 'array', $blob );
587 $this->assertArrayHasKey( 'metadata', $blob );
588 $metadata = $blob['metadata'];
589 $this->assertInternalType( 'array', $metadata );
590 $this->assertArrayHasKey( '???', $metadata );
591 $this->assertSame( '!!!', $metadata['???'] );
592 $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
593 'making sure it did save to backend' );
594
595 // Bad hook
596 $this->provider = null;
597 $mockHook = $this->getMock( __CLASS__, array( 'onSessionMetadata' ) );
598 $mockHook->expects( $this->any() )->method( 'onSessionMetadata' )
599 ->will( $this->returnCallback(
600 function ( SessionBackend $backend, array &$metadata, array $requests ) {
601 $metadata['userId']++;
602 }
603 ) );
604 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $mockHook ) ) );
605 $this->store->setSessionData( self::SESSIONID, $testData );
606 $backend = $this->getBackend( $user );
607 $backend->dirty();
608 try {
609 $backend->save();
610 $this->fail( 'Expected exception not thrown' );
611 } catch ( \UnexpectedValueException $ex ) {
612 $this->assertSame(
613 'SessionMetadata hook changed metadata key "userId"',
614 $ex->getMessage()
615 );
616 }
617
618 // SessionManager::preventSessionsForUser
619 \TestingAccessWrapper::newFromObject( $this->manager )->preventUsers = array(
620 $user->getName() => true,
621 );
622 $this->provider = $neverProvider;
623 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $neverHook ) ) );
624 $this->store->setSessionData( self::SESSIONID, $testData );
625 $backend = $this->getBackend( $user );
626 $this->store->deleteSession( self::SESSIONID );
627 \TestingAccessWrapper::newFromObject( $backend )->persist = true;
628 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
629 \TestingAccessWrapper::newFromObject( $backend )->metaDirty = true;
630 \TestingAccessWrapper::newFromObject( $backend )->dataDirty = true;
631 $backend->save();
632 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
633 }
634
635 public function testRenew() {
636 $user = User::newFromName( 'UTSysop' );
637 $this->store = new TestBagOStuff();
638 $testData = array( 'foo' => 'foo!', 'bar', array( 'baz', null ) );
639
640 // Not persistent
641 $this->provider = $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
642 $this->provider->expects( $this->never() )->method( 'persistSession' );
643 $this->onSessionMetadataCalled = false;
644 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
645 $this->store->setSessionData( self::SESSIONID, $testData );
646 $backend = $this->getBackend( $user );
647 $this->store->deleteSession( self::SESSIONID );
648 $wrap = \TestingAccessWrapper::newFromObject( $backend );
649 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
650 $wrap->metaDirty = false;
651 $wrap->dataDirty = false;
652 $wrap->forcePersist = false;
653 $wrap->expires = 0;
654 $backend->renew();
655 $this->assertTrue( $this->onSessionMetadataCalled );
656 $blob = $this->store->getSession( self::SESSIONID );
657 $this->assertInternalType( 'array', $blob );
658 $this->assertArrayHasKey( 'metadata', $blob );
659 $metadata = $blob['metadata'];
660 $this->assertInternalType( 'array', $metadata );
661 $this->assertArrayHasKey( '???', $metadata );
662 $this->assertSame( '!!!', $metadata['???'] );
663 $this->assertNotEquals( 0, $wrap->expires );
664
665 // Persistent
666 $this->provider = $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
667 $this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
668 $this->onSessionMetadataCalled = false;
669 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
670 $this->store->setSessionData( self::SESSIONID, $testData );
671 $backend = $this->getBackend( $user );
672 $this->store->deleteSession( self::SESSIONID );
673 $wrap = \TestingAccessWrapper::newFromObject( $backend );
674 $wrap->persist = true;
675 $this->assertTrue( $backend->isPersistent(), 'sanity check' );
676 $wrap->metaDirty = false;
677 $wrap->dataDirty = false;
678 $wrap->forcePersist = false;
679 $wrap->expires = 0;
680 $backend->renew();
681 $this->assertTrue( $this->onSessionMetadataCalled );
682 $blob = $this->store->getSession( self::SESSIONID );
683 $this->assertInternalType( 'array', $blob );
684 $this->assertArrayHasKey( 'metadata', $blob );
685 $metadata = $blob['metadata'];
686 $this->assertInternalType( 'array', $metadata );
687 $this->assertArrayHasKey( '???', $metadata );
688 $this->assertSame( '!!!', $metadata['???'] );
689 $this->assertNotEquals( 0, $wrap->expires );
690
691 // Not persistent, not expiring
692 $this->provider = $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
693 $this->provider->expects( $this->never() )->method( 'persistSession' );
694 $this->onSessionMetadataCalled = false;
695 $this->mergeMwGlobalArrayValue( 'wgHooks', array( 'SessionMetadata' => array( $this ) ) );
696 $this->store->setSessionData( self::SESSIONID, $testData );
697 $backend = $this->getBackend( $user );
698 $this->store->deleteSession( self::SESSIONID );
699 $wrap = \TestingAccessWrapper::newFromObject( $backend );
700 $this->assertFalse( $backend->isPersistent(), 'sanity check' );
701 $wrap->metaDirty = false;
702 $wrap->dataDirty = false;
703 $wrap->forcePersist = false;
704 $expires = time() + $wrap->lifetime + 100;
705 $wrap->expires = $expires;
706 $backend->renew();
707 $this->assertFalse( $this->onSessionMetadataCalled );
708 $this->assertFalse( $this->store->getSession( self::SESSIONID ), 'making sure it didn\'t save' );
709 $this->assertEquals( $expires, $wrap->expires );
710 }
711
712 public function onSessionMetadata( SessionBackend $backend, array &$metadata, array $requests ) {
713 $this->onSessionMetadataCalled = true;
714 $metadata['???'] = '!!!';
715 }
716
717 public function testResetIdOfGlobalSession() {
718 if ( !PHPSessionHandler::isInstalled() ) {
719 PHPSessionHandler::install( SessionManager::singleton() );
720 }
721 if ( !PHPSessionHandler::isEnabled() ) {
722 $rProp = new \ReflectionProperty( 'MediaWiki\\Session\\PHPSessionHandler', 'instance' );
723 $rProp->setAccessible( true );
724 $handler = \TestingAccessWrapper::newFromObject( $rProp->getValue() );
725 $resetHandler = new \ScopedCallback( function () use ( $handler ) {
726 session_write_close();
727 $handler->enable = false;
728 } );
729 $handler->enable = true;
730 }
731
732 $backend = $this->getBackend( User::newFromName( 'UTSysop' ) );
733 \TestingAccessWrapper::newFromObject( $backend )->usePhpSessionHandling = true;
734
735 TestUtils::setSessionManagerSingleton( $this->manager );
736
737 $manager = \TestingAccessWrapper::newFromObject( $this->manager );
738 $request = \RequestContext::getMain()->getRequest();
739 $manager->globalSession = $backend->getSession( $request );
740 $manager->globalSessionRequest = $request;
741
742 session_id( self::SESSIONID );
743 \MediaWiki\quietCall( 'session_start' );
744 $backend->resetId();
745 $this->assertNotEquals( self::SESSIONID, $backend->getId() );
746 $this->assertSame( $backend->getId(), session_id() );
747 session_write_close();
748
749 session_id( '' );
750 $this->assertNotSame( $backend->getId(), session_id(), 'sanity check' );
751 $backend->persist();
752 $this->assertSame( $backend->getId(), session_id() );
753 session_write_close();
754 }
755
756 public function testGetAllowedUserRights() {
757 $this->provider = $this->getMockBuilder( 'DummySessionProvider' )
758 ->setMethods( array( 'getAllowedUserRights' ) )
759 ->getMock();
760 $this->provider->expects( $this->any() )->method( 'getAllowedUserRights' )
761 ->will( $this->returnValue( array( 'foo', 'bar' ) ) );
762
763 $backend = $this->getBackend();
764 $this->assertSame( array( 'foo', 'bar' ), $backend->getAllowedUserRights() );
765 }
766
767 }