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 // Tolerate debug message, anything else is unexpected
75 $logger = new \
TestLogger( false, function ( $m ) {
76 return preg_match( '/^SessionManager using store/', $m ) ?
null : $m;
78 $manager = new SessionManager( [
83 $this->assertFalse( PHPSessionHandler
::isInstalled() );
84 PHPSessionHandler
::install( $manager );
85 $this->assertTrue( PHPSessionHandler
::isInstalled() );
87 $this->assertFalse( wfIniGetBool( 'session.use_cookies' ) );
88 $this->assertFalse( wfIniGetBool( 'session.use_trans_sid' ) );
90 $this->assertNotNull( $rProp->getValue() );
91 $priv = TestingAccessWrapper
::newFromObject( $rProp->getValue() );
92 $this->assertSame( $manager, $priv->manager
);
93 $this->assertSame( $store, $priv->store
);
94 $this->assertSame( $logger, $priv->logger
);
98 * @dataProvider provideHandlers
99 * @param string $handler php serialize_handler to use
101 public function testSessionHandling( $handler ) {
102 $this->hideDeprecated( '$_SESSION' );
103 $reset[] = $this->getResetter( $rProp );
105 $this->setMwGlobals( [
106 'wgSessionProviders' => [ [ 'class' => \DummySessionProvider
::class ] ],
107 'wgObjectCacheSessionExpiry' => 2,
110 $store = new TestBagOStuff();
111 $logger = new \
TestLogger( true, function ( $m ) {
112 // Discard all log events starting with expected prefix
113 return preg_match( '/^SessionBackend "\{session\}" /', $m ) ?
null : $m;
115 $manager = new SessionManager( [
119 PHPSessionHandler
::install( $manager );
120 $wrap = TestingAccessWrapper
::newFromObject( $rProp->getValue() );
121 $reset[] = new \Wikimedia\
ScopedCallback(
122 [ $wrap, 'setEnableFlags' ],
123 [ $wrap->enable ?
( $wrap->warn ?
'warn' : 'enable' ) : 'disable' ]
125 $wrap->setEnableFlags( 'warn' );
127 \Wikimedia\
suppressWarnings();
128 ini_set( 'session.serialize_handler', $handler );
129 \Wikimedia\restoreWarnings
();
130 if ( ini_get( 'session.serialize_handler' ) !== $handler ) {
131 $this->markTestSkipped( "Cannot set session.serialize_handler to \"$handler\"" );
134 // Session IDs for testing
135 $sessionA = str_repeat( 'a', 32 );
136 $sessionB = str_repeat( 'b', 32 );
137 $sessionC = str_repeat( 'c', 32 );
139 // Set up garbage data in the session
140 $_SESSION['AuthenticationSessionTest'] = 'bogus';
142 session_id( $sessionA );
144 $this->assertSame( [], $_SESSION );
145 $this->assertSame( $sessionA, session_id() );
147 // Set some data in the session so we can see if it works.
149 $_SESSION['AuthenticationSessionTest'] = $rand;
150 $expect = [ 'AuthenticationSessionTest' => $rand ];
151 session_write_close();
153 [ LogLevel
::DEBUG
, 'SessionManager using store MediaWiki\Session\TestBagOStuff' ],
154 [ LogLevel
::WARNING
, 'Something wrote to $_SESSION!' ],
155 ], $logger->getBuffer() );
157 // Screw up $_SESSION so we can tell the difference between "this
158 // worked" and "this did nothing"
159 $_SESSION['AuthenticationSessionTest'] = 'bogus';
161 // Re-open the session and see that data was actually reloaded
163 $this->assertSame( $expect, $_SESSION );
165 // Make sure session_reset() works too.
166 if ( function_exists( 'session_reset' ) ) {
167 $_SESSION['AuthenticationSessionTest'] = 'bogus';
169 $this->assertSame( $expect, $_SESSION );
172 // Re-fill the session, then test that session_destroy() works.
173 $_SESSION['AuthenticationSessionTest'] = $rand;
174 session_write_close();
176 $this->assertSame( $expect, $_SESSION );
178 session_id( $sessionA );
180 $this->assertSame( [], $_SESSION );
181 session_write_close();
183 // Test that our session handler won't clone someone else's session
184 session_id( $sessionB );
186 $this->assertSame( $sessionB, session_id() );
187 $_SESSION['id'] = 'B';
188 session_write_close();
190 session_id( $sessionC );
192 $this->assertSame( [], $_SESSION );
193 $_SESSION['id'] = 'C';
194 session_write_close();
196 session_id( $sessionB );
198 $this->assertSame( [ 'id' => 'B' ], $_SESSION );
199 session_write_close();
201 session_id( $sessionC );
203 $this->assertSame( [ 'id' => 'C' ], $_SESSION );
206 session_id( $sessionB );
208 $this->assertSame( [ 'id' => 'B' ], $_SESSION );
210 // Test merging between Session and $_SESSION
211 session_write_close();
213 $session = $manager->getEmptySession();
214 $session->set( 'Unchanged', 'setup' );
215 $session->set( 'Unchanged, null', null );
216 $session->set( 'Changed in $_SESSION', 'setup' );
217 $session->set( 'Changed in Session', 'setup' );
218 $session->set( 'Changed in both', 'setup' );
219 $session->set( 'Deleted in Session', 'setup' );
220 $session->set( 'Deleted in $_SESSION', 'setup' );
221 $session->set( 'Deleted in both', 'setup' );
222 $session->set( 'Deleted in Session, changed in $_SESSION', 'setup' );
223 $session->set( 'Deleted in $_SESSION, changed in Session', 'setup' );
227 session_id( $session->getId() );
229 $session->set( 'Added in Session', 'Session' );
230 $session->set( 'Added in both', 'Session' );
231 $session->set( 'Changed in Session', 'Session' );
232 $session->set( 'Changed in both', 'Session' );
233 $session->set( 'Deleted in $_SESSION, changed in Session', 'Session' );
234 $session->remove( 'Deleted in Session' );
235 $session->remove( 'Deleted in both' );
236 $session->remove( 'Deleted in Session, changed in $_SESSION' );
238 $_SESSION['Added in $_SESSION'] = '$_SESSION';
239 $_SESSION['Added in both'] = '$_SESSION';
240 $_SESSION['Changed in $_SESSION'] = '$_SESSION';
241 $_SESSION['Changed in both'] = '$_SESSION';
242 $_SESSION['Deleted in Session, changed in $_SESSION'] = '$_SESSION';
243 unset( $_SESSION['Deleted in $_SESSION'] );
244 unset( $_SESSION['Deleted in both'] );
245 unset( $_SESSION['Deleted in $_SESSION, changed in Session'] );
246 session_write_close();
248 $this->assertEquals( [
249 'Added in Session' => 'Session',
250 'Added in $_SESSION' => '$_SESSION',
251 'Added in both' => 'Session',
252 'Unchanged' => 'setup',
253 'Unchanged, null' => null,
254 'Changed in Session' => 'Session',
255 'Changed in $_SESSION' => '$_SESSION',
256 'Changed in both' => 'Session',
257 'Deleted in Session, changed in $_SESSION' => '$_SESSION',
258 'Deleted in $_SESSION, changed in Session' => 'Session',
259 ], iterator_to_array( $session ) );
262 $session->set( 42, 'forty-two' );
263 $session->set( 'forty-two', 42 );
264 $session->set( 'wrong', 43 );
269 $this->assertArrayHasKey( 'forty-two', $_SESSION );
270 $this->assertSame( 42, $_SESSION['forty-two'] );
271 $this->assertArrayHasKey( 'wrong', $_SESSION );
272 unset( $_SESSION['wrong'] );
273 session_write_close();
275 $this->assertEquals( [
278 ], iterator_to_array( $session ) );
280 // Test that write doesn't break if the session is invalid
281 $session = $manager->getEmptySession();
283 $id = $session->getId();
287 $this->mergeMwGlobalArrayValue( 'wgHooks', [
288 'SessionCheckInfo' => [ function ( &$reason ) {
293 $this->assertNull( $manager->getSessionById( $id, true ), 'sanity check' );
294 session_write_close();
296 $this->mergeMwGlobalArrayValue( 'wgHooks', [
297 'SessionCheckInfo' => [],
299 $this->assertNotNull( $manager->getSessionById( $id, true ), 'sanity check' );
302 public static function provideHandlers() {
311 * @dataProvider provideDisabled
312 * @expectedException BadMethodCallException
313 * @expectedExceptionMessage Attempt to use PHP session management
315 public function testDisabled( $method, $args ) {
316 $rProp = new \
ReflectionProperty( PHPSessionHandler
::class, 'instance' );
317 $rProp->setAccessible( true );
318 $handler = $this->getMockBuilder( PHPSessionHandler
::class )
320 ->disableOriginalConstructor()
322 TestingAccessWrapper
::newFromObject( $handler )->setEnableFlags( 'disable' );
323 $oldValue = $rProp->getValue();
324 $rProp->setValue( $handler );
325 $reset = new \Wikimedia\
ScopedCallback( [ $rProp, 'setValue' ], [ $oldValue ] );
327 call_user_func_array( [ $handler, $method ], $args );
330 public static function provideDisabled() {
332 [ 'open', [ '', '' ] ],
334 [ 'write', [ '', '' ] ],
335 [ 'destroy', [ '' ] ],
340 * @dataProvider provideWrongInstance
341 * @expectedException UnexpectedValueException
342 * @expectedExceptionMessageRegExp /: Wrong instance called!$/
344 public function testWrongInstance( $method, $args ) {
345 $handler = $this->getMockBuilder( PHPSessionHandler
::class )
347 ->disableOriginalConstructor()
349 TestingAccessWrapper
::newFromObject( $handler )->setEnableFlags( 'enable' );
351 call_user_func_array( [ $handler, $method ], $args );
354 public static function provideWrongInstance() {
356 [ 'open', [ '', '' ] ],
359 [ 'write', [ '', '' ] ],
360 [ 'destroy', [ '' ] ],