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