]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - tests/phpunit/includes/session/PHPSessionHandlerTest.php
MediaWiki 1.30.2-scripts
[autoinstallsdev/mediawiki.git] / 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 (file)
index 0000000..0a2e84e
--- /dev/null
@@ -0,0 +1,370 @@
+<?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 = [];
+
+               // Ignore "headers already sent" warnings during this test
+               set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) {
+                       if ( preg_match( '/headers already sent/', $errstr ) ) {
+                               return true;
+                       }
+                       return false;
+               } );
+               $reset[] = new \Wikimedia\ScopedCallback( 'restore_error_handler' );
+
+               $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' ] ],
+                       '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 ] ],
+               ];
+       }
+
+}