MediaWiki  master
PHPSessionHandlerTest.php
Go to the documentation of this file.
1 <?php
2 
3 namespace MediaWiki\Session;
4 
8 
14 
15  private function getResetter( &$rProp = null ) {
16  $reset = [];
17 
18  $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' );
19  $rProp->setAccessible( true );
20  if ( $rProp->getValue() ) {
21  $old = TestingAccessWrapper::newFromObject( $rProp->getValue() );
22  $oldManager = $old->manager;
23  $oldStore = $old->store;
24  $oldLogger = $old->logger;
25  $reset[] = new \Wikimedia\ScopedCallback(
26  [ PHPSessionHandler::class, 'install' ],
27  [ $oldManager, $oldStore, $oldLogger ]
28  );
29  }
30 
31  return $reset;
32  }
33 
34  public function testEnableFlags() {
35  $handler = TestingAccessWrapper::newFromObject(
36  $this->getMockBuilder( PHPSessionHandler::class )
37  ->setMethods( null )
38  ->disableOriginalConstructor()
39  ->getMock()
40  );
41 
42  $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' );
43  $rProp->setAccessible( true );
44  $reset = new \Wikimedia\ScopedCallback( [ $rProp, 'setValue' ], [ $rProp->getValue() ] );
45  $rProp->setValue( $handler );
46 
47  $handler->setEnableFlags( 'enable' );
48  $this->assertTrue( $handler->enable );
49  $this->assertFalse( $handler->warn );
50  $this->assertTrue( PHPSessionHandler::isEnabled() );
51 
52  $handler->setEnableFlags( 'warn' );
53  $this->assertTrue( $handler->enable );
54  $this->assertTrue( $handler->warn );
55  $this->assertTrue( PHPSessionHandler::isEnabled() );
56 
57  $handler->setEnableFlags( 'disable' );
58  $this->assertFalse( $handler->enable );
59  $this->assertFalse( PHPSessionHandler::isEnabled() );
60 
61  $rProp->setValue( null );
62  $this->assertFalse( PHPSessionHandler::isEnabled() );
63  }
64 
65  public function testInstall() {
66  $reset = $this->getResetter( $rProp );
67  $rProp->setValue( null );
68 
69  session_write_close();
70  ini_set( 'session.use_cookies', 1 );
71  ini_set( 'session.use_trans_sid', 1 );
72 
73  $store = new TestBagOStuff();
74  $logger = new \TestLogger();
75  $manager = new SessionManager( [
76  'store' => $store,
77  'logger' => $logger,
78  ] );
79 
80  $this->assertFalse( PHPSessionHandler::isInstalled() );
81  PHPSessionHandler::install( $manager );
82  $this->assertTrue( PHPSessionHandler::isInstalled() );
83 
84  $this->assertFalse( wfIniGetBool( 'session.use_cookies' ) );
85  $this->assertFalse( wfIniGetBool( 'session.use_trans_sid' ) );
86 
87  $this->assertNotNull( $rProp->getValue() );
88  $priv = TestingAccessWrapper::newFromObject( $rProp->getValue() );
89  $this->assertSame( $manager, $priv->manager );
90  $this->assertSame( $store, $priv->store );
91  $this->assertSame( $logger, $priv->logger );
92  }
93 
98  public function testSessionHandling( $handler ) {
99  $this->hideDeprecated( '$_SESSION' );
100  $reset[] = $this->getResetter( $rProp );
101 
102  $this->setMwGlobals( [
103  'wgSessionProviders' => [ [ 'class' => \DummySessionProvider::class ] ],
104  'wgObjectCacheSessionExpiry' => 2,
105  ] );
106 
107  $store = new TestBagOStuff();
108  $logger = new \TestLogger( true, function ( $m ) {
109  // Discard all log events starting with expected prefix
110  return preg_match( '/^SessionBackend "\{session\}" /', $m ) ? null : $m;
111  } );
112  $manager = new SessionManager( [
113  'store' => $store,
114  'logger' => $logger,
115  ] );
116  PHPSessionHandler::install( $manager );
117  $wrap = TestingAccessWrapper::newFromObject( $rProp->getValue() );
118  $reset[] = new \Wikimedia\ScopedCallback(
119  [ $wrap, 'setEnableFlags' ],
120  [ $wrap->enable ? ( $wrap->warn ? 'warn' : 'enable' ) : 'disable' ]
121  );
122  $wrap->setEnableFlags( 'warn' );
123 
124  \Wikimedia\suppressWarnings();
125  ini_set( 'session.serialize_handler', $handler );
126  \Wikimedia\restoreWarnings();
127  if ( ini_get( 'session.serialize_handler' ) !== $handler ) {
128  $this->markTestSkipped( "Cannot set session.serialize_handler to \"$handler\"" );
129  }
130 
131  // Session IDs for testing
132  $sessionA = str_repeat( 'a', 32 );
133  $sessionB = str_repeat( 'b', 32 );
134  $sessionC = str_repeat( 'c', 32 );
135 
136  // Set up garbage data in the session
137  $_SESSION['AuthenticationSessionTest'] = 'bogus';
138 
139  session_id( $sessionA );
140  session_start();
141  $this->assertSame( [], $_SESSION );
142  $this->assertSame( $sessionA, session_id() );
143 
144  // Set some data in the session so we can see if it works.
145  $rand = mt_rand();
146  $_SESSION['AuthenticationSessionTest'] = $rand;
147  $expect = [ 'AuthenticationSessionTest' => $rand ];
148  session_write_close();
149  $this->assertSame( [
150  [ LogLevel::WARNING, 'Something wrote to $_SESSION!' ],
151  ], $logger->getBuffer() );
152 
153  // Screw up $_SESSION so we can tell the difference between "this
154  // worked" and "this did nothing"
155  $_SESSION['AuthenticationSessionTest'] = 'bogus';
156 
157  // Re-open the session and see that data was actually reloaded
158  session_start();
159  $this->assertSame( $expect, $_SESSION );
160 
161  // Make sure session_reset() works too.
162  if ( function_exists( 'session_reset' ) ) {
163  $_SESSION['AuthenticationSessionTest'] = 'bogus';
164  session_reset();
165  $this->assertSame( $expect, $_SESSION );
166  }
167 
168  // Re-fill the session, then test that session_destroy() works.
169  $_SESSION['AuthenticationSessionTest'] = $rand;
170  session_write_close();
171  session_start();
172  $this->assertSame( $expect, $_SESSION );
173  session_destroy();
174  session_id( $sessionA );
175  session_start();
176  $this->assertSame( [], $_SESSION );
177  session_write_close();
178 
179  // Test that our session handler won't clone someone else's session
180  session_id( $sessionB );
181  session_start();
182  $this->assertSame( $sessionB, session_id() );
183  $_SESSION['id'] = 'B';
184  session_write_close();
185 
186  session_id( $sessionC );
187  session_start();
188  $this->assertSame( [], $_SESSION );
189  $_SESSION['id'] = 'C';
190  session_write_close();
191 
192  session_id( $sessionB );
193  session_start();
194  $this->assertSame( [ 'id' => 'B' ], $_SESSION );
195  session_write_close();
196 
197  session_id( $sessionC );
198  session_start();
199  $this->assertSame( [ 'id' => 'C' ], $_SESSION );
200  session_destroy();
201 
202  session_id( $sessionB );
203  session_start();
204  $this->assertSame( [ 'id' => 'B' ], $_SESSION );
205 
206  // Test merging between Session and $_SESSION
207  session_write_close();
208 
209  $session = $manager->getEmptySession();
210  $session->set( 'Unchanged', 'setup' );
211  $session->set( 'Unchanged, null', null );
212  $session->set( 'Changed in $_SESSION', 'setup' );
213  $session->set( 'Changed in Session', 'setup' );
214  $session->set( 'Changed in both', 'setup' );
215  $session->set( 'Deleted in Session', 'setup' );
216  $session->set( 'Deleted in $_SESSION', 'setup' );
217  $session->set( 'Deleted in both', 'setup' );
218  $session->set( 'Deleted in Session, changed in $_SESSION', 'setup' );
219  $session->set( 'Deleted in $_SESSION, changed in Session', 'setup' );
220  $session->persist();
221  $session->save();
222 
223  session_id( $session->getId() );
224  session_start();
225  $session->set( 'Added in Session', 'Session' );
226  $session->set( 'Added in both', 'Session' );
227  $session->set( 'Changed in Session', 'Session' );
228  $session->set( 'Changed in both', 'Session' );
229  $session->set( 'Deleted in $_SESSION, changed in Session', 'Session' );
230  $session->remove( 'Deleted in Session' );
231  $session->remove( 'Deleted in both' );
232  $session->remove( 'Deleted in Session, changed in $_SESSION' );
233  $session->save();
234  $_SESSION['Added in $_SESSION'] = '$_SESSION';
235  $_SESSION['Added in both'] = '$_SESSION';
236  $_SESSION['Changed in $_SESSION'] = '$_SESSION';
237  $_SESSION['Changed in both'] = '$_SESSION';
238  $_SESSION['Deleted in Session, changed in $_SESSION'] = '$_SESSION';
239  unset( $_SESSION['Deleted in $_SESSION'] );
240  unset( $_SESSION['Deleted in both'] );
241  unset( $_SESSION['Deleted in $_SESSION, changed in Session'] );
242  session_write_close();
243 
244  $this->assertEquals( [
245  'Added in Session' => 'Session',
246  'Added in $_SESSION' => '$_SESSION',
247  'Added in both' => 'Session',
248  'Unchanged' => 'setup',
249  'Unchanged, null' => null,
250  'Changed in Session' => 'Session',
251  'Changed in $_SESSION' => '$_SESSION',
252  'Changed in both' => 'Session',
253  'Deleted in Session, changed in $_SESSION' => '$_SESSION',
254  'Deleted in $_SESSION, changed in Session' => 'Session',
255  ], iterator_to_array( $session ) );
256 
257  $session->clear();
258  $session->set( 42, 'forty-two' );
259  $session->set( 'forty-two', 42 );
260  $session->set( 'wrong', 43 );
261  $session->persist();
262  $session->save();
263 
264  session_start();
265  $this->assertArrayHasKey( 'forty-two', $_SESSION );
266  $this->assertSame( 42, $_SESSION['forty-two'] );
267  $this->assertArrayHasKey( 'wrong', $_SESSION );
268  unset( $_SESSION['wrong'] );
269  session_write_close();
270 
271  $this->assertEquals( [
272  42 => 'forty-two',
273  'forty-two' => 42,
274  ], iterator_to_array( $session ) );
275 
276  // Test that write doesn't break if the session is invalid
277  $session = $manager->getEmptySession();
278  $session->persist();
279  $id = $session->getId();
280  unset( $session );
281  session_id( $id );
282  session_start();
283  $this->mergeMwGlobalArrayValue( 'wgHooks', [
284  'SessionCheckInfo' => [ function ( &$reason ) {
285  $reason = 'Testing';
286  return false;
287  } ],
288  ] );
289  $this->assertNull( $manager->getSessionById( $id, true ), 'sanity check' );
290  session_write_close();
291 
292  $this->mergeMwGlobalArrayValue( 'wgHooks', [
293  'SessionCheckInfo' => [],
294  ] );
295  $this->assertNotNull( $manager->getSessionById( $id, true ), 'sanity check' );
296  }
297 
298  public static function provideHandlers() {
299  return [
300  [ 'php' ],
301  [ 'php_binary' ],
302  [ 'php_serialize' ],
303  ];
304  }
305 
311  public function testDisabled( $method, $args ) {
312  $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' );
313  $rProp->setAccessible( true );
314  $handler = $this->getMockBuilder( PHPSessionHandler::class )
315  ->setMethods( null )
316  ->disableOriginalConstructor()
317  ->getMock();
318  TestingAccessWrapper::newFromObject( $handler )->setEnableFlags( 'disable' );
319  $oldValue = $rProp->getValue();
320  $rProp->setValue( $handler );
321  $reset = new \Wikimedia\ScopedCallback( [ $rProp, 'setValue' ], [ $oldValue ] );
322 
323  call_user_func_array( [ $handler, $method ], $args );
324  }
325 
326  public static function provideDisabled() {
327  return [
328  [ 'open', [ '', '' ] ],
329  [ 'read', [ '' ] ],
330  [ 'write', [ '', '' ] ],
331  [ 'destroy', [ '' ] ],
332  ];
333  }
334 
340  public function testWrongInstance( $method, $args ) {
341  $handler = $this->getMockBuilder( PHPSessionHandler::class )
342  ->setMethods( null )
343  ->disableOriginalConstructor()
344  ->getMock();
345  TestingAccessWrapper::newFromObject( $handler )->setEnableFlags( 'enable' );
346 
347  call_user_func_array( [ $handler, $method ], $args );
348  }
349 
350  public static function provideWrongInstance() {
351  return [
352  [ 'open', [ '', '' ] ],
353  [ 'close', [] ],
354  [ 'read', [ '' ] ],
355  [ 'write', [ '', '' ] ],
356  [ 'destroy', [ '' ] ],
357  [ 'gc', [ 0 ] ],
358  ];
359  }
360 
361 }
testSessionHandling( $handler)
provideHandlers
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static install(SessionManagerInterface $manager)
Install a session handler for the current web request.
Session MediaWiki\Session\PHPSessionHandler.
testDisabled( $method, $args)
provideDisabled BadMethodCallException Attempt to use PHP session management
if( $line===false) $args
Definition: cdb.php:64
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:767
BagOStuff with utility functions for MediaWiki\\Session\\* testing.
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:767
static isInstalled()
Test whether the handler is installed.
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
testWrongInstance( $method, $args)
provideWrongInstance UnexpectedValueException /: Wrong instance called!$/
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
This serves as the entry point to the MediaWiki session handling system.
wfIniGetBool( $setting)
Safety wrapper around ini_get() for boolean settings.