X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php diff --git a/tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php b/tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php new file mode 100644 index 00000000..086fa28b --- /dev/null +++ b/tests/phpunit/includes/session/ImmutableSessionProviderWithCookieTest.php @@ -0,0 +1,305 @@ +set( 'CookiePrefix', 'wgCookiePrefix' ); + + $params = [ + 'sessionCookieName' => $name, + 'sessionCookieOptions' => [], + ]; + if ( $prefix !== null ) { + $params['sessionCookieOptions']['prefix'] = $prefix; + } + + $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class ) + ->setConstructorArgs( [ $params ] ) + ->getMockForAbstractClass(); + $provider->setLogger( new \TestLogger() ); + $provider->setConfig( $config ); + $provider->setManager( new SessionManager() ); + + return $provider; + } + + public function testConstructor() { + $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class ) + ->getMockForAbstractClass(); + $priv = TestingAccessWrapper::newFromObject( $provider ); + $this->assertNull( $priv->sessionCookieName ); + $this->assertSame( [], $priv->sessionCookieOptions ); + + $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class ) + ->setConstructorArgs( [ [ + 'sessionCookieName' => 'Foo', + 'sessionCookieOptions' => [ 'Bar' ], + ] ] ) + ->getMockForAbstractClass(); + $priv = TestingAccessWrapper::newFromObject( $provider ); + $this->assertSame( 'Foo', $priv->sessionCookieName ); + $this->assertSame( [ 'Bar' ], $priv->sessionCookieOptions ); + + try { + $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class ) + ->setConstructorArgs( [ [ + 'sessionCookieName' => false, + ] ] ) + ->getMockForAbstractClass(); + $this->fail( 'Expected exception not thrown' ); + } catch ( \InvalidArgumentException $ex ) { + $this->assertSame( + 'sessionCookieName must be a string', + $ex->getMessage() + ); + } + + try { + $provider = $this->getMockBuilder( ImmutableSessionProviderWithCookie::class ) + ->setConstructorArgs( [ [ + 'sessionCookieOptions' => 'x', + ] ] ) + ->getMockForAbstractClass(); + $this->fail( 'Expected exception not thrown' ); + } catch ( \InvalidArgumentException $ex ) { + $this->assertSame( + 'sessionCookieOptions must be an array', + $ex->getMessage() + ); + } + } + + public function testBasics() { + $provider = $this->getProvider( null ); + $this->assertFalse( $provider->persistsSessionID() ); + $this->assertFalse( $provider->canChangeUser() ); + + $provider = $this->getProvider( 'Foo' ); + $this->assertTrue( $provider->persistsSessionID() ); + $this->assertFalse( $provider->canChangeUser() ); + + $msg = $provider->whyNoSession(); + $this->assertInstanceOf( 'Message', $msg ); + $this->assertSame( 'sessionprovider-nocookies', $msg->getKey() ); + } + + public function testGetVaryCookies() { + $provider = $this->getProvider( null ); + $this->assertSame( [], $provider->getVaryCookies() ); + + $provider = $this->getProvider( 'Foo' ); + $this->assertSame( [ 'wgCookiePrefixFoo' ], $provider->getVaryCookies() ); + + $provider = $this->getProvider( 'Foo', 'Bar' ); + $this->assertSame( [ 'BarFoo' ], $provider->getVaryCookies() ); + + $provider = $this->getProvider( 'Foo', '' ); + $this->assertSame( [ 'Foo' ], $provider->getVaryCookies() ); + } + + public function testGetSessionIdFromCookie() { + $this->setMwGlobals( 'wgCookiePrefix', 'wgCookiePrefix' ); + $request = new \FauxRequest(); + $request->setCookies( [ + '' => 'empty---------------------------', + 'Foo' => 'foo-----------------------------', + 'wgCookiePrefixFoo' => 'wgfoo---------------------------', + 'BarFoo' => 'foobar--------------------------', + 'bad' => 'bad', + ], '' ); + + $provider = TestingAccessWrapper::newFromObject( $this->getProvider( null ) ); + try { + $provider->getSessionIdFromCookie( $request ); + $this->fail( 'Expected exception not thrown' ); + } catch ( \BadMethodCallException $ex ) { + $this->assertSame( + 'MediaWiki\\Session\\ImmutableSessionProviderWithCookie::getSessionIdFromCookie ' . + 'may not be called when $this->sessionCookieName === null', + $ex->getMessage() + ); + } + + $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'Foo' ) ); + $this->assertSame( + 'wgfoo---------------------------', + $provider->getSessionIdFromCookie( $request ) + ); + + $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'Foo', 'Bar' ) ); + $this->assertSame( + 'foobar--------------------------', + $provider->getSessionIdFromCookie( $request ) + ); + + $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'Foo', '' ) ); + $this->assertSame( + 'foo-----------------------------', + $provider->getSessionIdFromCookie( $request ) + ); + + $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'bad', '' ) ); + $this->assertSame( null, $provider->getSessionIdFromCookie( $request ) ); + + $provider = TestingAccessWrapper::newFromObject( $this->getProvider( 'none', '' ) ); + $this->assertSame( null, $provider->getSessionIdFromCookie( $request ) ); + } + + protected function getSentRequest() { + $sentResponse = $this->getMockBuilder( 'FauxResponse' ) + ->setMethods( [ 'headersSent', 'setCookie', 'header' ] ) + ->getMock(); + $sentResponse->expects( $this->any() )->method( 'headersSent' ) + ->will( $this->returnValue( true ) ); + $sentResponse->expects( $this->never() )->method( 'setCookie' ); + $sentResponse->expects( $this->never() )->method( 'header' ); + + $sentRequest = $this->getMockBuilder( 'FauxRequest' ) + ->setMethods( [ 'response' ] )->getMock(); + $sentRequest->expects( $this->any() )->method( 'response' ) + ->will( $this->returnValue( $sentResponse ) ); + return $sentRequest; + } + + /** + * @dataProvider providePersistSession + * @param bool $secure + * @param bool $remember + */ + public function testPersistSession( $secure, $remember ) { + $this->setMwGlobals( [ + 'wgCookieExpiration' => 100, + 'wgSecureLogin' => false, + ] ); + + $provider = $this->getProvider( 'session' ); + $provider->setLogger( new \Psr\Log\NullLogger() ); + $priv = TestingAccessWrapper::newFromObject( $provider ); + $priv->sessionCookieOptions = [ + 'prefix' => 'x', + 'path' => 'CookiePath', + 'domain' => 'CookieDomain', + 'secure' => false, + 'httpOnly' => true, + ]; + + $sessionId = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + $user = User::newFromName( 'UTSysop' ); + $this->assertFalse( $user->requiresHTTPS(), 'sanity check' ); + + $backend = new SessionBackend( + new SessionId( $sessionId ), + new SessionInfo( SessionInfo::MIN_PRIORITY, [ + 'provider' => $provider, + 'id' => $sessionId, + 'persisted' => true, + 'userInfo' => UserInfo::newFromUser( $user, true ), + 'idIsSafe' => true, + ] ), + new TestBagOStuff(), + new \Psr\Log\NullLogger(), + 10 + ); + TestingAccessWrapper::newFromObject( $backend )->usePhpSessionHandling = false; + $backend->setRememberUser( $remember ); + $backend->setForceHTTPS( $secure ); + + // No cookie + $priv->sessionCookieName = null; + $request = new \FauxRequest(); + $provider->persistSession( $backend, $request ); + $this->assertSame( [], $request->response()->getCookies() ); + + // Cookie + $priv->sessionCookieName = 'session'; + $request = new \FauxRequest(); + $time = time(); + $provider->persistSession( $backend, $request ); + + $cookie = $request->response()->getCookieData( 'xsession' ); + $this->assertInternalType( 'array', $cookie ); + if ( isset( $cookie['expire'] ) && $cookie['expire'] > 0 ) { + // Round expiry so we don't randomly fail if the seconds ticked during the test. + $cookie['expire'] = round( $cookie['expire'] - $time, -2 ); + } + $this->assertEquals( [ + 'value' => $sessionId, + 'expire' => null, + 'path' => 'CookiePath', + 'domain' => 'CookieDomain', + 'secure' => $secure, + 'httpOnly' => true, + 'raw' => false, + ], $cookie ); + + $cookie = $request->response()->getCookieData( 'forceHTTPS' ); + if ( $secure ) { + $this->assertInternalType( 'array', $cookie ); + if ( isset( $cookie['expire'] ) && $cookie['expire'] > 0 ) { + // Round expiry so we don't randomly fail if the seconds ticked during the test. + $cookie['expire'] = round( $cookie['expire'] - $time, -2 ); + } + $this->assertEquals( [ + 'value' => 'true', + 'expire' => null, + 'path' => 'CookiePath', + 'domain' => 'CookieDomain', + 'secure' => false, + 'httpOnly' => true, + 'raw' => false, + ], $cookie ); + } else { + $this->assertNull( $cookie ); + } + + // Headers sent + $request = $this->getSentRequest(); + $provider->persistSession( $backend, $request ); + $this->assertSame( [], $request->response()->getCookies() ); + } + + public static function providePersistSession() { + return [ + [ false, false ], + [ false, true ], + [ true, false ], + [ true, true ], + ]; + } + + public function testUnpersistSession() { + $provider = $this->getProvider( 'session', '' ); + $provider->setLogger( new \Psr\Log\NullLogger() ); + $priv = TestingAccessWrapper::newFromObject( $provider ); + + // No cookie + $priv->sessionCookieName = null; + $request = new \FauxRequest(); + $provider->unpersistSession( $request ); + $this->assertSame( null, $request->response()->getCookie( 'session', '' ) ); + + // Cookie + $priv->sessionCookieName = 'session'; + $request = new \FauxRequest(); + $provider->unpersistSession( $request ); + $this->assertSame( '', $request->response()->getCookie( 'session', '' ) ); + + // Headers sent + $request = $this->getSentRequest(); + $provider->unpersistSession( $request ); + $this->assertSame( null, $request->response()->getCookie( 'session', '' ) ); + } + +}