Merge "Add semantic tags to license info text"
[lhc/web/wiklou.git] / tests / phpunit / includes / user / UserTest.php
index fe56d27..ea7f715 100644 (file)
@@ -4,6 +4,7 @@ define( 'NS_UNITTEST', 5600 );
 define( 'NS_UNITTEST_TALK', 5601 );
 
 use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
 
 /**
  * @group Database
@@ -24,8 +25,7 @@ class UserTest extends MediaWikiTestCase {
 
                $this->setUpPermissionGlobals();
 
-               $this->user = new User;
-               $this->user->addGroup( 'unittesters' );
+               $this->user = $this->getTestUser( [ 'unittesters' ] )->getUser();
        }
 
        private function setUpPermissionGlobals() {
@@ -98,9 +98,7 @@ class UserTest extends MediaWikiTestCase {
         * @covers User::getRights
         */
        public function testUserGetRightsHooks() {
-               $user = new User;
-               $user->addGroup( 'unittesters' );
-               $user->addGroup( 'testwriters' );
+               $user = $this->getTestUser( [ 'unittesters', 'testwriters' ] )->getUser();
                $userWrapper = TestingAccessWrapper::newFromObject( $user );
 
                $rights = $user->getRights();
@@ -219,6 +217,8 @@ class UserTest extends MediaWikiTestCase {
                        [ 'Ab/cd', false, 'Contains slash' ],
                        [ 'Ab cd', true, 'Whitespace' ],
                        [ '192.168.1.1', false, 'IP' ],
+                       [ '116.17.184.5/32', false, 'IP range' ],
+                       [ '::e:f:2001/96', false, 'IPv6 range' ],
                        [ 'User:Abcd', false, 'Reserved Namespace' ],
                        [ '12abcd232', true, 'Starts with Numbers' ],
                        [ '?abcd', true, 'Start with ? mark' ],
@@ -236,6 +236,8 @@ class UserTest extends MediaWikiTestCase {
         * Test, if for all rights a right- message exist,
         * which is used on Special:ListGroupRights as help text
         * Extensions and core
+        *
+        * @coversNothing
         */
        public function testAllRightsWithMessage() {
                // Getting all user rights, for core: User::$mCoreRights, for extensions: $wgAvailableRights
@@ -360,7 +362,7 @@ class UserTest extends MediaWikiTestCase {
        }
 
        /**
-        * Bug 37963
+        * T39963
         * Make sure defaults are loaded when setOption is called.
         * @covers User::loadOptions
         */
@@ -597,9 +599,16 @@ class UserTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgCookieSetOnAutoblock' => true,
                        'wgCookiePrefix' => 'wmsitetitle',
+                       'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
+               ] );
+
+               // Unregister the hooks for proper unit testing
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       'PerformRetroactiveAutoblock' => []
                ] );
 
                // 1. Log in a test user, and block them.
+               $userBlocker = $this->getTestSysop()->getUser();
                $user1tmp = $this->getTestUser()->getUser();
                $request1 = new FauxRequest();
                $request1->getSession()->setUser( $user1tmp );
@@ -609,7 +618,9 @@ class UserTest extends MediaWikiTestCase {
                        'expiry' => wfTimestamp( TS_MW, $expiryFiveHours ),
                ] );
                $block->setTarget( $user1tmp );
-               $block->insert();
+               $block->setBlocker( $userBlocker );
+               $res = $block->insert();
+               $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
                $user1 = User::newFromSession( $request1 );
                $user1->mBlock = $block;
                $user1->load();
@@ -624,12 +635,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( $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() );
@@ -637,7 +649,8 @@ class UserTest extends MediaWikiTestCase {
                $this->assertTrue( $user2->isAnon() );
                $this->assertFalse( $user2->isLoggedIn() );
                $this->assertTrue( $user2->isBlocked() );
-               $this->assertEquals( true, $user2->getBlock()->isAutoblocking() ); // Non-strict type-check.
+               // Non-strict type-check.
+               $this->assertEquals( true, $user2->getBlock()->isAutoblocking(), 'Autoblock does not work' );
                // Can't directly compare the objects becuase of member type differences.
                // One day this will work: $this->assertEquals( $block, $user2->getBlock() );
                $this->assertEquals( $block->getId(), $user2->getBlock()->getId() );
@@ -667,15 +680,24 @@ class UserTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgCookieSetOnAutoblock' => false,
                        'wgCookiePrefix' => 'wm_no_cookies',
+                       'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
+               ] );
+
+               // Unregister the hooks for proper unit testing
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       'PerformRetroactiveAutoblock' => []
                ] );
 
                // 1. Log in a test user, and block them.
+               $userBlocker = $this->getTestSysop()->getUser();
                $testUser = $this->getTestUser()->getUser();
                $request1 = new FauxRequest();
                $request1->getSession()->setUser( $testUser );
                $block = new Block( [ 'enableAutoblock' => true ] );
                $block->setTarget( $testUser );
-               $block->insert();
+               $block->setBlocker( $userBlocker );
+               $res = $block->insert();
+               $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
                $user = User::newFromSession( $request1 );
                $user->mBlock = $block;
                $user->load();
@@ -703,14 +725,24 @@ class UserTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgCookieSetOnAutoblock' => true,
                        'wgCookiePrefix' => 'wm_infinite_block',
+                       'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
+               ] );
+
+               // Unregister the hooks for proper unit testing
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       'PerformRetroactiveAutoblock' => []
                ] );
+
                // 1. Log in a test user, and block them indefinitely.
+               $userBlocker = $this->getTestSysop()->getUser();
                $user1Tmp = $this->getTestUser()->getUser();
                $request1 = new FauxRequest();
                $request1->getSession()->setUser( $user1Tmp );
                $block = new Block( [ 'enableAutoblock' => true, 'expiry' => 'infinity' ] );
                $block->setTarget( $user1Tmp );
-               $block->insert();
+               $block->setBlocker( $userBlocker );
+               $res = $block->insert();
+               $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
                $user1 = User::newFromSession( $request1 );
                $user1->mBlock = $block;
                $user1->load();
@@ -780,4 +812,220 @@ class UserTest extends MediaWikiTestCase {
                $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 ),
+               ] );
+
+               // Unregister the hooks for proper unit testing
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       'PerformRetroactiveAutoblock' => []
+               ] );
+
+               // 1. Log in a blocked test user.
+               $userBlocker = $this->getTestSysop()->getUser();
+               $user1tmp = $this->getTestUser()->getUser();
+               $request1 = new FauxRequest();
+               $request1->getSession()->setUser( $user1tmp );
+               $block = new Block( [ 'enableAutoblock' => true ] );
+               $block->setTarget( $user1tmp );
+               $block->setBlocker( $userBlocker );
+               $res = $block->insert();
+               $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
+               $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,
+               ] );
+
+               // Unregister the hooks for proper unit testing
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [
+                       'PerformRetroactiveAutoblock' => []
+               ] );
+
+               // 1. Log in a blocked test user.
+               $userBlocker = $this->getTestSysop()->getUser();
+               $user1tmp = $this->getTestUser()->getUser();
+               $request1 = new FauxRequest();
+               $request1->getSession()->setUser( $user1tmp );
+               $block = new Block( [ 'enableAutoblock' => true ] );
+               $block->setTarget( $user1tmp );
+               $block->setBlocker( $userBlocker );
+               $res = $block->insert();
+               $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
+               $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();
+       }
+
+       /**
+        * @covers User::isPingLimitable
+        */
+       public function testIsPingLimitable() {
+               $request = new FauxRequest();
+               $request->setIP( '1.2.3.4' );
+               $user = User::newFromSession( $request );
+
+               $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
+               $this->assertTrue( $user->isPingLimitable() );
+
+               $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [ '1.2.3.4' ] );
+               $this->assertFalse( $user->isPingLimitable() );
+
+               $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [ '1.2.3.0/8' ] );
+               $this->assertFalse( $user->isPingLimitable() );
+
+               $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
+               $noRateLimitUser = $this->getMockBuilder( User::class )->disableOriginalConstructor()
+                       ->setMethods( [ 'getIP', 'getRights' ] )->getMock();
+               $noRateLimitUser->expects( $this->any() )->method( 'getIP' )->willReturn( '1.2.3.4' );
+               $noRateLimitUser->expects( $this->any() )->method( 'getRights' )->willReturn( [ 'noratelimit' ] );
+               $this->assertFalse( $noRateLimitUser->isPingLimitable() );
+       }
+
+       public function provideExperienceLevel() {
+               return [
+                       [ 2, 2, 'newcomer' ],
+                       [ 12, 3, 'newcomer' ],
+                       [ 8, 5, 'newcomer' ],
+                       [ 15, 10, 'learner' ],
+                       [ 450, 20, 'learner' ],
+                       [ 460, 33, 'learner' ],
+                       [ 525, 28, 'learner' ],
+                       [ 538, 33, 'experienced' ],
+               ];
+       }
+
+       /**
+        * @covers User::getExperienceLevel
+        * @dataProvider provideExperienceLevel
+        */
+       public function testExperienceLevel( $editCount, $memberSince, $expLevel ) {
+               $this->setMwGlobals( [
+                       'wgLearnerEdits' => 10,
+                       'wgLearnerMemberSince' => 4,
+                       'wgExperiencedUserEdits' => 500,
+                       'wgExperiencedUserMemberSince' => 30,
+               ] );
+
+               $db = wfGetDB( DB_MASTER );
+
+               $data = new stdClass();
+               $data->user_id = 1;
+               $data->user_name = 'name';
+               $data->user_real_name = 'Real Name';
+               $data->user_touched = 1;
+               $data->user_token = 'token';
+               $data->user_email = 'a@a.a';
+               $data->user_email_authenticated = null;
+               $data->user_email_token = 'token';
+               $data->user_email_token_expires = null;
+               $data->user_editcount = $editCount;
+               $data->user_registration = $db->timestamp( time() - $memberSince * 86400 );
+               $user = User::newFromRow( $data );
+
+               $this->assertEquals( $expLevel, $user->getExperienceLevel() );
+       }
+
+       /**
+        * @covers User::getExperienceLevel
+        */
+       public function testExperienceLevelAnon() {
+               $user = User::newFromName( '10.11.12.13', false );
+
+               $this->assertFalse( $user->getExperienceLevel() );
+       }
+
+       public static function provideIsLocallBlockedProxy() {
+               return [
+                       [ '1.2.3.4', '1.2.3.4' ],
+                       [ '1.2.3.4', '1.2.3.0/16' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideIsLocallBlockedProxy
+        * @covers User::isLocallyBlockedProxy
+        */
+       public function testIsLocallyBlockedProxy( $ip, $blockListEntry ) {
+               $this->setMwGlobals(
+                       'wgProxyList', []
+               );
+               $this->assertFalse( User::isLocallyBlockedProxy( $ip ) );
+
+               $this->setMwGlobals(
+                       'wgProxyList',
+                       [
+                               $blockListEntry
+                       ]
+               );
+               $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
+
+               $this->setMwGlobals(
+                       'wgProxyList',
+                       [
+                               'test' => $blockListEntry
+                       ]
+               );
+               $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
+
+               $this->hideDeprecated(
+                       'IP addresses in the keys of $wgProxyList (found the following IP ' .
+                       'addresses in keys: ' . $blockListEntry . ', please move them to values)'
+               );
+               $this->setMwGlobals(
+                       'wgProxyList',
+                       [
+                               $blockListEntry => 'test'
+                       ]
+               );
+               $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
+       }
 }