summaryrefslogtreecommitdiff
path: root/www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php')
-rw-r--r--www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php361
1 files changed, 361 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php b/www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php
new file mode 100644
index 00000000..045ba2f0
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/session/PHPSessionHandlerTest.php
@@ -0,0 +1,361 @@
+<?php
+
+namespace MediaWiki\Session;
+
+use Psr\Log\LogLevel;
+use MediaWikiTestCase;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Session
+ * @covers MediaWiki\Session\PHPSessionHandler
+ */
+class PHPSessionHandlerTest extends MediaWikiTestCase {
+
+ private function getResetter( &$rProp = null ) {
+ $reset = [];
+
+ $rProp = new \ReflectionProperty( PHPSessionHandler::class, 'instance' );
+ $rProp->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::class ] ],
+ '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' );
+
+ \Wikimedia\suppressWarnings();
+ ini_set( 'session.serialize_handler', $handler );
+ \Wikimedia\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 ] ],
+ ];
+ }
+
+}