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