3 namespace MediaWiki\Session
;
10 * @covers MediaWiki\Session\PHPSessionHandler
12 class PHPSessionHandlerTest
extends MediaWikiTestCase
{
14 private function getResetter( &$rProp = null ) {
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 ) ) {
24 $reset[] = new \
ScopedCallback( 'restore_error_handler' );
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 )
42 public function testEnableFlags() {
43 $handler = \TestingAccessWrapper
::newFromObject(
44 $this->getMockBuilder( 'MediaWiki\\Session\\PHPSessionHandler' )
46 ->disableOriginalConstructor()
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 );
55 $handler->setEnableFlags( 'enable' );
56 $this->assertTrue( $handler->enable
);
57 $this->assertFalse( $handler->warn
);
58 $this->assertTrue( PHPSessionHandler
::isEnabled() );
60 $handler->setEnableFlags( 'warn' );
61 $this->assertTrue( $handler->enable
);
62 $this->assertTrue( $handler->warn
);
63 $this->assertTrue( PHPSessionHandler
::isEnabled() );
65 $handler->setEnableFlags( 'disable' );
66 $this->assertFalse( $handler->enable
);
67 $this->assertFalse( PHPSessionHandler
::isEnabled() );
69 $rProp->setValue( null );
70 $this->assertFalse( PHPSessionHandler
::isEnabled() );
73 public function testInstall() {
74 $reset = $this->getResetter( $rProp );
75 $rProp->setValue( null );
77 session_write_close();
78 ini_set( 'session.use_cookies', 1 );
79 ini_set( 'session.use_trans_sid', 1 );
81 $store = new \
HashBagOStuff();
82 $logger = new \
TestLogger();
83 $manager = new SessionManager( array(
88 $this->assertFalse( PHPSessionHandler
::isInstalled() );
89 PHPSessionHandler
::install( $manager );
90 $this->assertTrue( PHPSessionHandler
::isInstalled() );
92 $this->assertFalse( wfIniGetBool( 'session.use_cookies' ) );
93 $this->assertFalse( wfIniGetBool( 'session.use_trans_sid' ) );
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
);
103 * @dataProvider provideHandlers
104 * @param string $handler php serialize_handler to use
106 public function testSessionHandling( $handler ) {
107 $this->hideDeprecated( '$_SESSION' );
108 $reset[] = $this->getResetter( $rProp );
110 $this->setMwGlobals( array(
111 'wgSessionProviders' => array( array( 'class' => 'DummySessionProvider' ) ),
112 'wgObjectCacheSessionExpiry' => 2,
115 $store = new \
HashBagOStuff();
116 $logger = new \
TestLogger( true, function ( $m ) {
117 return preg_match( '/^SessionBackend a{32} /', $m ) ?
null : $m;
119 $manager = new SessionManager( array(
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' )
129 $wrap->setEnableFlags( 'warn' );
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\"" );
138 // Session IDs for testing
139 $sessionA = str_repeat( 'a', 32 );
140 $sessionB = str_repeat( 'b', 32 );
141 $sessionC = str_repeat( 'c', 32 );
143 // Set up garbage data in the session
144 $_SESSION['AuthenticationSessionTest'] = 'bogus';
146 session_id( $sessionA );
148 $this->assertSame( array(), $_SESSION );
149 $this->assertSame( $sessionA, session_id() );
151 // Set some data in the session so we can see if it works.
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() );
160 // Screw up $_SESSION so we can tell the difference between "this
161 // worked" and "this did nothing"
162 $_SESSION['AuthenticationSessionTest'] = 'bogus';
164 // Re-open the session and see that data was actually reloaded
166 $this->assertSame( $expect, $_SESSION );
168 // Make sure session_reset() works too.
169 if ( function_exists( 'session_reset' ) ) {
170 $_SESSION['AuthenticationSessionTest'] = 'bogus';
172 $this->assertSame( $expect, $_SESSION );
175 // Re-fill the session, then test that session_destroy() works.
176 $_SESSION['AuthenticationSessionTest'] = $rand;
177 session_write_close();
179 $this->assertSame( $expect, $_SESSION );
181 session_id( $sessionA );
183 $this->assertSame( array(), $_SESSION );
184 session_write_close();
186 // Test that our session handler won't clone someone else's session
187 session_id( $sessionB );
189 $this->assertSame( $sessionB, session_id() );
190 $_SESSION['id'] = 'B';
191 session_write_close();
193 session_id( $sessionC );
195 $this->assertSame( array(), $_SESSION );
196 $_SESSION['id'] = 'C';
197 session_write_close();
199 session_id( $sessionB );
201 $this->assertSame( array( 'id' => 'B' ), $_SESSION );
202 session_write_close();
204 session_id( $sessionC );
206 $this->assertSame( array( 'id' => 'C' ), $_SESSION );
209 session_id( $sessionB );
211 $this->assertSame( array( 'id' => 'B' ), $_SESSION );
213 // Test merging between Session and $_SESSION
214 session_write_close();
216 $session = $manager->getEmptySession();
217 $session->set( 'Unchanged', 'setup' );
218 $session->set( 'Unchanged, null', null );
219 $session->set( 'Changed in $_SESSION', 'setup' );
220 $session->set( 'Changed in Session', 'setup' );
221 $session->set( 'Changed in both', 'setup' );
222 $session->set( 'Deleted in Session', 'setup' );
223 $session->set( 'Deleted in $_SESSION', 'setup' );
224 $session->set( 'Deleted in both', 'setup' );
225 $session->set( 'Deleted in Session, changed in $_SESSION', 'setup' );
226 $session->set( 'Deleted in $_SESSION, changed in Session', 'setup' );
230 session_id( $session->getId() );
232 $session->set( 'Added in Session', 'Session' );
233 $session->set( 'Added in both', 'Session' );
234 $session->set( 'Changed in Session', 'Session' );
235 $session->set( 'Changed in both', 'Session' );
236 $session->set( 'Deleted in $_SESSION, changed in Session', 'Session' );
237 $session->remove( 'Deleted in Session' );
238 $session->remove( 'Deleted in both' );
239 $session->remove( 'Deleted in Session, changed in $_SESSION' );
241 $_SESSION['Added in $_SESSION'] = '$_SESSION';
242 $_SESSION['Added in both'] = '$_SESSION';
243 $_SESSION['Changed in $_SESSION'] = '$_SESSION';
244 $_SESSION['Changed in both'] = '$_SESSION';
245 $_SESSION['Deleted in Session, changed in $_SESSION'] = '$_SESSION';
246 unset( $_SESSION['Deleted in $_SESSION'] );
247 unset( $_SESSION['Deleted in both'] );
248 unset( $_SESSION['Deleted in $_SESSION, changed in Session'] );
249 session_write_close();
251 $this->assertEquals( array(
252 'Added in Session' => 'Session',
253 'Added in $_SESSION' => '$_SESSION',
254 'Added in both' => 'Session',
255 'Unchanged' => 'setup',
256 'Unchanged, null' => null,
257 'Changed in Session' => 'Session',
258 'Changed in $_SESSION' => '$_SESSION',
259 'Changed in both' => 'Session',
260 'Deleted in Session, changed in $_SESSION' => '$_SESSION',
261 'Deleted in $_SESSION, changed in Session' => 'Session',
262 ), iterator_to_array( $session ) );
265 $session->set( 42, 'forty-two' );
266 $session->set( 'forty-two', 42 );
267 $session->set( 'wrong', 43 );
272 $this->assertArrayHasKey( 'forty-two', $_SESSION );
273 $this->assertSame( 42, $_SESSION['forty-two'] );
274 $this->assertArrayHasKey( 'wrong', $_SESSION );
275 unset( $_SESSION['wrong'] );
276 session_write_close();
278 $this->assertEquals( array(
281 ), iterator_to_array( $session ) );
283 // Test that write doesn't break if the session is invalid
284 $session = $manager->getEmptySession();
286 session_id( $session->getId() );
288 $this->mergeMwGlobalArrayValue( 'wgHooks', array(
289 'SessionCheckInfo' => array( function ( &$reason ) {
294 $this->assertNull( $manager->getSessionById( $session->getId(), true ), 'sanity check' );
295 session_write_close();
296 $this->mergeMwGlobalArrayValue( 'wgHooks', array(
297 'SessionCheckInfo' => array(),
299 $this->assertNotNull( $manager->getSessionById( $session->getId(), true ), 'sanity check' );
302 public static function provideHandlers() {
305 array( 'php_binary' ),
306 array( 'php_serialize' ),
311 * @dataProvider provideDisabled
312 * @expectedException BadMethodCallException
313 * @expectedExceptionMessage Attempt to use PHP session management
315 public function testDisabled( $method, $args ) {
316 $rProp = new \
ReflectionProperty( 'MediaWiki\\Session\\PHPSessionHandler', 'instance' );
317 $rProp->setAccessible( true );
318 $handler = $this->getMockBuilder( 'MediaWiki\\Session\\PHPSessionHandler' )
320 ->disableOriginalConstructor()
322 \TestingAccessWrapper
::newFromObject( $handler )->setEnableFlags( 'disable' );
323 $oldValue = $rProp->getValue();
324 $rProp->setValue( $handler );
325 $reset = new \
ScopedCallback( array( $rProp, 'setValue' ), array( $oldValue ) );
327 call_user_func_array( array( $handler, $method ), $args );
330 public static function provideDisabled() {
332 array( 'open', array( '', '' ) ),
333 array( 'read', array( '' ) ),
334 array( 'write', array( '', '' ) ),
335 array( 'destroy', array( '' ) ),
340 * @dataProvider provideWrongInstance
341 * @expectedException UnexpectedValueException
342 * @expectedExceptionMessageRegExp /: Wrong instance called!$/
344 public function testWrongInstance( $method, $args ) {
345 $handler = $this->getMockBuilder( 'MediaWiki\\Session\\PHPSessionHandler' )
347 ->disableOriginalConstructor()
349 \TestingAccessWrapper
::newFromObject( $handler )->setEnableFlags( 'enable' );
351 call_user_func_array( array( $handler, $method ), $args );
354 public static function provideWrongInstance() {
356 array( 'open', array( '', '' ) ),
357 array( 'close', array() ),
358 array( 'read', array( '' ) ),
359 array( 'write', array( '', '' ) ),
360 array( 'destroy', array( '' ) ),
361 array( 'gc', array( 0 ) ),