define( 'NS_UNITTEST_TALK', 5601 );
use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
/**
* @group Database
$this->setUpPermissionGlobals();
- $this->user = new User;
- $this->user->addGroup( 'unittesters' );
+ $this->user = $this->getTestUser( [ 'unittesters' ] )->getUser();
}
private function setUpPermissionGlobals() {
* @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();
[ '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' ],
* 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
$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' ) );
}
/**
- * Bug 37963
+ * T39963
* Make sure defaults are loaded when setOption is called.
* @covers User::loadOptions
*/
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' ) );
}
$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 );
'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();
// 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() );
$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() );
$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();
$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();
$this->assertArrayHasKey( 'wm_infinite_blockBlockID', $cookies );
$expOneDay = wfTimestamp() + ( 24 * 60 * 60 );
// Check for expiry dates in a 10-second window, to account for slow testing.
- $this->assertGreaterThan(
- $expOneDay - 5,
- $cookies['wm_infinite_blockBlockID']['expire']
- );
- $this->assertLessThan(
- $expOneDay + 5,
- $cookies['wm_infinite_blockBlockID']['expire']
+ $this->assertEquals(
+ $expOneDay,
+ $cookies['wm_infinite_blockBlockID']['expire'],
+ 'Expiry date',
+ 5.0
);
// 3. Change the block's expiry (to 2 hours), and the cookie's should be changed also.
// 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 ),
+ ] );
+
+ // 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 ) );
+ }
}