X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/d57edfddd6c01f0ed6b1a84019649cdf6cddd5f8..83d871ca0d985c6d586b323bf96161afb510ebf6:/tests/phpunit/includes/session/PHPSessionHandlerTest.php diff --git a/tests/phpunit/includes/session/PHPSessionHandlerTest.php b/tests/phpunit/includes/session/PHPSessionHandlerTest.php new file mode 100644 index 00000000..0a2e84e1 --- /dev/null +++ b/tests/phpunit/includes/session/PHPSessionHandlerTest.php @@ -0,0 +1,370 @@ +setAccessible( true ); + if ( $rProp->getValue() ) { + $old = TestingAccessWrapper::newFromObject( $rProp->getValue() ); + $oldManager = $old->manager; + $oldStore = $old->store; + $oldLogger = $old->logger; + $reset[] = new \Wikimedia\ScopedCallback( + [ PHPSessionHandler::class, 'install' ], + [ $oldManager, $oldStore, $oldLogger ] + ); + } + + return $reset; + } + + public function testEnableFlags() { + $handler = TestingAccessWrapper::newFromObject( + $this->getMockBuilder( PHPSessionHandler::class ) + ->setMethods( null ) + ->disableOriginalConstructor() + ->getMock() + ); + + $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' ); + $rProp->setAccessible( true ); + $reset = new \Wikimedia\ScopedCallback( [ $rProp, 'setValue' ], [ $rProp->getValue() ] ); + $rProp->setValue( $handler ); + + $handler->setEnableFlags( 'enable' ); + $this->assertTrue( $handler->enable ); + $this->assertFalse( $handler->warn ); + $this->assertTrue( PHPSessionHandler::isEnabled() ); + + $handler->setEnableFlags( 'warn' ); + $this->assertTrue( $handler->enable ); + $this->assertTrue( $handler->warn ); + $this->assertTrue( PHPSessionHandler::isEnabled() ); + + $handler->setEnableFlags( 'disable' ); + $this->assertFalse( $handler->enable ); + $this->assertFalse( PHPSessionHandler::isEnabled() ); + + $rProp->setValue( null ); + $this->assertFalse( PHPSessionHandler::isEnabled() ); + } + + public function testInstall() { + $reset = $this->getResetter( $rProp ); + $rProp->setValue( null ); + + session_write_close(); + ini_set( 'session.use_cookies', 1 ); + ini_set( 'session.use_trans_sid', 1 ); + + $store = new TestBagOStuff(); + $logger = new \TestLogger(); + $manager = new SessionManager( [ + 'store' => $store, + 'logger' => $logger, + ] ); + + $this->assertFalse( PHPSessionHandler::isInstalled() ); + PHPSessionHandler::install( $manager ); + $this->assertTrue( PHPSessionHandler::isInstalled() ); + + $this->assertFalse( wfIniGetBool( 'session.use_cookies' ) ); + $this->assertFalse( wfIniGetBool( 'session.use_trans_sid' ) ); + + $this->assertNotNull( $rProp->getValue() ); + $priv = TestingAccessWrapper::newFromObject( $rProp->getValue() ); + $this->assertSame( $manager, $priv->manager ); + $this->assertSame( $store, $priv->store ); + $this->assertSame( $logger, $priv->logger ); + } + + /** + * @dataProvider provideHandlers + * @param string $handler php serialize_handler to use + */ + public function testSessionHandling( $handler ) { + $this->hideDeprecated( '$_SESSION' ); + $reset[] = $this->getResetter( $rProp ); + + $this->setMwGlobals( [ + 'wgSessionProviders' => [ [ 'class' => 'DummySessionProvider' ] ], + 'wgObjectCacheSessionExpiry' => 2, + ] ); + + $store = new TestBagOStuff(); + $logger = new \TestLogger( true, function ( $m ) { + // Discard all log events starting with expected prefix + return preg_match( '/^SessionBackend "\{session\}" /', $m ) ? null : $m; + } ); + $manager = new SessionManager( [ + 'store' => $store, + 'logger' => $logger, + ] ); + PHPSessionHandler::install( $manager ); + $wrap = TestingAccessWrapper::newFromObject( $rProp->getValue() ); + $reset[] = new \Wikimedia\ScopedCallback( + [ $wrap, 'setEnableFlags' ], + [ $wrap->enable ? $wrap->warn ? 'warn' : 'enable' : 'disable' ] + ); + $wrap->setEnableFlags( 'warn' ); + + \MediaWiki\suppressWarnings(); + ini_set( 'session.serialize_handler', $handler ); + \MediaWiki\restoreWarnings(); + if ( ini_get( 'session.serialize_handler' ) !== $handler ) { + $this->markTestSkipped( "Cannot set session.serialize_handler to \"$handler\"" ); + } + + // Session IDs for testing + $sessionA = str_repeat( 'a', 32 ); + $sessionB = str_repeat( 'b', 32 ); + $sessionC = str_repeat( 'c', 32 ); + + // Set up garbage data in the session + $_SESSION['AuthenticationSessionTest'] = 'bogus'; + + session_id( $sessionA ); + session_start(); + $this->assertSame( [], $_SESSION ); + $this->assertSame( $sessionA, session_id() ); + + // Set some data in the session so we can see if it works. + $rand = mt_rand(); + $_SESSION['AuthenticationSessionTest'] = $rand; + $expect = [ 'AuthenticationSessionTest' => $rand ]; + session_write_close(); + $this->assertSame( [ + [ LogLevel::WARNING, 'Something wrote to $_SESSION!' ], + ], $logger->getBuffer() ); + + // Screw up $_SESSION so we can tell the difference between "this + // worked" and "this did nothing" + $_SESSION['AuthenticationSessionTest'] = 'bogus'; + + // Re-open the session and see that data was actually reloaded + session_start(); + $this->assertSame( $expect, $_SESSION ); + + // Make sure session_reset() works too. + if ( function_exists( 'session_reset' ) ) { + $_SESSION['AuthenticationSessionTest'] = 'bogus'; + session_reset(); + $this->assertSame( $expect, $_SESSION ); + } + + // Re-fill the session, then test that session_destroy() works. + $_SESSION['AuthenticationSessionTest'] = $rand; + session_write_close(); + session_start(); + $this->assertSame( $expect, $_SESSION ); + session_destroy(); + session_id( $sessionA ); + session_start(); + $this->assertSame( [], $_SESSION ); + session_write_close(); + + // Test that our session handler won't clone someone else's session + session_id( $sessionB ); + session_start(); + $this->assertSame( $sessionB, session_id() ); + $_SESSION['id'] = 'B'; + session_write_close(); + + session_id( $sessionC ); + session_start(); + $this->assertSame( [], $_SESSION ); + $_SESSION['id'] = 'C'; + session_write_close(); + + session_id( $sessionB ); + session_start(); + $this->assertSame( [ 'id' => 'B' ], $_SESSION ); + session_write_close(); + + session_id( $sessionC ); + session_start(); + $this->assertSame( [ 'id' => 'C' ], $_SESSION ); + session_destroy(); + + session_id( $sessionB ); + session_start(); + $this->assertSame( [ 'id' => 'B' ], $_SESSION ); + + // Test merging between Session and $_SESSION + session_write_close(); + + $session = $manager->getEmptySession(); + $session->set( 'Unchanged', 'setup' ); + $session->set( 'Unchanged, null', null ); + $session->set( 'Changed in $_SESSION', 'setup' ); + $session->set( 'Changed in Session', 'setup' ); + $session->set( 'Changed in both', 'setup' ); + $session->set( 'Deleted in Session', 'setup' ); + $session->set( 'Deleted in $_SESSION', 'setup' ); + $session->set( 'Deleted in both', 'setup' ); + $session->set( 'Deleted in Session, changed in $_SESSION', 'setup' ); + $session->set( 'Deleted in $_SESSION, changed in Session', 'setup' ); + $session->persist(); + $session->save(); + + session_id( $session->getId() ); + session_start(); + $session->set( 'Added in Session', 'Session' ); + $session->set( 'Added in both', 'Session' ); + $session->set( 'Changed in Session', 'Session' ); + $session->set( 'Changed in both', 'Session' ); + $session->set( 'Deleted in $_SESSION, changed in Session', 'Session' ); + $session->remove( 'Deleted in Session' ); + $session->remove( 'Deleted in both' ); + $session->remove( 'Deleted in Session, changed in $_SESSION' ); + $session->save(); + $_SESSION['Added in $_SESSION'] = '$_SESSION'; + $_SESSION['Added in both'] = '$_SESSION'; + $_SESSION['Changed in $_SESSION'] = '$_SESSION'; + $_SESSION['Changed in both'] = '$_SESSION'; + $_SESSION['Deleted in Session, changed in $_SESSION'] = '$_SESSION'; + unset( $_SESSION['Deleted in $_SESSION'] ); + unset( $_SESSION['Deleted in both'] ); + unset( $_SESSION['Deleted in $_SESSION, changed in Session'] ); + session_write_close(); + + $this->assertEquals( [ + 'Added in Session' => 'Session', + 'Added in $_SESSION' => '$_SESSION', + 'Added in both' => 'Session', + 'Unchanged' => 'setup', + 'Unchanged, null' => null, + 'Changed in Session' => 'Session', + 'Changed in $_SESSION' => '$_SESSION', + 'Changed in both' => 'Session', + 'Deleted in Session, changed in $_SESSION' => '$_SESSION', + 'Deleted in $_SESSION, changed in Session' => 'Session', + ], iterator_to_array( $session ) ); + + $session->clear(); + $session->set( 42, 'forty-two' ); + $session->set( 'forty-two', 42 ); + $session->set( 'wrong', 43 ); + $session->persist(); + $session->save(); + + session_start(); + $this->assertArrayHasKey( 'forty-two', $_SESSION ); + $this->assertSame( 42, $_SESSION['forty-two'] ); + $this->assertArrayHasKey( 'wrong', $_SESSION ); + unset( $_SESSION['wrong'] ); + session_write_close(); + + $this->assertEquals( [ + 42 => 'forty-two', + 'forty-two' => 42, + ], iterator_to_array( $session ) ); + + // Test that write doesn't break if the session is invalid + $session = $manager->getEmptySession(); + $session->persist(); + $id = $session->getId(); + unset( $session ); + session_id( $id ); + session_start(); + $this->mergeMwGlobalArrayValue( 'wgHooks', [ + 'SessionCheckInfo' => [ function ( &$reason ) { + $reason = 'Testing'; + return false; + } ], + ] ); + $this->assertNull( $manager->getSessionById( $id, true ), 'sanity check' ); + session_write_close(); + + $this->mergeMwGlobalArrayValue( 'wgHooks', [ + 'SessionCheckInfo' => [], + ] ); + $this->assertNotNull( $manager->getSessionById( $id, true ), 'sanity check' ); + } + + public static function provideHandlers() { + return [ + [ 'php' ], + [ 'php_binary' ], + [ 'php_serialize' ], + ]; + } + + /** + * @dataProvider provideDisabled + * @expectedException BadMethodCallException + * @expectedExceptionMessage Attempt to use PHP session management + */ + public function testDisabled( $method, $args ) { + $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' ); + $rProp->setAccessible( true ); + $handler = $this->getMockBuilder( PHPSessionHandler::class ) + ->setMethods( null ) + ->disableOriginalConstructor() + ->getMock(); + TestingAccessWrapper::newFromObject( $handler )->setEnableFlags( 'disable' ); + $oldValue = $rProp->getValue(); + $rProp->setValue( $handler ); + $reset = new \Wikimedia\ScopedCallback( [ $rProp, 'setValue' ], [ $oldValue ] ); + + call_user_func_array( [ $handler, $method ], $args ); + } + + public static function provideDisabled() { + return [ + [ 'open', [ '', '' ] ], + [ 'read', [ '' ] ], + [ 'write', [ '', '' ] ], + [ 'destroy', [ '' ] ], + ]; + } + + /** + * @dataProvider provideWrongInstance + * @expectedException UnexpectedValueException + * @expectedExceptionMessageRegExp /: Wrong instance called!$/ + */ + public function testWrongInstance( $method, $args ) { + $handler = $this->getMockBuilder( PHPSessionHandler::class ) + ->setMethods( null ) + ->disableOriginalConstructor() + ->getMock(); + TestingAccessWrapper::newFromObject( $handler )->setEnableFlags( 'enable' ); + + call_user_func_array( [ $handler, $method ], $args ); + } + + public static function provideWrongInstance() { + return [ + [ 'open', [ '', '' ] ], + [ 'close', [] ], + [ 'read', [ '' ] ], + [ 'write', [ '', '' ] ], + [ 'destroy', [ '' ] ], + [ 'gc', [ 0 ] ], + ]; + } + +}