3 namespace MediaWiki\Session
;
7 use Wikimedia\TestingAccessWrapper
;
11 * @covers MediaWiki\Session\PHPSessionHandler
13 class PHPSessionHandlerTest
extends MediaWikiTestCase
{
15 private function getResetter( &$rProp = null ) {
18 // Ignore "headers already sent" warnings during this test
19 set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) {
20 if ( preg_match( '/[hH]eaders already sent/', $errstr ) ) {
25 $reset[] = new \Wikimedia\
ScopedCallback( 'restore_error_handler' );
27 $rProp = new \
ReflectionProperty( PHPSessionHandler
::class, 'instance' );
28 $rProp->setAccessible( true );
29 if ( $rProp->getValue() ) {
30 $old = TestingAccessWrapper
::newFromObject( $rProp->getValue() );
31 $oldManager = $old->manager
;
32 $oldStore = $old->store
;
33 $oldLogger = $old->logger
;
34 $reset[] = new \Wikimedia\
ScopedCallback(
35 [ PHPSessionHandler
::class, 'install' ],
36 [ $oldManager, $oldStore, $oldLogger ]
43 public function testEnableFlags() {
44 $handler = TestingAccessWrapper
::newFromObject(
45 $this->getMockBuilder( PHPSessionHandler
::class )
47 ->disableOriginalConstructor()
51 $rProp = new \
ReflectionProperty( PHPSessionHandler
::class, 'instance' );
52 $rProp->setAccessible( true );
53 $reset = new \Wikimedia\
ScopedCallback( [ $rProp, 'setValue' ], [ $rProp->getValue() ] );
54 $rProp->setValue( $handler );
56 $handler->setEnableFlags( 'enable' );
57 $this->assertTrue( $handler->enable
);
58 $this->assertFalse( $handler->warn
);
59 $this->assertTrue( PHPSessionHandler
::isEnabled() );
61 $handler->setEnableFlags( 'warn' );
62 $this->assertTrue( $handler->enable
);
63 $this->assertTrue( $handler->warn
);
64 $this->assertTrue( PHPSessionHandler
::isEnabled() );
66 $handler->setEnableFlags( 'disable' );
67 $this->assertFalse( $handler->enable
);
68 $this->assertFalse( PHPSessionHandler
::isEnabled() );
70 $rProp->setValue( null );
71 $this->assertFalse( PHPSessionHandler
::isEnabled() );
74 public function testInstall() {
75 $reset = $this->getResetter( $rProp );
76 $rProp->setValue( null );
78 session_write_close();
79 ini_set( 'session.use_cookies', 1 );
80 ini_set( 'session.use_trans_sid', 1 );
82 $store = new TestBagOStuff();
83 $logger = new \
TestLogger();
84 $manager = new SessionManager( [
89 $this->assertFalse( PHPSessionHandler
::isInstalled() );
90 PHPSessionHandler
::install( $manager );
91 $this->assertTrue( PHPSessionHandler
::isInstalled() );
93 $this->assertFalse( wfIniGetBool( 'session.use_cookies' ) );
94 $this->assertFalse( wfIniGetBool( 'session.use_trans_sid' ) );
96 $this->assertNotNull( $rProp->getValue() );
97 $priv = TestingAccessWrapper
::newFromObject( $rProp->getValue() );
98 $this->assertSame( $manager, $priv->manager
);
99 $this->assertSame( $store, $priv->store
);
100 $this->assertSame( $logger, $priv->logger
);
104 * @dataProvider provideHandlers
105 * @param string $handler php serialize_handler to use
107 public function testSessionHandling( $handler ) {
108 $this->hideDeprecated( '$_SESSION' );
109 $reset[] = $this->getResetter( $rProp );
111 $this->setMwGlobals( [
112 'wgSessionProviders' => [ [ 'class' => \DummySessionProvider
::class ] ],
113 'wgObjectCacheSessionExpiry' => 2,
116 $store = new TestBagOStuff();
117 $logger = new \
TestLogger( true, function ( $m ) {
118 // Discard all log events starting with expected prefix
119 return preg_match( '/^SessionBackend "\{session\}" /', $m ) ?
null : $m;
121 $manager = new SessionManager( [
125 PHPSessionHandler
::install( $manager );
126 $wrap = TestingAccessWrapper
::newFromObject( $rProp->getValue() );
127 $reset[] = new \Wikimedia\
ScopedCallback(
128 [ $wrap, 'setEnableFlags' ],
129 [ $wrap->enable ?
( $wrap->warn ?
'warn' : 'enable' ) : 'disable' ]
131 $wrap->setEnableFlags( 'warn' );
133 \Wikimedia\
suppressWarnings();
134 ini_set( 'session.serialize_handler', $handler );
135 \Wikimedia\restoreWarnings
();
136 if ( ini_get( 'session.serialize_handler' ) !== $handler ) {
137 $this->markTestSkipped( "Cannot set session.serialize_handler to \"$handler\"" );
140 // Session IDs for testing
141 $sessionA = str_repeat( 'a', 32 );
142 $sessionB = str_repeat( 'b', 32 );
143 $sessionC = str_repeat( 'c', 32 );
145 // Set up garbage data in the session
146 $_SESSION['AuthenticationSessionTest'] = 'bogus';
148 session_id( $sessionA );
150 $this->assertSame( [], $_SESSION );
151 $this->assertSame( $sessionA, session_id() );
153 // Set some data in the session so we can see if it works.
155 $_SESSION['AuthenticationSessionTest'] = $rand;
156 $expect = [ 'AuthenticationSessionTest' => $rand ];
157 session_write_close();
159 [ LogLevel
::WARNING
, 'Something wrote to $_SESSION!' ],
160 ], $logger->getBuffer() );
162 // Screw up $_SESSION so we can tell the difference between "this
163 // worked" and "this did nothing"
164 $_SESSION['AuthenticationSessionTest'] = 'bogus';
166 // Re-open the session and see that data was actually reloaded
168 $this->assertSame( $expect, $_SESSION );
170 // Make sure session_reset() works too.
171 if ( function_exists( 'session_reset' ) ) {
172 $_SESSION['AuthenticationSessionTest'] = 'bogus';
174 $this->assertSame( $expect, $_SESSION );
177 // Re-fill the session, then test that session_destroy() works.
178 $_SESSION['AuthenticationSessionTest'] = $rand;
179 session_write_close();
181 $this->assertSame( $expect, $_SESSION );
183 session_id( $sessionA );
185 $this->assertSame( [], $_SESSION );
186 session_write_close();
188 // Test that our session handler won't clone someone else's session
189 session_id( $sessionB );
191 $this->assertSame( $sessionB, session_id() );
192 $_SESSION['id'] = 'B';
193 session_write_close();
195 session_id( $sessionC );
197 $this->assertSame( [], $_SESSION );
198 $_SESSION['id'] = 'C';
199 session_write_close();
201 session_id( $sessionB );
203 $this->assertSame( [ 'id' => 'B' ], $_SESSION );
204 session_write_close();
206 session_id( $sessionC );
208 $this->assertSame( [ 'id' => 'C' ], $_SESSION );
211 session_id( $sessionB );
213 $this->assertSame( [ 'id' => 'B' ], $_SESSION );
215 // Test merging between Session and $_SESSION
216 session_write_close();
218 $session = $manager->getEmptySession();
219 $session->set( 'Unchanged', 'setup' );
220 $session->set( 'Unchanged, null', null );
221 $session->set( 'Changed in $_SESSION', 'setup' );
222 $session->set( 'Changed in Session', 'setup' );
223 $session->set( 'Changed in both', 'setup' );
224 $session->set( 'Deleted in Session', 'setup' );
225 $session->set( 'Deleted in $_SESSION', 'setup' );
226 $session->set( 'Deleted in both', 'setup' );
227 $session->set( 'Deleted in Session, changed in $_SESSION', 'setup' );
228 $session->set( 'Deleted in $_SESSION, changed in Session', 'setup' );
232 session_id( $session->getId() );
234 $session->set( 'Added in Session', 'Session' );
235 $session->set( 'Added in both', 'Session' );
236 $session->set( 'Changed in Session', 'Session' );
237 $session->set( 'Changed in both', 'Session' );
238 $session->set( 'Deleted in $_SESSION, changed in Session', 'Session' );
239 $session->remove( 'Deleted in Session' );
240 $session->remove( 'Deleted in both' );
241 $session->remove( 'Deleted in Session, changed in $_SESSION' );
243 $_SESSION['Added in $_SESSION'] = '$_SESSION';
244 $_SESSION['Added in both'] = '$_SESSION';
245 $_SESSION['Changed in $_SESSION'] = '$_SESSION';
246 $_SESSION['Changed in both'] = '$_SESSION';
247 $_SESSION['Deleted in Session, changed in $_SESSION'] = '$_SESSION';
248 unset( $_SESSION['Deleted in $_SESSION'] );
249 unset( $_SESSION['Deleted in both'] );
250 unset( $_SESSION['Deleted in $_SESSION, changed in Session'] );
251 session_write_close();
253 $this->assertEquals( [
254 'Added in Session' => 'Session',
255 'Added in $_SESSION' => '$_SESSION',
256 'Added in both' => 'Session',
257 'Unchanged' => 'setup',
258 'Unchanged, null' => null,
259 'Changed in Session' => 'Session',
260 'Changed in $_SESSION' => '$_SESSION',
261 'Changed in both' => 'Session',
262 'Deleted in Session, changed in $_SESSION' => '$_SESSION',
263 'Deleted in $_SESSION, changed in Session' => 'Session',
264 ], iterator_to_array( $session ) );
267 $session->set( 42, 'forty-two' );
268 $session->set( 'forty-two', 42 );
269 $session->set( 'wrong', 43 );
274 $this->assertArrayHasKey( 'forty-two', $_SESSION );
275 $this->assertSame( 42, $_SESSION['forty-two'] );
276 $this->assertArrayHasKey( 'wrong', $_SESSION );
277 unset( $_SESSION['wrong'] );
278 session_write_close();
280 $this->assertEquals( [
283 ], iterator_to_array( $session ) );
285 // Test that write doesn't break if the session is invalid
286 $session = $manager->getEmptySession();
288 $id = $session->getId();
292 $this->mergeMwGlobalArrayValue( 'wgHooks', [
293 'SessionCheckInfo' => [ function ( &$reason ) {
298 $this->assertNull( $manager->getSessionById( $id, true ), 'sanity check' );
299 session_write_close();
301 $this->mergeMwGlobalArrayValue( 'wgHooks', [
302 'SessionCheckInfo' => [],
304 $this->assertNotNull( $manager->getSessionById( $id, true ), 'sanity check' );
307 public static function provideHandlers() {
316 * @dataProvider provideDisabled
317 * @expectedException BadMethodCallException
318 * @expectedExceptionMessage Attempt to use PHP session management
320 public function testDisabled( $method, $args ) {
321 $rProp = new \
ReflectionProperty( PHPSessionHandler
::class, 'instance' );
322 $rProp->setAccessible( true );
323 $handler = $this->getMockBuilder( PHPSessionHandler
::class )
325 ->disableOriginalConstructor()
327 TestingAccessWrapper
::newFromObject( $handler )->setEnableFlags( 'disable' );
328 $oldValue = $rProp->getValue();
329 $rProp->setValue( $handler );
330 $reset = new \Wikimedia\
ScopedCallback( [ $rProp, 'setValue' ], [ $oldValue ] );
332 call_user_func_array( [ $handler, $method ], $args );
335 public static function provideDisabled() {
337 [ 'open', [ '', '' ] ],
339 [ 'write', [ '', '' ] ],
340 [ 'destroy', [ '' ] ],
345 * @dataProvider provideWrongInstance
346 * @expectedException UnexpectedValueException
347 * @expectedExceptionMessageRegExp /: Wrong instance called!$/
349 public function testWrongInstance( $method, $args ) {
350 $handler = $this->getMockBuilder( PHPSessionHandler
::class )
352 ->disableOriginalConstructor()
354 TestingAccessWrapper
::newFromObject( $handler )->setEnableFlags( 'enable' );
356 call_user_func_array( [ $handler, $method ], $args );
359 public static function provideWrongInstance() {
361 [ 'open', [ '', '' ] ],
364 [ 'write', [ '', '' ] ],
365 [ 'destroy', [ '' ] ],