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 $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 ]
34 public function testEnableFlags() {
35 $handler = TestingAccessWrapper
::newFromObject(
36 $this->getMockBuilder( PHPSessionHandler
::class )
38 ->disableOriginalConstructor()
42 $rProp = new \
ReflectionProperty( PHPSessionHandler
::class, 'instance' );
43 $rProp->setAccessible( true );
44 $reset = new \Wikimedia\
ScopedCallback( [ $rProp, 'setValue' ], [ $rProp->getValue() ] );
45 $rProp->setValue( $handler );
47 $handler->setEnableFlags( 'enable' );
48 $this->assertTrue( $handler->enable
);
49 $this->assertFalse( $handler->warn
);
50 $this->assertTrue( PHPSessionHandler
::isEnabled() );
52 $handler->setEnableFlags( 'warn' );
53 $this->assertTrue( $handler->enable
);
54 $this->assertTrue( $handler->warn
);
55 $this->assertTrue( PHPSessionHandler
::isEnabled() );
57 $handler->setEnableFlags( 'disable' );
58 $this->assertFalse( $handler->enable
);
59 $this->assertFalse( PHPSessionHandler
::isEnabled() );
61 $rProp->setValue( null );
62 $this->assertFalse( PHPSessionHandler
::isEnabled() );
65 public function testInstall() {
66 $reset = $this->getResetter( $rProp );
67 $rProp->setValue( null );
69 session_write_close();
70 ini_set( 'session.use_cookies', 1 );
71 ini_set( 'session.use_trans_sid', 1 );
73 $store = new TestBagOStuff();
74 $logger = new \
TestLogger();
75 $manager = new SessionManager( [
80 $this->assertFalse( PHPSessionHandler
::isInstalled() );
81 PHPSessionHandler
::install( $manager );
82 $this->assertTrue( PHPSessionHandler
::isInstalled() );
84 $this->assertFalse( wfIniGetBool( 'session.use_cookies' ) );
85 $this->assertFalse( wfIniGetBool( 'session.use_trans_sid' ) );
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
);
95 * @dataProvider provideHandlers
96 * @param string $handler php serialize_handler to use
98 public function testSessionHandling( $handler ) {
99 $this->hideDeprecated( '$_SESSION' );
100 $reset[] = $this->getResetter( $rProp );
102 $this->setMwGlobals( [
103 'wgSessionProviders' => [ [ 'class' => \DummySessionProvider
::class ] ],
104 'wgObjectCacheSessionExpiry' => 2,
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;
112 $manager = new SessionManager( [
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' ]
122 $wrap->setEnableFlags( 'warn' );
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\"" );
131 // Session IDs for testing
132 $sessionA = str_repeat( 'a', 32 );
133 $sessionB = str_repeat( 'b', 32 );
134 $sessionC = str_repeat( 'c', 32 );
136 // Set up garbage data in the session
137 $_SESSION['AuthenticationSessionTest'] = 'bogus';
139 session_id( $sessionA );
141 $this->assertSame( [], $_SESSION );
142 $this->assertSame( $sessionA, session_id() );
144 // Set some data in the session so we can see if it works.
146 $_SESSION['AuthenticationSessionTest'] = $rand;
147 $expect = [ 'AuthenticationSessionTest' => $rand ];
148 session_write_close();
150 [ LogLevel
::WARNING
, 'Something wrote to $_SESSION!' ],
151 ], $logger->getBuffer() );
153 // Screw up $_SESSION so we can tell the difference between "this
154 // worked" and "this did nothing"
155 $_SESSION['AuthenticationSessionTest'] = 'bogus';
157 // Re-open the session and see that data was actually reloaded
159 $this->assertSame( $expect, $_SESSION );
161 // Make sure session_reset() works too.
162 if ( function_exists( 'session_reset' ) ) {
163 $_SESSION['AuthenticationSessionTest'] = 'bogus';
165 $this->assertSame( $expect, $_SESSION );
168 // Re-fill the session, then test that session_destroy() works.
169 $_SESSION['AuthenticationSessionTest'] = $rand;
170 session_write_close();
172 $this->assertSame( $expect, $_SESSION );
174 session_id( $sessionA );
176 $this->assertSame( [], $_SESSION );
177 session_write_close();
179 // Test that our session handler won't clone someone else's session
180 session_id( $sessionB );
182 $this->assertSame( $sessionB, session_id() );
183 $_SESSION['id'] = 'B';
184 session_write_close();
186 session_id( $sessionC );
188 $this->assertSame( [], $_SESSION );
189 $_SESSION['id'] = 'C';
190 session_write_close();
192 session_id( $sessionB );
194 $this->assertSame( [ 'id' => 'B' ], $_SESSION );
195 session_write_close();
197 session_id( $sessionC );
199 $this->assertSame( [ 'id' => 'C' ], $_SESSION );
202 session_id( $sessionB );
204 $this->assertSame( [ 'id' => 'B' ], $_SESSION );
206 // Test merging between Session and $_SESSION
207 session_write_close();
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' );
223 session_id( $session->getId() );
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' );
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();
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 ) );
258 $session->set( 42, 'forty-two' );
259 $session->set( 'forty-two', 42 );
260 $session->set( 'wrong', 43 );
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();
271 $this->assertEquals( [
274 ], iterator_to_array( $session ) );
276 // Test that write doesn't break if the session is invalid
277 $session = $manager->getEmptySession();
279 $id = $session->getId();
283 $this->mergeMwGlobalArrayValue( 'wgHooks', [
284 'SessionCheckInfo' => [ function ( &$reason ) {
289 $this->assertNull( $manager->getSessionById( $id, true ), 'sanity check' );
290 session_write_close();
292 $this->mergeMwGlobalArrayValue( 'wgHooks', [
293 'SessionCheckInfo' => [],
295 $this->assertNotNull( $manager->getSessionById( $id, true ), 'sanity check' );
298 public static function provideHandlers() {
307 * @dataProvider provideDisabled
308 * @expectedException BadMethodCallException
309 * @expectedExceptionMessage Attempt to use PHP session management
311 public function testDisabled( $method, $args ) {
312 $rProp = new \
ReflectionProperty( PHPSessionHandler
::class, 'instance' );
313 $rProp->setAccessible( true );
314 $handler = $this->getMockBuilder( PHPSessionHandler
::class )
316 ->disableOriginalConstructor()
318 TestingAccessWrapper
::newFromObject( $handler )->setEnableFlags( 'disable' );
319 $oldValue = $rProp->getValue();
320 $rProp->setValue( $handler );
321 $reset = new \Wikimedia\
ScopedCallback( [ $rProp, 'setValue' ], [ $oldValue ] );
323 call_user_func_array( [ $handler, $method ], $args );
326 public static function provideDisabled() {
328 [ 'open', [ '', '' ] ],
330 [ 'write', [ '', '' ] ],
331 [ 'destroy', [ '' ] ],
336 * @dataProvider provideWrongInstance
337 * @expectedException UnexpectedValueException
338 * @expectedExceptionMessageRegExp /: Wrong instance called!$/
340 public function testWrongInstance( $method, $args ) {
341 $handler = $this->getMockBuilder( PHPSessionHandler
::class )
343 ->disableOriginalConstructor()
345 TestingAccessWrapper
::newFromObject( $handler )->setEnableFlags( 'enable' );
347 call_user_func_array( [ $handler, $method ], $args );
350 public static function provideWrongInstance() {
352 [ 'open', [ '', '' ] ],
355 [ 'write', [ '', '' ] ],
356 [ 'destroy', [ '' ] ],