Validate BlockID cookie before use
[lhc/web/wiklou.git] / tests / phpunit / includes / user / UserTest.php
index 0819bf2..deb9708 100644 (file)
@@ -25,6 +25,7 @@ class UserTest extends MediaWikiTestCase {
                $this->setUpPermissionGlobals();
 
                $this->user = new User;
+               $this->user->addToDatabase();
                $this->user->addGroup( 'unittesters' );
        }
 
@@ -99,6 +100,7 @@ class UserTest extends MediaWikiTestCase {
         */
        public function testUserGetRightsHooks() {
                $user = new User;
+               $user->addToDatabase();
                $user->addGroup( 'unittesters' );
                $user->addGroup( 'testwriters' );
                $userWrapper = TestingAccessWrapper::newFromObject( $user );
@@ -345,18 +347,18 @@ class UserTest extends MediaWikiTestCase {
                $user = $this->getMutableTestUser()->getUser();
 
                $user->setOption( 'userjs-someoption', 'test' );
-               $user->setOption( 'cols', 200 );
+               $user->setOption( 'rclimit', 200 );
                $user->saveSettings();
 
                $user = User::newFromName( $user->getName() );
                $user->load( User::READ_LATEST );
                $this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) );
-               $this->assertEquals( 200, $user->getOption( 'cols' ) );
+               $this->assertEquals( 200, $user->getOption( 'rclimit' ) );
 
                $user = User::newFromName( $user->getName() );
                MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
                $this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) );
-               $this->assertEquals( 200, $user->getOption( 'cols' ) );
+               $this->assertEquals( 200, $user->getOption( 'rclimit' ) );
        }
 
        /**
@@ -367,7 +369,7 @@ class UserTest extends MediaWikiTestCase {
        public function testAnonOptions() {
                global $wgDefaultUserOptions;
                $this->user->setOption( 'userjs-someoption', 'test' );
-               $this->assertEquals( $wgDefaultUserOptions['cols'], $this->user->getOption( 'cols' ) );
+               $this->assertEquals( $wgDefaultUserOptions['rclimit'], $this->user->getOption( 'rclimit' ) );
                $this->assertEquals( 'test', $this->user->getOption( 'userjs-someoption' ) );
        }
 
@@ -597,16 +599,17 @@ class UserTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgCookieSetOnAutoblock' => true,
                        'wgCookiePrefix' => 'wmsitetitle',
+                       'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
                ] );
 
                // 1. Log in a test user, and block them.
                $user1tmp = $this->getTestUser()->getUser();
                $request1 = new FauxRequest();
                $request1->getSession()->setUser( $user1tmp );
-               $expiryFiveDays = time() + ( 5 * 24 * 60 * 60 );
+               $expiryFiveHours = wfTimestamp() + ( 5 * 60 * 60 );
                $block = new Block( [
                        'enableAutoblock' => true,
-                       'expiry' => wfTimestamp( TS_MW, $expiryFiveDays ),
+                       'expiry' => wfTimestamp( TS_MW, $expiryFiveHours ),
                ] );
                $block->setTarget( $user1tmp );
                $block->insert();
@@ -624,12 +627,13 @@ class UserTest extends MediaWikiTestCase {
                // Test for the desired cookie name, value, and expiry.
                $cookies = $request1->response()->getCookies();
                $this->assertArrayHasKey( 'wmsitetitleBlockID', $cookies );
-               $this->assertEquals( $block->getId(), $cookies['wmsitetitleBlockID']['value'] );
-               $this->assertEquals( $expiryFiveDays, $cookies['wmsitetitleBlockID']['expire'] );
+               $this->assertEquals( $expiryFiveHours, $cookies['wmsitetitleBlockID']['expire'] );
+               $cookieValue = Block::getIdFromCookieValue( $cookies['wmsitetitleBlockID']['value'] );
+               $this->assertEquals( $block->getId(), $cookieValue );
 
                // 2. Create a new request, set the cookies, and see if the (anon) user is blocked.
                $request2 = new FauxRequest();
-               $request2->setCookie( 'BlockID', $block->getId() );
+               $request2->setCookie( 'BlockID', $block->getCookieValue() );
                $user2 = User::newFromSession( $request2 );
                $user2->load();
                $this->assertNotEquals( $user1->getId(), $user2->getId() );
@@ -667,6 +671,7 @@ class UserTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgCookieSetOnAutoblock' => false,
                        'wgCookiePrefix' => 'wm_no_cookies',
+                       'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
                ] );
 
                // 1. Log in a test user, and block them.
@@ -696,15 +701,14 @@ class UserTest extends MediaWikiTestCase {
 
        /**
         * When a user is autoblocked and a cookie is set to track them, the expiry time of the cookie
-        * should match the block's expiry. If the block is infinite, the cookie expiry time should
-        * match $wgCookieExpiration. If the expiry time is changed, the cookie's should change with it.
+        * should match the block's expiry, to a maximum of 24 hours. If the expiry time is changed,
+        * the cookie's should change with it.
         */
        public function testAutoblockCookieInfiniteExpiry() {
-               $cookieExpiration = 20 * 24 * 60 * 60; // 20 days
                $this->setMwGlobals( [
                        'wgCookieSetOnAutoblock' => true,
-                       'wgCookieExpiration' => $cookieExpiration,
                        'wgCookiePrefix' => 'wm_infinite_block',
+                       'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
                ] );
                // 1. Log in a test user, and block them indefinitely.
                $user1Tmp = $this->getTestUser()->getUser();
@@ -724,12 +728,19 @@ class UserTest extends MediaWikiTestCase {
                $this->assertTrue( $block->isAutoblocking() );
                $this->assertGreaterThanOrEqual( 1, $user1->getBlockId() );
                $cookies = $request1->response()->getCookies();
-               // Calculate the expected cookie expiry date.
+               // Test the cookie's expiry to the nearest minute.
                $this->assertArrayHasKey( 'wm_infinite_blockBlockID', $cookies );
-               $this->assertEquals( time() + $cookieExpiration, $cookies['wm_infinite_blockBlockID']['expire'] );
+               $expOneDay = wfTimestamp() + ( 24 * 60 * 60 );
+               // Check for expiry dates in a 10-second window, to account for slow testing.
+               $this->assertEquals(
+                       $expOneDay,
+                       $cookies['wm_infinite_blockBlockID']['expire'],
+                       'Expiry date',
+                       5.0
+               );
 
-               // 3. Change the block's expiry (to 2 days), and the cookie's should be changed also.
-               $newExpiry = time() + 2 * 24 * 60 * 60;
+               // 3. Change the block's expiry (to 2 hours), and the cookie's should be changed also.
+               $newExpiry = wfTimestamp() + 2 * 60 * 60;
                $block->mExpiry = wfTimestamp( TS_MW, $newExpiry );
                $block->update();
                $user2tmp = $this->getTestUser()->getUser();
@@ -739,9 +750,116 @@ class UserTest extends MediaWikiTestCase {
                $user2->mBlock = $block;
                $user2->load();
                $cookies = $request2->response()->getCookies();
+               $this->assertEquals( wfTimestamp( TS_MW, $newExpiry ), $block->getExpiry() );
                $this->assertEquals( $newExpiry, $cookies['wm_infinite_blockBlockID']['expire'] );
 
                // Clean up.
                $block->delete();
        }
+
+       public function testSoftBlockRanges() {
+               global $wgUser;
+
+               $this->setMwGlobals( [
+                       'wgSoftBlockRanges' => [ '10.0.0.0/8' ],
+                       'wgUser' => null,
+               ] );
+
+               // IP isn't in $wgSoftBlockRanges
+               $request = new FauxRequest();
+               $request->setIP( '192.168.0.1' );
+               $wgUser = User::newFromSession( $request );
+               $this->assertNull( $wgUser->getBlock() );
+
+               // IP is in $wgSoftBlockRanges
+               $request = new FauxRequest();
+               $request->setIP( '10.20.30.40' );
+               $wgUser = User::newFromSession( $request );
+               $block = $wgUser->getBlock();
+               $this->assertInstanceOf( Block::class, $block );
+               $this->assertSame( 'wgSoftBlockRanges', $block->getSystemBlockType() );
+
+               // Make sure the block is really soft
+               $request->getSession()->setUser( $this->getTestUser()->getUser() );
+               $wgUser = User::newFromSession( $request );
+               $this->assertFalse( $wgUser->isAnon(), 'sanity check' );
+               $this->assertNull( $wgUser->getBlock() );
+       }
+
+       /**
+        * Test that a modified BlockID cookie doesn't actually load the relevant block (T152951).
+        */
+       public function testAutoblockCookieInauthentic() {
+               // Set up the bits of global configuration that we use.
+               $this->setMwGlobals( [
+                       'wgCookieSetOnAutoblock' => true,
+                       'wgCookiePrefix' => 'wmsitetitle',
+                       'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
+               ] );
+
+               // 1. Log in a blocked test user.
+               $user1tmp = $this->getTestUser()->getUser();
+               $request1 = new FauxRequest();
+               $request1->getSession()->setUser( $user1tmp );
+               $block = new Block( [ 'enableAutoblock' => true ] );
+               $block->setTarget( $user1tmp );
+               $block->insert();
+               $user1 = User::newFromSession( $request1 );
+               $user1->mBlock = $block;
+               $user1->load();
+
+               // 2. Create a new request, set the cookie to an invalid value, and make sure the (anon)
+               // user not blocked.
+               $request2 = new FauxRequest();
+               $request2->setCookie( 'BlockID', $block->getId() . '!zzzzzzz' );
+               $user2 = User::newFromSession( $request2 );
+               $user2->load();
+               $this->assertTrue( $user2->isAnon() );
+               $this->assertFalse( $user2->isLoggedIn() );
+               $this->assertFalse( $user2->isBlocked() );
+
+               // Clean up.
+               $block->delete();
+       }
+
+       /**
+        * The BlockID cookie is normally verified with a HMAC, but not if wgSecretKey is not set.
+        * This checks that a non-authenticated cookie still works.
+        */
+       public function testAutoblockCookieNoSecretKey() {
+               // Set up the bits of global configuration that we use.
+               $this->setMwGlobals( [
+                       'wgCookieSetOnAutoblock' => true,
+                       'wgCookiePrefix' => 'wmsitetitle',
+                       'wgSecretKey' => null,
+               ] );
+
+               // 1. Log in a blocked test user.
+               $user1tmp = $this->getTestUser()->getUser();
+               $request1 = new FauxRequest();
+               $request1->getSession()->setUser( $user1tmp );
+               $block = new Block( [ 'enableAutoblock' => true ] );
+               $block->setTarget( $user1tmp );
+               $block->insert();
+               $user1 = User::newFromSession( $request1 );
+               $user1->mBlock = $block;
+               $user1->load();
+               $this->assertTrue( $user1->isBlocked() );
+
+               // 2. Create a new request, set the cookie to just the block ID, and the user should
+               // still get blocked when they log in again.
+               $request2 = new FauxRequest();
+               $request2->setCookie( 'BlockID', $block->getId() );
+               $user2 = User::newFromSession( $request2 );
+               $user2->load();
+               $this->assertNotEquals( $user1->getId(), $user2->getId() );
+               $this->assertNotEquals( $user1->getToken(), $user2->getToken() );
+               $this->assertTrue( $user2->isAnon() );
+               $this->assertFalse( $user2->isLoggedIn() );
+               $this->assertTrue( $user2->isBlocked() );
+               $this->assertEquals( true, $user2->getBlock()->isAutoblocking() ); // Non-strict type-check.
+
+               // Clean up.
+               $block->delete();
+       }
 }