2011-07-11 18:36:29 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
|
2019-03-19 18:56:10 +00:00
|
|
|
|
use MediaWiki\Block\CompositeBlock;
|
2020-01-10 00:00:51 +00:00
|
|
|
|
use MediaWiki\Block\DatabaseBlock;
|
2018-10-30 18:19:22 +00:00
|
|
|
|
use MediaWiki\Block\Restriction\NamespaceRestriction;
|
2020-01-10 00:00:51 +00:00
|
|
|
|
use MediaWiki\Block\Restriction\PageRestriction;
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
|
use MediaWiki\Block\SystemBlock;
|
2016-11-17 00:38:09 +00:00
|
|
|
|
use MediaWiki\MediaWikiServices;
|
2021-05-05 00:03:26 +00:00
|
|
|
|
use MediaWiki\Tests\Unit\DummyServicesTrait;
|
2018-01-27 01:48:19 +00:00
|
|
|
|
use MediaWiki\User\UserIdentityValue;
|
2021-01-27 04:25:24 +00:00
|
|
|
|
use Wikimedia\Assert\PreconditionException;
|
2017-04-19 19:37:35 +00:00
|
|
|
|
use Wikimedia\TestingAccessWrapper;
|
2016-11-17 00:38:09 +00:00
|
|
|
|
|
2011-07-19 21:41:25 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @group Database
|
|
|
|
|
|
*/
|
2020-06-30 15:09:24 +00:00
|
|
|
|
class UserTest extends MediaWikiIntegrationTestCase {
|
2021-05-05 00:03:26 +00:00
|
|
|
|
use DummyServicesTrait;
|
2018-11-01 14:11:03 +00:00
|
|
|
|
|
|
|
|
|
|
/** Constant for self::testIsBlockedFrom */
|
2020-05-16 00:27:13 +00:00
|
|
|
|
private const USER_TALK_PAGE = '<user talk page>';
|
2018-11-01 14:11:03 +00:00
|
|
|
|
|
2012-01-19 14:56:18 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @var User
|
|
|
|
|
|
*/
|
2011-07-16 16:09:00 +00:00
|
|
|
|
protected $user;
|
2011-10-11 10:02:50 +00:00
|
|
|
|
|
2019-10-20 18:11:08 +00:00
|
|
|
|
protected function setUp() : void {
|
2011-07-11 18:36:29 +00:00
|
|
|
|
parent::setUp();
|
2011-10-11 10:02:50 +00:00
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgGroupPermissions' => [],
|
|
|
|
|
|
'wgRevokePermissions' => [],
|
2019-09-20 03:02:12 +00:00
|
|
|
|
'wgUseRCPatrol' => true,
|
2020-04-30 01:22:12 +00:00
|
|
|
|
'wgWatchlistExpiry' => true,
|
2020-10-30 15:17:02 +00:00
|
|
|
|
'wgAutoConfirmAge' => 0,
|
|
|
|
|
|
'wgAutoConfirmCount' => 0,
|
2016-02-17 09:09:32 +00:00
|
|
|
|
] );
|
2011-10-11 10:02:50 +00:00
|
|
|
|
|
2011-07-11 18:36:29 +00:00
|
|
|
|
$this->setUpPermissionGlobals();
|
2012-10-08 10:56:20 +00:00
|
|
|
|
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user = $this->getTestUser( 'unittesters' )->getUser();
|
2011-07-11 18:36:29 +00:00
|
|
|
|
}
|
2012-10-08 10:56:20 +00:00
|
|
|
|
|
2011-07-11 18:36:29 +00:00
|
|
|
|
private function setUpPermissionGlobals() {
|
|
|
|
|
|
global $wgGroupPermissions, $wgRevokePermissions;
|
2011-10-11 10:02:50 +00:00
|
|
|
|
|
2011-07-16 16:09:00 +00:00
|
|
|
|
# Data for regular $wgGroupPermissions test
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$wgGroupPermissions['unittesters'] = [
|
2011-07-16 16:09:00 +00:00
|
|
|
|
'test' => true,
|
2011-07-11 18:36:29 +00:00
|
|
|
|
'runtest' => true,
|
|
|
|
|
|
'writetest' => false,
|
|
|
|
|
|
'nukeworld' => false,
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'autoconfirmed' => false,
|
2016-02-17 09:09:32 +00:00
|
|
|
|
];
|
|
|
|
|
|
$wgGroupPermissions['testwriters'] = [
|
2011-07-16 16:09:00 +00:00
|
|
|
|
'test' => true,
|
2011-07-11 18:36:29 +00:00
|
|
|
|
'writetest' => true,
|
|
|
|
|
|
'modifytest' => true,
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'autoconfirmed' => true,
|
2016-02-17 09:09:32 +00:00
|
|
|
|
];
|
2012-10-08 10:56:20 +00:00
|
|
|
|
|
2011-07-16 16:09:00 +00:00
|
|
|
|
# Data for regular $wgRevokePermissions test
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$wgRevokePermissions['formertesters'] = [
|
2011-07-11 18:36:29 +00:00
|
|
|
|
'runtest' => true,
|
2016-02-17 09:09:32 +00:00
|
|
|
|
];
|
2013-06-10 19:30:43 +00:00
|
|
|
|
|
2019-11-28 06:27:30 +00:00
|
|
|
|
# For the options and watchlist tests
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$wgGroupPermissions['*'] = [
|
2019-11-28 06:27:30 +00:00
|
|
|
|
'editmyoptions' => true,
|
|
|
|
|
|
'editmywatchlist' => true,
|
|
|
|
|
|
'viewmywatchlist' => true,
|
2019-09-20 03:02:12 +00:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
# For patrol tests
|
|
|
|
|
|
$wgGroupPermissions['patroller'] = [
|
|
|
|
|
|
'patrol' => true,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
# For account creation when blocked test
|
|
|
|
|
|
$wgGroupPermissions['accountcreator'] = [
|
|
|
|
|
|
'createaccount' => true,
|
|
|
|
|
|
'ipblock-exempt' => true
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2020-02-06 20:05:05 +00:00
|
|
|
|
# For bot and ratelimit tests
|
2019-09-20 03:02:12 +00:00
|
|
|
|
$wgGroupPermissions['bot'] = [
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'bot' => true,
|
|
|
|
|
|
'noratelimit' => true,
|
2016-02-17 09:09:32 +00:00
|
|
|
|
];
|
2011-07-16 16:09:00 +00:00
|
|
|
|
}
|
2011-10-11 10:02:50 +00:00
|
|
|
|
|
2019-03-19 18:56:10 +00:00
|
|
|
|
private function setSessionUser( User $user, WebRequest $request ) {
|
|
|
|
|
|
RequestContext::getMain()->setUser( $user );
|
|
|
|
|
|
RequestContext::getMain()->setRequest( $request );
|
|
|
|
|
|
TestingAccessWrapper::newFromObject( $user )->mRequest = $request;
|
|
|
|
|
|
$request->getSession()->setUser( $user );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-10-21 21:09:13 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getGroupPermissions
|
|
|
|
|
|
*/
|
2011-07-11 18:36:29 +00:00
|
|
|
|
public function testGroupPermissions() {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$rights = User::getGroupPermissions( [ 'unittesters' ] );
|
2011-07-11 18:36:29 +00:00
|
|
|
|
$this->assertContains( 'runtest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'writetest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'modifytest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'nukeworld', $rights );
|
2011-10-11 10:02:50 +00:00
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$rights = User::getGroupPermissions( [ 'unittesters', 'testwriters' ] );
|
2011-07-11 18:36:29 +00:00
|
|
|
|
$this->assertContains( 'runtest', $rights );
|
|
|
|
|
|
$this->assertContains( 'writetest', $rights );
|
|
|
|
|
|
$this->assertContains( 'modifytest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'nukeworld', $rights );
|
|
|
|
|
|
}
|
2013-02-14 11:36:35 +00:00
|
|
|
|
|
2013-10-21 21:09:13 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getGroupPermissions
|
|
|
|
|
|
*/
|
2011-07-11 18:36:29 +00:00
|
|
|
|
public function testRevokePermissions() {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$rights = User::getGroupPermissions( [ 'unittesters', 'formertesters' ] );
|
2011-07-11 18:36:29 +00:00
|
|
|
|
$this->assertNotContains( 'runtest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'writetest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'modifytest', $rights );
|
2011-10-11 10:02:50 +00:00
|
|
|
|
$this->assertNotContains( 'nukeworld', $rights );
|
2011-07-11 18:36:29 +00:00
|
|
|
|
}
|
2011-10-11 10:02:50 +00:00
|
|
|
|
|
2013-10-21 21:09:13 +00:00
|
|
|
|
/**
|
2019-08-21 02:21:13 +00:00
|
|
|
|
* TODO: Remove. This is the same as PermissionManagerTest::testGetUserPermissions
|
2013-10-21 21:09:13 +00:00
|
|
|
|
* @covers User::getRights
|
|
|
|
|
|
*/
|
2011-07-16 16:09:00 +00:00
|
|
|
|
public function testUserPermissions() {
|
2021-05-20 15:14:28 +00:00
|
|
|
|
$this->hideDeprecated( 'User::getRights' );
|
2011-07-16 16:09:00 +00:00
|
|
|
|
$rights = $this->user->getRights();
|
|
|
|
|
|
$this->assertContains( 'runtest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'writetest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'modifytest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'nukeworld', $rights );
|
|
|
|
|
|
}
|
2011-10-11 10:02:50 +00:00
|
|
|
|
|
2016-07-07 21:24:50 +00:00
|
|
|
|
/**
|
2019-08-21 02:21:13 +00:00
|
|
|
|
* TODO: Remove. This is the same as PermissionManagerTest::testGetUserPermissionsHooks
|
2016-07-07 21:24:50 +00:00
|
|
|
|
* @covers User::getRights
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testUserGetRightsHooks() {
|
2021-05-20 15:14:28 +00:00
|
|
|
|
$this->hideDeprecated( 'User::getRights' );
|
2017-05-18 20:16:55 +00:00
|
|
|
|
$user = $this->getTestUser( [ 'unittesters', 'testwriters' ] )->getUser();
|
2016-07-07 21:24:50 +00:00
|
|
|
|
$userWrapper = TestingAccessWrapper::newFromObject( $user );
|
|
|
|
|
|
|
|
|
|
|
|
$rights = $user->getRights();
|
|
|
|
|
|
$this->assertContains( 'test', $rights, 'sanity check' );
|
|
|
|
|
|
$this->assertContains( 'runtest', $rights, 'sanity check' );
|
|
|
|
|
|
$this->assertContains( 'writetest', $rights, 'sanity check' );
|
|
|
|
|
|
$this->assertNotContains( 'nukeworld', $rights, 'sanity check' );
|
|
|
|
|
|
|
2020-04-15 19:30:38 +00:00
|
|
|
|
// Add a hook manipulating the rights
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'UserGetRights', static function ( $user, &$rights ) {
|
2016-07-07 21:24:50 +00:00
|
|
|
|
$rights[] = 'nukeworld';
|
|
|
|
|
|
$rights = array_diff( $rights, [ 'writetest' ] );
|
2019-04-09 06:58:04 +00:00
|
|
|
|
} );
|
2016-07-07 21:24:50 +00:00
|
|
|
|
|
2020-04-15 19:30:38 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $user );
|
2016-07-07 21:24:50 +00:00
|
|
|
|
$rights = $user->getRights();
|
|
|
|
|
|
$this->assertContains( 'test', $rights );
|
|
|
|
|
|
$this->assertContains( 'runtest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'writetest', $rights );
|
|
|
|
|
|
$this->assertContains( 'nukeworld', $rights );
|
|
|
|
|
|
|
|
|
|
|
|
// Add a Session that limits rights
|
2018-01-13 00:02:09 +00:00
|
|
|
|
$mock = $this->getMockBuilder( stdClass::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
|
->addMethods( [ 'getAllowedUserRights', 'deregisterSession', 'getSessionId' ] )
|
2016-07-07 21:24:50 +00:00
|
|
|
|
->getMock();
|
|
|
|
|
|
$mock->method( 'getAllowedUserRights' )->willReturn( [ 'test', 'writetest' ] );
|
|
|
|
|
|
$mock->method( 'getSessionId' )->willReturn(
|
|
|
|
|
|
new MediaWiki\Session\SessionId( str_repeat( 'X', 32 ) )
|
|
|
|
|
|
);
|
|
|
|
|
|
$session = MediaWiki\Session\TestUtils::getDummySession( $mock );
|
|
|
|
|
|
$mockRequest = $this->getMockBuilder( FauxRequest::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
|
->onlyMethods( [ 'getSession' ] )
|
2016-07-07 21:24:50 +00:00
|
|
|
|
->getMock();
|
|
|
|
|
|
$mockRequest->method( 'getSession' )->willReturn( $session );
|
|
|
|
|
|
$userWrapper->mRequest = $mockRequest;
|
|
|
|
|
|
|
2019-04-09 06:58:04 +00:00
|
|
|
|
$this->resetServices();
|
2016-07-07 21:24:50 +00:00
|
|
|
|
$rights = $user->getRights();
|
|
|
|
|
|
$this->assertContains( 'test', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'runtest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'writetest', $rights );
|
|
|
|
|
|
$this->assertNotContains( 'nukeworld', $rights );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-07-16 16:09:00 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @dataProvider provideGetGroupsWithPermission
|
2013-10-21 21:09:13 +00:00
|
|
|
|
* @covers User::getGroupsWithPermission
|
2011-07-16 16:09:00 +00:00
|
|
|
|
*/
|
2020-01-27 13:25:43 +00:00
|
|
|
|
public function testGetGroupsWithPermission( array $expected, $right ) {
|
2011-12-12 06:03:01 +00:00
|
|
|
|
$result = User::getGroupsWithPermission( $right );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertArrayEquals( $expected, $result );
|
2011-07-16 16:09:00 +00:00
|
|
|
|
}
|
2012-01-19 14:56:18 +00:00
|
|
|
|
|
2012-10-08 10:56:20 +00:00
|
|
|
|
public static function provideGetGroupsWithPermission() {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
return [
|
|
|
|
|
|
[
|
|
|
|
|
|
[ 'unittesters', 'testwriters' ],
|
2011-12-12 06:03:01 +00:00
|
|
|
|
'test'
|
2016-02-17 09:09:32 +00:00
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
[ 'unittesters' ],
|
2011-12-12 06:03:01 +00:00
|
|
|
|
'runtest'
|
2016-02-17 09:09:32 +00:00
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
[ 'testwriters' ],
|
2011-12-12 06:03:01 +00:00
|
|
|
|
'writetest'
|
2016-02-17 09:09:32 +00:00
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
[ 'testwriters' ],
|
2011-12-12 06:03:01 +00:00
|
|
|
|
'modifytest'
|
2016-02-17 09:09:32 +00:00
|
|
|
|
],
|
|
|
|
|
|
];
|
2011-07-16 16:09:00 +00:00
|
|
|
|
}
|
2011-10-11 09:17:36 +00:00
|
|
|
|
|
2020-02-06 20:05:05 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isAllowedAny
|
|
|
|
|
|
* @covers User::isAllowedAll
|
|
|
|
|
|
* @covers User::isAllowed
|
|
|
|
|
|
* @covers User::isNewbie
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsAllowed() {
|
|
|
|
|
|
$this->assertFalse(
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user->isAllowed( 'writetest' ),
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'Basic isAllowed works with a group not granted a right'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertTrue(
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user->isAllowedAny( 'test', 'writetest' ),
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'A user with only one of the rights can pass isAllowedAll'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertTrue(
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user->isAllowedAll( 'test', 'runtest' ),
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'A user with multiple rights can pass isAllowedAll'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertFalse(
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user->isAllowedAll( 'test', 'runtest', 'writetest' ),
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'A user needs all rights specified to pass isAllowedAll'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertTrue(
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user->isNewbie(),
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'Unit testers are not autoconfirmed yet'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$user = $this->getTestUser( 'testwriters' )->getUser();
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
$user->isAllowed( 'test' ),
|
|
|
|
|
|
'Basic isAllowed works with a group granted a right'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
$user->isAllowed( 'writetest' ),
|
|
|
|
|
|
'Testwriters pass isAllowed with `writetest`'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
$user->isNewbie(),
|
|
|
|
|
|
'Test writers are autoconfirmed'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-09-20 03:02:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::useRCPatrol
|
|
|
|
|
|
* @covers User::useNPPatrol
|
|
|
|
|
|
* @covers User::useFilePatrol
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testPatrolling() {
|
|
|
|
|
|
$user = $this->getTestUser( 'patroller' )->getUser();
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $user->useRCPatrol() );
|
|
|
|
|
|
$this->assertTrue( $user->useNPPatrol() );
|
|
|
|
|
|
$this->assertTrue( $user->useFilePatrol() );
|
|
|
|
|
|
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->assertFalse( $this->user->useRCPatrol() );
|
|
|
|
|
|
$this->assertFalse( $this->user->useNPPatrol() );
|
|
|
|
|
|
$this->assertFalse( $this->user->useFilePatrol() );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getGroups
|
2020-01-27 15:07:25 +00:00
|
|
|
|
* @covers User::getGroupMemberships
|
2019-09-20 03:02:12 +00:00
|
|
|
|
* @covers User::isBot
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testBot() {
|
|
|
|
|
|
$user = $this->getTestUser( 'bot' )->getUser();
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $user->getGroups(), [ 'bot' ] );
|
2020-01-27 15:07:25 +00:00
|
|
|
|
$this->assertArrayHasKey( 'bot', $user->getGroupMemberships() );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
$this->assertTrue( $user->isBot() );
|
|
|
|
|
|
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->assertArrayNotHasKey( 'bot', $this->user->getGroupMemberships() );
|
|
|
|
|
|
$this->assertFalse( $this->user->isBot() );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-01 16:31:07 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @dataProvider provideIPs
|
|
|
|
|
|
* @covers User::isIP
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsIP( $value, $result, $message ) {
|
2021-04-12 14:45:26 +00:00
|
|
|
|
$this->hideDeprecated( 'User::isIP' );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $result, $this->user->isIP( $value ), $message );
|
2014-07-01 16:31:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function provideIPs() {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
return [
|
|
|
|
|
|
[ '', false, 'Empty string' ],
|
|
|
|
|
|
[ ' ', false, 'Blank space' ],
|
|
|
|
|
|
[ '10.0.0.0', true, 'IPv4 private 10/8' ],
|
|
|
|
|
|
[ '10.255.255.255', true, 'IPv4 private 10/8' ],
|
|
|
|
|
|
[ '192.168.1.1', true, 'IPv4 private 192.168/16' ],
|
|
|
|
|
|
[ '203.0.113.0', true, 'IPv4 example' ],
|
|
|
|
|
|
[ '2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff', true, 'IPv6 example' ],
|
2014-07-01 16:31:07 +00:00
|
|
|
|
// Not valid IPs but classified as such by MediaWiki for negated asserting
|
|
|
|
|
|
// of whether this might be the identifier of a logged-out user or whether
|
|
|
|
|
|
// to allow usernames like it.
|
2016-02-17 09:09:32 +00:00
|
|
|
|
[ '300.300.300.300', true, 'Looks too much like an IPv4 address' ],
|
|
|
|
|
|
[ '203.0.113.xxx', true, 'Assigned by UseMod to cloaked logged-out users' ],
|
|
|
|
|
|
];
|
2014-07-01 16:31:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-10-11 10:25:58 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @dataProvider provideUserNames
|
2013-10-21 21:09:13 +00:00
|
|
|
|
* @covers User::isValidUserName
|
2011-10-11 10:25:58 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testIsValidUserName( $username, $result, $message ) {
|
2021-03-25 19:36:44 +00:00
|
|
|
|
$this->hideDeprecated( 'User::isValidUserName' );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $result, $this->user->isValidUserName( $username ), $message );
|
2011-10-11 09:17:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-10-08 10:56:20 +00:00
|
|
|
|
public static function provideUserNames() {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
return [
|
|
|
|
|
|
[ '', false, 'Empty string' ],
|
|
|
|
|
|
[ ' ', false, 'Blank space' ],
|
|
|
|
|
|
[ 'abcd', false, 'Starts with small letter' ],
|
|
|
|
|
|
[ 'Ab/cd', false, 'Contains slash' ],
|
|
|
|
|
|
[ 'Ab cd', true, 'Whitespace' ],
|
|
|
|
|
|
[ '192.168.1.1', false, 'IP' ],
|
2017-04-21 16:17:59 +00:00
|
|
|
|
[ '116.17.184.5/32', false, 'IP range' ],
|
|
|
|
|
|
[ '::e:f:2001/96', false, 'IPv6 range' ],
|
2016-02-17 09:09:32 +00:00
|
|
|
|
[ 'User:Abcd', false, 'Reserved Namespace' ],
|
|
|
|
|
|
[ '12abcd232', true, 'Starts with Numbers' ],
|
|
|
|
|
|
[ '?abcd', true, 'Start with ? mark' ],
|
|
|
|
|
|
[ '#abcd', false, 'Start with #' ],
|
|
|
|
|
|
[ 'Abcdകഖഗഘ', true, ' Mixed scripts' ],
|
|
|
|
|
|
[ 'ജോസ്തോമസ്', false, 'ZWNJ- Format control character' ],
|
|
|
|
|
|
[ 'Ab cd', false, ' Ideographic space' ],
|
|
|
|
|
|
[ '300.300.300.300', false, 'Looks too much like an IPv4 address' ],
|
|
|
|
|
|
[ '302.113.311.900', false, 'Looks too much like an IPv4 address' ],
|
|
|
|
|
|
[ '203.0.113.xxx', false, 'Reserved for usage by UseMod for cloaked logged-out users' ],
|
|
|
|
|
|
];
|
2011-10-11 10:25:58 +00:00
|
|
|
|
}
|
2012-08-13 08:18:18 +00:00
|
|
|
|
|
2012-10-18 03:03:10 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Test User::editCount
|
2012-10-26 11:02:48 +00:00
|
|
|
|
* @group medium
|
2013-10-21 21:09:13 +00:00
|
|
|
|
* @covers User::getEditCount
|
2012-10-18 03:03:10 +00:00
|
|
|
|
*/
|
2016-08-03 15:26:47 +00:00
|
|
|
|
public function testGetEditCount() {
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
2012-10-18 03:03:10 +00:00
|
|
|
|
|
2012-10-26 11:02:48 +00:00
|
|
|
|
// let the user have a few (3) edits
|
2019-12-19 07:26:37 +00:00
|
|
|
|
$page = WikiPage::factory( Title::makeTitle( NS_HELP, 'UserTest_EditCount' ) );
|
2013-02-14 11:36:35 +00:00
|
|
|
|
for ( $i = 0; $i < 3; $i++ ) {
|
2016-09-15 20:13:22 +00:00
|
|
|
|
$page->doEditContent(
|
|
|
|
|
|
ContentHandler::makeContent( (string)$i, $page->getTitle() ),
|
|
|
|
|
|
'test',
|
|
|
|
|
|
0,
|
|
|
|
|
|
false,
|
|
|
|
|
|
$user
|
|
|
|
|
|
);
|
2012-10-18 03:03:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame(
|
2014-04-24 12:50:36 +00:00
|
|
|
|
3,
|
|
|
|
|
|
$user->getEditCount(),
|
|
|
|
|
|
'After three edits, the user edit count should be 3'
|
|
|
|
|
|
);
|
2012-10-18 03:03:10 +00:00
|
|
|
|
|
2016-08-03 15:26:47 +00:00
|
|
|
|
// increase the edit count
|
2012-10-18 03:03:10 +00:00
|
|
|
|
$user->incEditCount();
|
2018-10-22 22:58:02 +00:00
|
|
|
|
$user->clearInstanceCache();
|
2012-10-18 03:03:10 +00:00
|
|
|
|
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame(
|
2014-04-24 12:50:36 +00:00
|
|
|
|
4,
|
|
|
|
|
|
$user->getEditCount(),
|
|
|
|
|
|
'After increasing the edit count manually, the user edit count should be 4'
|
|
|
|
|
|
);
|
2012-10-18 03:03:10 +00:00
|
|
|
|
}
|
2012-10-29 20:07:49 +00:00
|
|
|
|
|
2016-08-03 15:26:47 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Test User::editCount
|
|
|
|
|
|
* @group medium
|
|
|
|
|
|
* @covers User::getEditCount
|
2019-09-20 03:02:12 +00:00
|
|
|
|
* @covers User::incEditCount
|
2016-08-03 15:26:47 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testGetEditCountForAnons() {
|
|
|
|
|
|
$user = User::newFromName( 'Anonymous' );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertNull(
|
|
|
|
|
|
$user->getEditCount(),
|
|
|
|
|
|
'Edit count starts null for anonymous users.'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2019-09-20 03:02:12 +00:00
|
|
|
|
$this->assertNull(
|
|
|
|
|
|
$user->incEditCount(),
|
|
|
|
|
|
'Edit count cannot be increased for anonymous users'
|
|
|
|
|
|
);
|
2016-08-03 15:26:47 +00:00
|
|
|
|
|
|
|
|
|
|
$this->assertNull(
|
|
|
|
|
|
$user->getEditCount(),
|
|
|
|
|
|
'Edit count remains null for anonymous users despite calls to increase it.'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Test User::editCount
|
|
|
|
|
|
* @group medium
|
|
|
|
|
|
* @covers User::incEditCount
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIncEditCount() {
|
|
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$user->incEditCount();
|
|
|
|
|
|
|
|
|
|
|
|
$reloadedUser = User::newFromId( $user->getId() );
|
|
|
|
|
|
$reloadedUser->incEditCount();
|
|
|
|
|
|
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame(
|
2016-08-03 15:26:47 +00:00
|
|
|
|
2,
|
|
|
|
|
|
$reloadedUser->getEditCount(),
|
|
|
|
|
|
'Increasing the edit count after a fresh load leaves the object up to date.'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-10-29 20:07:49 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Test changing user options.
|
2013-10-21 21:09:13 +00:00
|
|
|
|
* @covers User::setOption
|
2020-02-13 03:27:25 +00:00
|
|
|
|
* @covers User::getOptions
|
2019-12-19 09:37:56 +00:00
|
|
|
|
* @covers User::getBoolOption
|
|
|
|
|
|
* @covers User::getIntOption
|
2020-01-27 15:07:25 +00:00
|
|
|
|
* @covers User::getStubThreshold
|
2012-10-29 20:07:49 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testOptions() {
|
2020-01-27 15:07:25 +00:00
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgMaxArticleSize' => 2,
|
|
|
|
|
|
] );
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
2012-10-29 20:07:49 +00:00
|
|
|
|
|
2014-09-20 16:56:04 +00:00
|
|
|
|
$user->setOption( 'userjs-someoption', 'test' );
|
2019-12-19 09:37:56 +00:00
|
|
|
|
$user->setOption( 'userjs-someintoption', '42' );
|
2017-01-09 10:56:41 +00:00
|
|
|
|
$user->setOption( 'rclimit', 200 );
|
2018-03-23 01:14:41 +00:00
|
|
|
|
$user->setOption( 'wpwatchlistdays', '0' );
|
2020-01-27 15:07:25 +00:00
|
|
|
|
$user->setOption( 'stubthreshold', 1024 );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$user->setOption( 'userjs-usedefaultoverride', '' );
|
2012-10-29 20:07:49 +00:00
|
|
|
|
$user->saveSettings();
|
|
|
|
|
|
|
2020-01-17 06:21:28 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $user );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( 'test', $user->getOption( 'userjs-someoption' ) );
|
2019-12-19 09:37:56 +00:00
|
|
|
|
$this->assertTrue( $user->getBoolOption( 'userjs-someoption' ) );
|
2017-01-09 10:56:41 +00:00
|
|
|
|
$this->assertEquals( 200, $user->getOption( 'rclimit' ) );
|
2019-12-19 09:37:56 +00:00
|
|
|
|
$this->assertSame( 42, $user->getIntOption( 'userjs-someintoption' ) );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
123,
|
|
|
|
|
|
$user->getIntOption( 'userjs-usedefaultoverride', 123 ),
|
|
|
|
|
|
'Int options that are empty string can have a default returned'
|
|
|
|
|
|
);
|
2020-01-27 15:07:25 +00:00
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
1024,
|
|
|
|
|
|
$user->getStubThreshold(),
|
|
|
|
|
|
'Valid stub threshold preferences are respected'
|
|
|
|
|
|
);
|
2016-11-17 00:38:09 +00:00
|
|
|
|
|
2020-01-17 06:21:28 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $user );
|
2016-11-17 00:38:09 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( 'test', $user->getOption( 'userjs-someoption' ) );
|
2019-12-19 09:37:56 +00:00
|
|
|
|
$this->assertTrue( $user->getBoolOption( 'userjs-someoption' ) );
|
2017-01-09 10:56:41 +00:00
|
|
|
|
$this->assertEquals( 200, $user->getOption( 'rclimit' ) );
|
2019-12-19 09:37:56 +00:00
|
|
|
|
$this->assertSame( 42, $user->getIntOption( 'userjs-someintoption' ) );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
0,
|
|
|
|
|
|
$user->getIntOption( 'userjs-usedefaultoverride' ),
|
|
|
|
|
|
'Int options that are empty string and have no default specified default to 0'
|
|
|
|
|
|
);
|
2020-01-27 15:07:25 +00:00
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
1024,
|
|
|
|
|
|
$user->getStubThreshold(),
|
|
|
|
|
|
'Valid stub threshold preferences are respected after cache is cleared'
|
|
|
|
|
|
);
|
2018-03-23 01:14:41 +00:00
|
|
|
|
|
|
|
|
|
|
// Check that an option saved as a string '0' is returned as an integer.
|
2020-01-17 06:21:28 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $user );
|
2018-03-23 01:14:41 +00:00
|
|
|
|
$this->assertSame( 0, $user->getOption( 'wpwatchlistdays' ) );
|
2019-12-19 09:37:56 +00:00
|
|
|
|
$this->assertFalse( $user->getBoolOption( 'wpwatchlistdays' ) );
|
2020-01-27 15:07:25 +00:00
|
|
|
|
|
|
|
|
|
|
// Check that getStubThreashold resorts to 0 if invalid
|
|
|
|
|
|
$user->setOption( 'stubthreshold', 4096 );
|
|
|
|
|
|
$user->saveSettings();
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
0,
|
|
|
|
|
|
$user->getStubThreshold(),
|
|
|
|
|
|
'If a stub threashold is impossible, it defaults to 0'
|
|
|
|
|
|
);
|
2012-10-29 20:07:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-02-20 23:45:58 +00:00
|
|
|
|
* T39963
|
2012-10-29 20:07:49 +00:00
|
|
|
|
* Make sure defaults are loaded when setOption is called.
|
2020-01-17 06:21:28 +00:00
|
|
|
|
* @covers User::setOption
|
2012-10-29 20:07:49 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testAnonOptions() {
|
|
|
|
|
|
global $wgDefaultUserOptions;
|
2014-09-20 16:56:04 +00:00
|
|
|
|
$this->user->setOption( 'userjs-someoption', 'test' );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $wgDefaultUserOptions['rclimit'], $this->user->getOption( 'rclimit' ) );
|
|
|
|
|
|
$this->assertSame( 'test', $this->user->getOption( 'userjs-someoption' ) );
|
2012-10-29 20:07:49 +00:00
|
|
|
|
}
|
Password Expiration
Add functionality to expire users' passwords:
* Adds column to the user table to keep a password expiration
* Adds $wgPasswordExpirationDays, which will force users to reset
their passwords after a set number of days. By default, this set
to false, so passwords never expire.
* Adds a default grace period of 7 days, where if the user's password
is expired, they can still login, but are encouraged to reset their
password.
* Adds hook 'LoginPasswordResetMessage' to update reset message, in
case an extension wants to vary the message on a particular reset
event.
* Adds hook 'ResetPasswordExpiration' to allow extensions to change
the expiration date when the user resets their password. E.g., if
an extension wants to vary the expiration based on the user's group.
If the user is in the grace period, they get a password reset form
added to the login successful page. If an extension prevents showing
the login successful page (like CentralAuth), it should be updated to
show a password change form during the grace period. After the grace
period, the user will not be able to login without changing their
password.
Also prevents a successful reset if the user is "changing" their
password to their existing password.
No passwords will expire by default. Sites will have to call
User->expirePassword() from their own maintenance script to trigger a
password reset for a user.
Bug: 54997
Change-Id: I92a9fc63b409b182b1d7b48781d73fc7216f8061
2013-10-09 18:09:28 +00:00
|
|
|
|
|
2014-03-12 01:47:29 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Test password validity checks. There are 3 checks in core,
|
|
|
|
|
|
* - ensure the password meets the minimal length
|
|
|
|
|
|
* - ensure the password is not the same as the username
|
|
|
|
|
|
* - ensure the username/password combo isn't forbidden
|
|
|
|
|
|
* @covers User::checkPasswordValidity()
|
|
|
|
|
|
* @covers User::isValidPassword()
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testCheckPasswordValidity() {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgPasswordPolicy' => [
|
|
|
|
|
|
'policies' => [
|
|
|
|
|
|
'sysop' => [
|
2015-04-23 01:48:48 +00:00
|
|
|
|
'MinimalPasswordLength' => 8,
|
|
|
|
|
|
'MinimumPasswordLengthToLogin' => 1,
|
|
|
|
|
|
'PasswordCannotMatchUsername' => 1,
|
2020-01-06 23:10:14 +00:00
|
|
|
|
'PasswordCannotBeSubstringInUsername' => 1,
|
2016-02-17 09:09:32 +00:00
|
|
|
|
],
|
|
|
|
|
|
'default' => [
|
2015-04-23 01:48:48 +00:00
|
|
|
|
'MinimalPasswordLength' => 6,
|
|
|
|
|
|
'PasswordCannotMatchUsername' => true,
|
2020-01-06 23:10:14 +00:00
|
|
|
|
'PasswordCannotBeSubstringInUsername' => true,
|
2020-06-16 18:06:45 +00:00
|
|
|
|
'PasswordCannotMatchDefaults' => true,
|
2016-05-18 09:19:20 +00:00
|
|
|
|
'MaximalPasswordLength' => 40,
|
2016-02-17 09:09:32 +00:00
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
'checks' => [
|
2015-04-23 01:48:48 +00:00
|
|
|
|
'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength',
|
|
|
|
|
|
'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin',
|
|
|
|
|
|
'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername',
|
2020-01-06 23:10:14 +00:00
|
|
|
|
'PasswordCannotBeSubstringInUsername' =>
|
|
|
|
|
|
'PasswordPolicyChecks::checkPasswordCannotBeSubstringInUsername',
|
2020-06-16 18:06:45 +00:00
|
|
|
|
'PasswordCannotMatchDefaults' => 'PasswordPolicyChecks::checkPasswordCannotMatchDefaults',
|
2015-04-23 01:48:48 +00:00
|
|
|
|
'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength',
|
2016-02-17 09:09:32 +00:00
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
] );
|
2015-04-23 01:48:48 +00:00
|
|
|
|
|
2014-03-12 01:47:29 +00:00
|
|
|
|
// Sanity
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->assertTrue( $this->user->isValidPassword( 'Password1234' ) );
|
2014-03-12 01:47:29 +00:00
|
|
|
|
|
|
|
|
|
|
// Minimum length
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->assertFalse( $this->user->isValidPassword( 'a' ) );
|
|
|
|
|
|
$this->assertFalse( $this->user->checkPasswordValidity( 'a' )->isGood() );
|
|
|
|
|
|
$this->assertTrue( $this->user->checkPasswordValidity( 'a' )->isOK() );
|
2014-03-12 01:47:29 +00:00
|
|
|
|
|
2014-12-26 16:29:15 +00:00
|
|
|
|
// Maximum length
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$longPass = str_repeat( 'a', 41 );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->assertFalse( $this->user->isValidPassword( $longPass ) );
|
|
|
|
|
|
$this->assertFalse( $this->user->checkPasswordValidity( $longPass )->isGood() );
|
|
|
|
|
|
$this->assertFalse( $this->user->checkPasswordValidity( $longPass )->isOK() );
|
2014-12-26 16:29:15 +00:00
|
|
|
|
|
2014-03-12 01:47:29 +00:00
|
|
|
|
// Matches username
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->assertFalse( $this->user->checkPasswordValidity( $this->user->getName() )->isGood() );
|
|
|
|
|
|
$this->assertTrue( $this->user->checkPasswordValidity( $this->user->getName() )->isOK() );
|
2014-03-12 01:47:29 +00:00
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'isValidPassword', static function ( $password, &$result, $user ) {
|
2020-02-06 20:05:05 +00:00
|
|
|
|
$result = 'isValidPassword returned false';
|
|
|
|
|
|
return false;
|
|
|
|
|
|
} );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$status = $this->user->checkPasswordValidity( 'Password1234' );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
$this->assertTrue( $status->isOK() );
|
|
|
|
|
|
$this->assertFalse( $status->isGood() );
|
|
|
|
|
|
$this->assertSame( $status->getErrors()[0]['message'], 'isValidPassword returned false' );
|
|
|
|
|
|
|
2020-04-15 19:30:38 +00:00
|
|
|
|
$this->removeTemporaryHook( 'isValidPassword' );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'isValidPassword', static function ( $password, &$result, $user ) {
|
2020-02-06 20:05:05 +00:00
|
|
|
|
$result = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$status = $this->user->checkPasswordValidity( 'Password1234' );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
$this->assertTrue( $status->isOK() );
|
|
|
|
|
|
$this->assertTrue( $status->isGood() );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( [], $status->getErrors() );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
|
2020-04-15 19:30:38 +00:00
|
|
|
|
$this->removeTemporaryHook( 'isValidPassword' );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'isValidPassword', static function ( $password, &$result, $user ) {
|
2020-02-06 20:05:05 +00:00
|
|
|
|
$result = 'isValidPassword returned true';
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$status = $this->user->checkPasswordValidity( 'Password1234' );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
$this->assertTrue( $status->isOK() );
|
|
|
|
|
|
$this->assertFalse( $status->isGood() );
|
|
|
|
|
|
$this->assertSame( $status->getErrors()[0]['message'], 'isValidPassword returned true' );
|
|
|
|
|
|
|
2020-04-15 19:30:38 +00:00
|
|
|
|
$this->removeTemporaryHook( 'isValidPassword' );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
|
2014-03-12 01:47:29 +00:00
|
|
|
|
// On the forbidden list
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = User::newFromName( 'Useruser' );
|
2014-03-12 01:47:29 +00:00
|
|
|
|
$this->assertFalse( $user->checkPasswordValidity( 'Passpass' )->isGood() );
|
|
|
|
|
|
}
|
2014-08-25 18:24:10 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getCanonicalName()
|
|
|
|
|
|
* @dataProvider provideGetCanonicalName
|
|
|
|
|
|
*/
|
2020-01-27 13:25:43 +00:00
|
|
|
|
public function testGetCanonicalName( $name, array $expectedArray ) {
|
2021-05-28 13:38:53 +00:00
|
|
|
|
$this->hideDeprecated( 'User::getCanonicalName' );
|
2016-04-16 17:36:33 +00:00
|
|
|
|
// fake interwiki map for the 'Interwiki prefix' testcase
|
2021-05-05 00:03:26 +00:00
|
|
|
|
// DummyServicesTrait::getDummyInterwikiLookup
|
|
|
|
|
|
$interwikiLookup = $this->getDummyInterwikiLookup( [ 'interwiki' ] );
|
|
|
|
|
|
$this->setService( 'InterwikiLookup', $interwikiLookup );
|
2016-04-16 17:36:33 +00:00
|
|
|
|
|
2014-08-25 18:24:10 +00:00
|
|
|
|
foreach ( $expectedArray as $validate => $expected ) {
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame(
|
2014-08-25 18:24:10 +00:00
|
|
|
|
$expected,
|
2020-01-27 13:25:43 +00:00
|
|
|
|
User::getCanonicalName( $name, $validate === 'false' ? false : $validate ),
|
|
|
|
|
|
$validate
|
|
|
|
|
|
);
|
2014-08-25 18:24:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-09-18 01:28:26 +00:00
|
|
|
|
public static function provideGetCanonicalName() {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
return [
|
2016-04-16 17:36:33 +00:00
|
|
|
|
'Leading space' => [ ' Leading space', [ 'creatable' => 'Leading space' ] ],
|
|
|
|
|
|
'Trailing space ' => [ 'Trailing space ', [ 'creatable' => 'Trailing space' ] ],
|
|
|
|
|
|
'Namespace prefix' => [ 'Talk:Username', [ 'creatable' => false, 'usable' => false,
|
|
|
|
|
|
'valid' => false, 'false' => 'Talk:Username' ] ],
|
|
|
|
|
|
'Interwiki prefix' => [ 'interwiki:Username', [ 'creatable' => false, 'usable' => false,
|
|
|
|
|
|
'valid' => false, 'false' => 'Interwiki:Username' ] ],
|
|
|
|
|
|
'With hash' => [ 'name with # hash', [ 'creatable' => false, 'usable' => false ] ],
|
|
|
|
|
|
'Multi spaces' => [ 'Multi spaces', [ 'creatable' => 'Multi spaces',
|
|
|
|
|
|
'usable' => 'Multi spaces' ] ],
|
|
|
|
|
|
'Lowercase' => [ 'lowercase', [ 'creatable' => 'Lowercase' ] ],
|
|
|
|
|
|
'Invalid character' => [ 'in[]valid', [ 'creatable' => false, 'usable' => false,
|
|
|
|
|
|
'valid' => false, 'false' => 'In[]valid' ] ],
|
|
|
|
|
|
'With slash' => [ 'with / slash', [ 'creatable' => false, 'usable' => false, 'valid' => false,
|
|
|
|
|
|
'false' => 'With / slash' ] ],
|
2016-02-17 09:09:32 +00:00
|
|
|
|
];
|
2014-08-25 18:24:10 +00:00
|
|
|
|
}
|
2014-12-11 08:59:28 +00:00
|
|
|
|
|
2020-01-27 15:07:25 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getCanonicalName()
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetCanonicalName_bad() {
|
2021-05-28 13:38:53 +00:00
|
|
|
|
$this->hideDeprecated( 'User::getCanonicalName' );
|
2020-01-27 15:07:25 +00:00
|
|
|
|
$this->expectException( InvalidArgumentException::class );
|
|
|
|
|
|
$this->expectExceptionMessage(
|
Add a new UserNameUtils service
This replaces User::isValidUserName, ::isUsableName, ::isCreatableName,
::getCanonicalName, and ::isIP.
Unlike User::isIP, UserNameUtils::isIP will //not// return true
for IPv6 ranges.
UserNameUtils::isIPRange, like User::isIPRange, accepts a name and
simply calls IPUtils::isValidRange.
User::isValidUserName, ::isUsableName, ::isCreatableName,
::getCanonical, ::isIP, and ::isValidRange are all soft deprecated
A follow up patch will add this to the release notes, to avoid merge
conflicts.
Bug: T245231
Bug: T239527
Change-Id: I46684bc492bb74b728ff102971f6cdd4d746a50a
2020-02-23 23:52:44 +00:00
|
|
|
|
'Invalid parameter value for validation'
|
2020-01-27 15:07:25 +00:00
|
|
|
|
);
|
|
|
|
|
|
User::getCanonicalName( 'ValidName', 'InvalidValidationValue' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-01-13 18:25:41 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::equals
|
|
|
|
|
|
*/
|
2014-12-11 08:59:28 +00:00
|
|
|
|
public function testEquals() {
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$first = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$second = User::newFromName( $first->getName() );
|
2014-12-11 08:59:28 +00:00
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $first->equals( $first ) );
|
|
|
|
|
|
$this->assertTrue( $first->equals( $second ) );
|
|
|
|
|
|
$this->assertTrue( $second->equals( $first ) );
|
|
|
|
|
|
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$third = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$fourth = $this->getMutableTestUser()->getUser();
|
2014-12-11 08:59:28 +00:00
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $third->equals( $fourth ) );
|
|
|
|
|
|
$this->assertFalse( $fourth->equals( $third ) );
|
|
|
|
|
|
|
|
|
|
|
|
// Test users loaded from db with id
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$fifth = User::newFromId( $user->getId() );
|
|
|
|
|
|
$sixth = User::newFromName( $user->getName() );
|
2014-12-11 08:59:28 +00:00
|
|
|
|
$this->assertTrue( $fifth->equals( $sixth ) );
|
|
|
|
|
|
}
|
2015-04-02 01:15:50 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getId
|
2020-01-04 09:35:20 +00:00
|
|
|
|
* @covers User::setId
|
2015-04-02 01:15:50 +00:00
|
|
|
|
*/
|
2020-01-04 09:35:20 +00:00
|
|
|
|
public function testUserId() {
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->assertGreaterThan( 0, $this->user->getId() );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'UserWithNoId' );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( 0, $user->getId() );
|
2020-01-04 09:35:20 +00:00
|
|
|
|
|
|
|
|
|
|
$user->setId( 7 );
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
7,
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$user->getId(),
|
2020-01-04 09:35:20 +00:00
|
|
|
|
'Manually setting a user id via ::setId is reflected in ::getId'
|
|
|
|
|
|
);
|
2020-02-04 03:23:26 +00:00
|
|
|
|
|
|
|
|
|
|
$user = new User;
|
|
|
|
|
|
$user->setName( '1.2.3.4' );
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
0,
|
|
|
|
|
|
$user->getId(),
|
|
|
|
|
|
'IPs have an id of 0'
|
|
|
|
|
|
);
|
2015-04-02 01:15:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2019-04-28 11:07:18 +00:00
|
|
|
|
* @covers User::isRegistered
|
2015-04-02 01:15:50 +00:00
|
|
|
|
* @covers User::isLoggedIn
|
|
|
|
|
|
* @covers User::isAnon
|
2020-02-13 03:27:25 +00:00
|
|
|
|
* @covers User::logOut
|
2015-04-02 01:15:50 +00:00
|
|
|
|
*/
|
2020-12-17 23:10:11 +00:00
|
|
|
|
public function testIsRegistered() {
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
2019-04-28 11:07:18 +00:00
|
|
|
|
$this->assertTrue( $user->isRegistered() );
|
2020-12-23 03:24:16 +00:00
|
|
|
|
$this->hideDeprecated( 'User::isLoggedIn' );
|
2020-12-17 23:10:11 +00:00
|
|
|
|
$this->assertTrue( $user->isLoggedIn() ); // Deprecated wrapper method
|
2015-04-02 01:15:50 +00:00
|
|
|
|
$this->assertFalse( $user->isAnon() );
|
|
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'UserLogout', static function ( &$user ) {
|
2020-02-13 03:27:25 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
} );
|
|
|
|
|
|
$user->logout();
|
2020-12-17 23:10:11 +00:00
|
|
|
|
$this->assertTrue( $user->isRegistered() );
|
2020-02-13 03:27:25 +00:00
|
|
|
|
|
2020-04-15 19:30:38 +00:00
|
|
|
|
$this->removeTemporaryHook( 'UserLogout' );
|
2020-02-13 03:27:25 +00:00
|
|
|
|
$user->logout();
|
2020-12-17 23:10:11 +00:00
|
|
|
|
$this->assertFalse( $user->isRegistered() );
|
2020-02-13 03:27:25 +00:00
|
|
|
|
|
2015-04-02 01:15:50 +00:00
|
|
|
|
// Non-existent users are perceived as anonymous
|
|
|
|
|
|
$user = User::newFromName( 'UTNonexistent' );
|
2019-04-28 11:07:18 +00:00
|
|
|
|
$this->assertFalse( $user->isRegistered() );
|
2020-12-23 03:24:16 +00:00
|
|
|
|
$this->hideDeprecated( 'User::isLoggedIn' );
|
2020-12-17 23:10:11 +00:00
|
|
|
|
$this->assertFalse( $user->isLoggedIn() ); // Deprecated wrapper method
|
2015-04-02 01:15:50 +00:00
|
|
|
|
$this->assertTrue( $user->isAnon() );
|
|
|
|
|
|
|
|
|
|
|
|
$user = new User;
|
2019-04-28 11:07:18 +00:00
|
|
|
|
$this->assertFalse( $user->isRegistered() );
|
2020-12-23 03:24:16 +00:00
|
|
|
|
$this->hideDeprecated( 'User::isLoggedIn' );
|
2020-12-17 23:10:11 +00:00
|
|
|
|
$this->assertFalse( $user->isLoggedIn() ); // Deprecated wrapper method
|
2015-04-02 01:15:50 +00:00
|
|
|
|
$this->assertTrue( $user->isAnon() );
|
|
|
|
|
|
}
|
2015-04-07 20:50:00 +00:00
|
|
|
|
|
2019-09-20 03:02:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::setRealName
|
|
|
|
|
|
* @covers User::getRealName
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testRealName() {
|
|
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$realName = 'John Doe';
|
|
|
|
|
|
|
|
|
|
|
|
$user->setRealName( $realName );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
$realName,
|
|
|
|
|
|
$user->getRealName(),
|
|
|
|
|
|
'Real name retrieved from cache'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$id = $user->getId();
|
|
|
|
|
|
$user->saveSettings();
|
|
|
|
|
|
|
|
|
|
|
|
$otherUser = User::newFromId( $id );
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
$realName,
|
|
|
|
|
|
$user->getRealName(),
|
|
|
|
|
|
'Real name retrieved from database'
|
|
|
|
|
|
);
|
2019-09-20 03:02:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-04-07 20:50:00 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::checkAndSetTouched
|
2020-01-04 09:35:20 +00:00
|
|
|
|
* @covers User::getDBTouched()
|
2015-04-07 20:50:00 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testCheckAndSetTouched() {
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$user = TestingAccessWrapper::newFromObject( $user );
|
2020-12-17 23:10:11 +00:00
|
|
|
|
$this->assertTrue( $user->isRegistered() );
|
2015-04-07 20:50:00 +00:00
|
|
|
|
|
|
|
|
|
|
$touched = $user->getDBTouched();
|
|
|
|
|
|
$this->assertTrue(
|
2018-08-31 21:08:08 +00:00
|
|
|
|
$user->checkAndSetTouched(), "checkAndSetTouched() succedeed" );
|
2015-04-07 20:50:00 +00:00
|
|
|
|
$this->assertGreaterThan(
|
|
|
|
|
|
$touched, $user->getDBTouched(), "user_touched increased with casOnTouched()" );
|
|
|
|
|
|
|
|
|
|
|
|
$touched = $user->getDBTouched();
|
|
|
|
|
|
$this->assertTrue(
|
2018-08-31 21:08:08 +00:00
|
|
|
|
$user->checkAndSetTouched(), "checkAndSetTouched() succedeed #2" );
|
2015-04-07 20:50:00 +00:00
|
|
|
|
$this->assertGreaterThan(
|
|
|
|
|
|
$touched, $user->getDBTouched(), "user_touched increased with casOnTouched() #2" );
|
|
|
|
|
|
}
|
2016-07-27 01:44:53 +00:00
|
|
|
|
|
2020-02-13 03:27:25 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::validateCache
|
|
|
|
|
|
* @covers User::getTouched
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testValidateCache() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
|
|
|
|
|
|
$initialTouchMW = $user->getTouched();
|
|
|
|
|
|
$initialTouchUnix = ( new MWTimestamp( $initialTouchMW ) )->getTimestamp();
|
|
|
|
|
|
|
|
|
|
|
|
$earlierUnix = $initialTouchUnix - 1000;
|
|
|
|
|
|
$earlierMW = ( new MWTimestamp( $earlierUnix ) )->getTimestamp( TS_MW );
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
$user->validateCache( $earlierMW ),
|
|
|
|
|
|
'Caches from before the value of getTouched() are not valid'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$laterUnix = $initialTouchUnix + 1000;
|
|
|
|
|
|
$laterMW = ( new MWTimestamp( $laterUnix ) )->getTimestamp( TS_MW );
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
$user->validateCache( $laterMW ),
|
|
|
|
|
|
'Caches from after the value of getTouched() are valid'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-27 01:44:53 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::findUsersByGroup
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testFindUsersByGroup() {
|
2018-08-01 07:25:32 +00:00
|
|
|
|
// FIXME: fails under postgres
|
|
|
|
|
|
$this->markTestSkippedIfDbType( 'postgres' );
|
|
|
|
|
|
|
2016-07-27 01:44:53 +00:00
|
|
|
|
$users = User::findUsersByGroup( [] );
|
2019-09-17 14:31:49 +00:00
|
|
|
|
$this->assertSame( 0, iterator_count( $users ) );
|
2016-07-27 01:44:53 +00:00
|
|
|
|
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$users = User::findUsersByGroup( 'foo', 1, 1 );
|
2019-09-17 14:31:49 +00:00
|
|
|
|
$this->assertSame( 0, iterator_count( $users ) );
|
2016-07-27 01:44:53 +00:00
|
|
|
|
|
|
|
|
|
|
$user = $this->getMutableTestUser( [ 'foo' ] )->getUser();
|
|
|
|
|
|
$users = User::findUsersByGroup( 'foo' );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( 1, iterator_count( $users ) );
|
2016-07-27 01:44:53 +00:00
|
|
|
|
$users->rewind();
|
|
|
|
|
|
$this->assertTrue( $user->equals( $users->current() ) );
|
|
|
|
|
|
|
|
|
|
|
|
// arguments have OR relationship
|
|
|
|
|
|
$user2 = $this->getMutableTestUser( [ 'bar' ] )->getUser();
|
|
|
|
|
|
$users = User::findUsersByGroup( [ 'foo', 'bar' ] );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( 2, iterator_count( $users ) );
|
2016-07-27 01:44:53 +00:00
|
|
|
|
$users->rewind();
|
|
|
|
|
|
$this->assertTrue( $user->equals( $users->current() ) );
|
|
|
|
|
|
$users->next();
|
|
|
|
|
|
$this->assertTrue( $user2->equals( $users->current() ) );
|
|
|
|
|
|
|
|
|
|
|
|
// users are not duplicated
|
|
|
|
|
|
$user = $this->getMutableTestUser( [ 'baz', 'boom' ] )->getUser();
|
|
|
|
|
|
$users = User::findUsersByGroup( [ 'baz', 'boom' ] );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( 1, iterator_count( $users ) );
|
2016-07-27 01:44:53 +00:00
|
|
|
|
$users->rewind();
|
|
|
|
|
|
$this->assertTrue( $user->equals( $users->current() ) );
|
|
|
|
|
|
}
|
2013-02-07 21:56:54 +00:00
|
|
|
|
|
2019-02-01 19:44:21 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getBlockedStatus
|
|
|
|
|
|
*/
|
2016-11-23 19:51:30 +00:00
|
|
|
|
public function testSoftBlockRanges() {
|
2019-03-18 21:50:48 +00:00
|
|
|
|
$this->setMwGlobals( 'wgSoftBlockRanges', [ '10.0.0.0/8' ] );
|
2016-11-23 19:51:30 +00:00
|
|
|
|
|
|
|
|
|
|
// IP isn't in $wgSoftBlockRanges
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$user = new User();
|
2016-11-23 19:51:30 +00:00
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '192.168.0.1' );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->setSessionUser( $user, $request );
|
|
|
|
|
|
$this->assertNull( $user->getBlock() );
|
2016-11-23 19:51:30 +00:00
|
|
|
|
|
|
|
|
|
|
// IP is in $wgSoftBlockRanges
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$user = new User();
|
2016-11-23 19:51:30 +00:00
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '10.20.30.40' );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->setSessionUser( $user, $request );
|
|
|
|
|
|
$block = $user->getBlock();
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
|
$this->assertInstanceOf( SystemBlock::class, $block );
|
2016-11-23 19:51:30 +00:00
|
|
|
|
$this->assertSame( 'wgSoftBlockRanges', $block->getSystemBlockType() );
|
|
|
|
|
|
|
|
|
|
|
|
// Make sure the block is really soft
|
2019-03-18 21:50:48 +00:00
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '10.20.30.40' );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->setSessionUser( $this->user, $request );
|
|
|
|
|
|
$this->assertFalse( $this->user->isAnon(), 'sanity check' );
|
|
|
|
|
|
$this->assertNull( $this->user->getBlock() );
|
2016-11-23 19:51:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-07 20:57:19 +00:00
|
|
|
|
public function provideIsPingLimitable() {
|
|
|
|
|
|
yield 'Not ip excluded' => [ [], null, true ];
|
|
|
|
|
|
yield 'Ip excluded' => [ [ '1.2.3.4' ], null, false ];
|
|
|
|
|
|
yield 'Ip subnet excluded' => [ [ '1.2.3.0/8' ], null, false ];
|
|
|
|
|
|
yield 'noratelimit right' => [ [], 'noratelimit', false ];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-25 07:27:12 +00:00
|
|
|
|
/**
|
2021-01-07 20:57:19 +00:00
|
|
|
|
* @dataProvider provideIsPingLimitable
|
2017-12-25 07:27:12 +00:00
|
|
|
|
* @covers User::isPingLimitable
|
2021-01-07 20:57:19 +00:00
|
|
|
|
* @param array $rateLimitExcludeIps
|
|
|
|
|
|
* @param string|null $rightOverride
|
|
|
|
|
|
* @param bool $expected
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsPingLimitable(
|
|
|
|
|
|
array $rateLimitExcludeIps,
|
|
|
|
|
|
?string $rightOverride,
|
|
|
|
|
|
bool $expected
|
|
|
|
|
|
) {
|
2017-02-02 01:23:01 +00:00
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '1.2.3.4' );
|
|
|
|
|
|
$user = User::newFromSession( $request );
|
2021-01-07 20:57:19 +00:00
|
|
|
|
// We are trying to test for current user behaviour
|
|
|
|
|
|
// since we are interested in request IP
|
|
|
|
|
|
RequestContext::getMain()->setUser( $user );
|
2017-02-02 01:23:01 +00:00
|
|
|
|
|
2021-01-07 20:57:19 +00:00
|
|
|
|
$this->setMwGlobals( 'wgRateLimitsExcludedIPs', $rateLimitExcludeIps );
|
|
|
|
|
|
if ( $rightOverride ) {
|
|
|
|
|
|
$this->overrideUserPermissions( $user, $rightOverride );
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->assertSame( $expected, $user->isPingLimitable() );
|
2017-02-02 01:23:01 +00:00
|
|
|
|
}
|
2017-02-10 14:18:02 +00:00
|
|
|
|
|
|
|
|
|
|
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' ],
|
2020-12-12 11:24:19 +00:00
|
|
|
|
[ 9, null, 'newcomer' ],
|
|
|
|
|
|
[ 10, null, 'learner' ],
|
|
|
|
|
|
[ 501, null, 'experienced' ],
|
2017-02-10 14:18:02 +00:00
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-12-25 07:27:12 +00:00
|
|
|
|
* @covers User::getExperienceLevel
|
2017-02-10 14:18:02 +00:00
|
|
|
|
* @dataProvider provideExperienceLevel
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testExperienceLevel( $editCount, $memberSince, $expLevel ) {
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgLearnerEdits' => 10,
|
|
|
|
|
|
'wgLearnerMemberSince' => 4,
|
|
|
|
|
|
'wgExperiencedUserEdits' => 500,
|
|
|
|
|
|
'wgExperiencedUserMemberSince' => 30,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2021-04-29 02:37:11 +00:00
|
|
|
|
$db = wfGetDB( DB_PRIMARY );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$userQuery = User::getQueryInfo();
|
|
|
|
|
|
$row = $db->selectRow(
|
|
|
|
|
|
$userQuery['tables'],
|
|
|
|
|
|
$userQuery['fields'],
|
2020-01-27 13:15:00 +00:00
|
|
|
|
[ 'user_id' => $this->user->getId() ],
|
2017-09-12 17:12:29 +00:00
|
|
|
|
__METHOD__,
|
|
|
|
|
|
[],
|
|
|
|
|
|
$userQuery['joins']
|
|
|
|
|
|
);
|
|
|
|
|
|
$row->user_editcount = $editCount;
|
2020-12-12 11:24:19 +00:00
|
|
|
|
if ( $memberSince !== null ) {
|
|
|
|
|
|
$row->user_registration = $db->timestamp( time() - $memberSince * 86400 );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$row->user_registration = null;
|
|
|
|
|
|
}
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$user = User::newFromRow( $row );
|
2017-02-10 14:18:02 +00:00
|
|
|
|
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $expLevel, $user->getExperienceLevel() );
|
2017-02-10 14:18:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-25 07:27:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getExperienceLevel
|
|
|
|
|
|
*/
|
2017-02-10 14:18:02 +00:00
|
|
|
|
public function testExperienceLevelAnon() {
|
|
|
|
|
|
$user = User::newFromName( '10.11.12.13', false );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $user->getExperienceLevel() );
|
|
|
|
|
|
}
|
2017-04-15 23:52:23 +00:00
|
|
|
|
|
2019-04-05 19:13:17 +00:00
|
|
|
|
public static function provideIsLocallyBlockedProxy() {
|
2017-04-15 23:52:23 +00:00
|
|
|
|
return [
|
|
|
|
|
|
[ '1.2.3.4', '1.2.3.4' ],
|
|
|
|
|
|
[ '1.2.3.4', '1.2.3.0/16' ],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-01-27 15:07:25 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromId
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNewFromId() {
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$userId = $this->user->getId();
|
2020-01-27 15:07:25 +00:00
|
|
|
|
$this->assertGreaterThan(
|
|
|
|
|
|
0,
|
|
|
|
|
|
$userId,
|
|
|
|
|
|
'Sanity check: user has a working id'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$otherUser = User::newFromId( $userId );
|
|
|
|
|
|
$this->assertTrue(
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user->equals( $otherUser ),
|
2020-01-27 15:07:25 +00:00
|
|
|
|
'User created by id should match user with that id'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-01 19:44:21 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromActorId
|
|
|
|
|
|
*/
|
2017-09-12 17:12:29 +00:00
|
|
|
|
public function testActorId() {
|
2021-02-15 18:58:09 +00:00
|
|
|
|
$this->filterDeprecated( '/Passing a parameter to getActorId\(\) is deprecated/', '1.36' );
|
|
|
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
|
// Newly-created user has an actor ID
|
|
|
|
|
|
$user = User::createNew( 'UserTestActorId1' );
|
|
|
|
|
|
$id = $user->getId();
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertGreaterThan( 0, $user->getActorId(), 'User::createNew sets an actor ID' );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'UserTestActorId2' );
|
|
|
|
|
|
$user->addToDatabase();
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertGreaterThan( 0, $user->getActorId(), 'User::addToDatabase sets an actor ID' );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'UserTestActorId1' );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertGreaterThan( 0, $user->getActorId(),
|
|
|
|
|
|
'Actor ID can be retrieved for user loaded by name' );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
|
|
|
|
|
|
$user = User::newFromId( $id );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertGreaterThan( 0, $user->getActorId(),
|
|
|
|
|
|
'Actor ID can be retrieved for user loaded by ID' );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
|
2019-04-04 19:23:41 +00:00
|
|
|
|
$user2 = User::newFromActorId( $user->getActorId() );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $user->getId(), $user2->getId(),
|
2019-04-04 19:23:41 +00:00
|
|
|
|
'User::newFromActorId works for an existing user' );
|
|
|
|
|
|
|
2020-04-01 22:02:16 +00:00
|
|
|
|
$queryInfo = User::getQueryInfo();
|
|
|
|
|
|
$row = $this->db->selectRow( $queryInfo['tables'],
|
|
|
|
|
|
$queryInfo['fields'], [ 'user_id' => $id ], __METHOD__ );
|
2019-04-04 19:23:41 +00:00
|
|
|
|
$user = User::newFromRow( $row );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertGreaterThan( 0, $user->getActorId(),
|
2019-04-04 19:23:41 +00:00
|
|
|
|
'Actor ID can be retrieved for user loaded with User::selectFields()' );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromId( $id );
|
|
|
|
|
|
$user->setName( 'UserTestActorId4-renamed' );
|
|
|
|
|
|
$user->saveSettings();
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame(
|
2019-04-04 19:23:41 +00:00
|
|
|
|
$user->getName(),
|
|
|
|
|
|
$this->db->selectField(
|
|
|
|
|
|
'actor', 'actor_name', [ 'actor_id' => $user->getActorId() ], __METHOD__
|
|
|
|
|
|
),
|
|
|
|
|
|
'User::saveSettings updates actor table for name change'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// For sanity
|
|
|
|
|
|
$ip = '192.168.12.34';
|
|
|
|
|
|
$this->db->delete( 'actor', [ 'actor_name' => $ip ], __METHOD__ );
|
|
|
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$user = User::newFromName( $ip, false );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( 0, $user->getActorId(), 'Anonymous user has no actor ID by default' );
|
2021-02-15 22:26:08 +00:00
|
|
|
|
$this->filterDeprecated( '/Passing parameter of type IDatabase/' );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertGreaterThan( 0, $user->getActorId( $this->db ),
|
2017-09-12 17:12:29 +00:00
|
|
|
|
'Actor ID can be created for an anonymous user' );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( $ip, false );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertGreaterThan( 0, $user->getActorId(),
|
|
|
|
|
|
'Actor ID can be loaded for an anonymous user' );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$user2 = User::newFromActorId( $user->getActorId() );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $user->getName(), $user2->getName(),
|
2017-09-12 17:12:29 +00:00
|
|
|
|
'User::newFromActorId works for an anonymous user' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-27 04:25:24 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getActorId
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testForeignGetActorId() {
|
2021-02-15 18:58:09 +00:00
|
|
|
|
$this->filterDeprecated( '/Passing a parameter to getActorId\(\) is deprecated/', '1.36' );
|
|
|
|
|
|
|
2021-01-27 04:25:24 +00:00
|
|
|
|
$user = User::newFromName( 'UserTestActorId1' );
|
|
|
|
|
|
$this->expectException( PreconditionException::class );
|
|
|
|
|
|
$user->getActorId( 'Foreign Wiki' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getWikiId
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetWiki() {
|
|
|
|
|
|
$user = User::newFromName( 'UserTestActorId1' );
|
|
|
|
|
|
$this->assertSame( User::LOCAL, $user->getWikiId() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::assertWiki
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testAssertWiki() {
|
|
|
|
|
|
$user = User::newFromName( 'UserTestActorId1' );
|
|
|
|
|
|
|
|
|
|
|
|
$user->assertWiki( User::LOCAL );
|
|
|
|
|
|
$this->assertTrue( true, 'User is for local wiki' );
|
|
|
|
|
|
|
|
|
|
|
|
$this->expectException( PreconditionException::class );
|
|
|
|
|
|
$user->assertWiki( 'Foreign Wiki' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-01 19:44:21 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromAnyId
|
|
|
|
|
|
*/
|
2017-09-12 17:12:29 +00:00
|
|
|
|
public function testNewFromAnyId() {
|
|
|
|
|
|
// Registered user
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$user = $this->user;
|
2017-09-12 17:12:29 +00:00
|
|
|
|
for ( $i = 1; $i <= 7; $i++ ) {
|
|
|
|
|
|
$test = User::newFromAnyId(
|
|
|
|
|
|
( $i & 1 ) ? $user->getId() : null,
|
|
|
|
|
|
( $i & 2 ) ? $user->getName() : null,
|
|
|
|
|
|
( $i & 4 ) ? $user->getActorId() : null
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertSame( $user->getId(), $test->getId() );
|
|
|
|
|
|
$this->assertSame( $user->getName(), $test->getName() );
|
|
|
|
|
|
$this->assertSame( $user->getActorId(), $test->getActorId() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Anon user. Can't load by only user ID when that's 0.
|
|
|
|
|
|
$user = User::newFromName( '192.168.12.34', false );
|
2021-02-15 22:26:08 +00:00
|
|
|
|
// Make sure an actor ID exists
|
2021-03-04 21:46:45 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getActorNormalization()->acquireActorId( $user, $this->db );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
|
|
|
|
|
|
$test = User::newFromAnyId( null, '192.168.12.34', null );
|
|
|
|
|
|
$this->assertSame( $user->getId(), $test->getId() );
|
|
|
|
|
|
$this->assertSame( $user->getName(), $test->getName() );
|
|
|
|
|
|
$this->assertSame( $user->getActorId(), $test->getActorId() );
|
|
|
|
|
|
$test = User::newFromAnyId( null, null, $user->getActorId() );
|
|
|
|
|
|
$this->assertSame( $user->getId(), $test->getId() );
|
|
|
|
|
|
$this->assertSame( $user->getName(), $test->getName() );
|
|
|
|
|
|
$this->assertSame( $user->getActorId(), $test->getActorId() );
|
|
|
|
|
|
|
|
|
|
|
|
// Bogus data should still "work" as long as nothing triggers a ->load(),
|
|
|
|
|
|
// and accessing the specified data shouldn't do that.
|
|
|
|
|
|
$test = User::newFromAnyId( 123456, 'Bogus', 654321 );
|
|
|
|
|
|
$this->assertSame( 123456, $test->getId() );
|
|
|
|
|
|
$this->assertSame( 'Bogus', $test->getName() );
|
|
|
|
|
|
$this->assertSame( 654321, $test->getActorId() );
|
|
|
|
|
|
|
2019-05-03 01:57:40 +00:00
|
|
|
|
// Loading remote user by name from remote wiki should succeed
|
|
|
|
|
|
$test = User::newFromAnyId( null, 'Bogus', null, 'foo' );
|
|
|
|
|
|
$this->assertSame( 0, $test->getId() );
|
|
|
|
|
|
$this->assertSame( 'Bogus', $test->getName() );
|
|
|
|
|
|
$this->assertSame( 0, $test->getActorId() );
|
|
|
|
|
|
$test = User::newFromAnyId( 123456, 'Bogus', 654321, 'foo' );
|
|
|
|
|
|
$this->assertSame( 0, $test->getId() );
|
|
|
|
|
|
$this->assertSame( 0, $test->getActorId() );
|
|
|
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
|
// Exceptional cases
|
|
|
|
|
|
try {
|
|
|
|
|
|
User::newFromAnyId( null, null, null );
|
|
|
|
|
|
$this->fail( 'Expected exception not thrown' );
|
|
|
|
|
|
} catch ( InvalidArgumentException $ex ) {
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
User::newFromAnyId( 0, null, 0 );
|
|
|
|
|
|
$this->fail( 'Expected exception not thrown' );
|
|
|
|
|
|
} catch ( InvalidArgumentException $ex ) {
|
|
|
|
|
|
}
|
2019-05-03 01:57:40 +00:00
|
|
|
|
|
|
|
|
|
|
// Loading remote user by id from remote wiki should fail
|
|
|
|
|
|
try {
|
|
|
|
|
|
User::newFromAnyId( 123456, null, 654321, 'foo' );
|
|
|
|
|
|
$this->fail( 'Expected exception not thrown' );
|
|
|
|
|
|
} catch ( InvalidArgumentException $ex ) {
|
|
|
|
|
|
}
|
2017-09-12 17:12:29 +00:00
|
|
|
|
}
|
2018-03-22 16:52:59 +00:00
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromIdentity
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNewFromIdentity() {
|
|
|
|
|
|
// Registered user
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$user = $this->user;
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $user, User::newFromIdentity( $user ) );
|
|
|
|
|
|
|
|
|
|
|
|
// ID only
|
|
|
|
|
|
$identity = new UserIdentityValue( $user->getId(), '', 0 );
|
|
|
|
|
|
$result = User::newFromIdentity( $identity );
|
|
|
|
|
|
$this->assertInstanceOf( User::class, $result );
|
|
|
|
|
|
$this->assertSame( $user->getId(), $result->getId(), 'ID' );
|
|
|
|
|
|
$this->assertSame( $user->getName(), $result->getName(), 'Name' );
|
|
|
|
|
|
$this->assertSame( $user->getActorId(), $result->getActorId(), 'Actor' );
|
|
|
|
|
|
|
|
|
|
|
|
// Name only
|
|
|
|
|
|
$identity = new UserIdentityValue( 0, $user->getName(), 0 );
|
|
|
|
|
|
$result = User::newFromIdentity( $identity );
|
|
|
|
|
|
$this->assertInstanceOf( User::class, $result );
|
|
|
|
|
|
$this->assertSame( $user->getId(), $result->getId(), 'ID' );
|
|
|
|
|
|
$this->assertSame( $user->getName(), $result->getName(), 'Name' );
|
|
|
|
|
|
$this->assertSame( $user->getActorId(), $result->getActorId(), 'Actor' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-01-27 15:07:25 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromConfirmationCode
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNewFromConfirmationCode() {
|
|
|
|
|
|
$user = User::newFromConfirmationCode( 'NotARealConfirmationCode' );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$this->assertNull(
|
|
|
|
|
|
$user,
|
|
|
|
|
|
'Invalid confirmation codes result in null users when reading from replicas'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromConfirmationCode( 'OtherFakeCode', User::READ_LATEST );
|
|
|
|
|
|
$this->assertNull(
|
|
|
|
|
|
$user,
|
|
|
|
|
|
'Invalid confirmation codes result in null users when reading from master'
|
|
|
|
|
|
);
|
2020-01-27 15:07:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-09-20 03:02:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromName
|
|
|
|
|
|
* @covers User::getName
|
|
|
|
|
|
* @covers User::getUserPage
|
|
|
|
|
|
* @covers User::getTalkPage
|
|
|
|
|
|
* @covers User::getTitleKey
|
2020-01-04 09:35:20 +00:00
|
|
|
|
* @covers User::whoIs
|
2019-09-20 03:02:12 +00:00
|
|
|
|
* @dataProvider provideNewFromName
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNewFromName( $name, $titleKey ) {
|
|
|
|
|
|
$user = User::newFromName( $name );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $user->getName(), $name );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
$this->assertEquals( $user->getUserPage(), Title::makeTitle( NS_USER, $name ) );
|
|
|
|
|
|
$this->assertEquals( $user->getTalkPage(), Title::makeTitle( NS_USER_TALK, $name ) );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $user->getTitleKey(), $titleKey );
|
2020-01-04 09:35:20 +00:00
|
|
|
|
|
|
|
|
|
|
$status = $user->addToDatabase();
|
|
|
|
|
|
$this->assertTrue( $status->isOK(), 'User can be added to the database' );
|
|
|
|
|
|
$this->assertSame( $name, User::whoIs( $user->getId() ) );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function provideNewFromName() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
[ 'Example1', 'Example1' ],
|
|
|
|
|
|
[ 'Mediawiki easter egg', 'Mediawiki_easter_egg' ],
|
|
|
|
|
|
[ 'See T22281 for more', 'See_T22281_for_more' ],
|
|
|
|
|
|
[ 'DannyS712', 'DannyS712' ],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-01-04 09:35:20 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromName
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNewFromName_extra() {
|
|
|
|
|
|
$user = User::newFromName( '1.2.3.4' );
|
|
|
|
|
|
$this->assertFalse( $user, 'IP addresses are not valid user names' );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'DannyS712', true );
|
|
|
|
|
|
$otherUser = User::newFromName( 'DannyS712', 'valid' );
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
$user->equals( $otherUser ),
|
|
|
|
|
|
'true maps to valid for backwards compatibility'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-13 03:27:25 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromSession
|
|
|
|
|
|
* @covers User::getRequest
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testSessionAndRequest() {
|
|
|
|
|
|
$req1 = new WebRequest;
|
2020-12-15 09:47:15 +00:00
|
|
|
|
$this->setRequest( $req1 );
|
2020-02-13 03:27:25 +00:00
|
|
|
|
$user = User::newFromSession();
|
|
|
|
|
|
$request = $user->getRequest();
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
$req1,
|
|
|
|
|
|
$request,
|
|
|
|
|
|
'Creating a user without a request defaults to $wgRequest'
|
|
|
|
|
|
);
|
|
|
|
|
|
$req2 = new WebRequest;
|
|
|
|
|
|
$this->assertNotSame(
|
|
|
|
|
|
$req1,
|
|
|
|
|
|
$req2,
|
|
|
|
|
|
'Sanity check: passing a request that does not match $wgRequest'
|
|
|
|
|
|
);
|
|
|
|
|
|
$user = User::newFromSession( $req2 );
|
|
|
|
|
|
$request = $user->getRequest();
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
$req2,
|
|
|
|
|
|
$request,
|
|
|
|
|
|
'Creating a user by passing a WebRequest successfully sets the request, ' .
|
|
|
|
|
|
'instead of using $wgRequest'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromRow
|
|
|
|
|
|
* @covers User::loadFromRow
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNewFromRow() {
|
|
|
|
|
|
// TODO: Create real tests here for loadFromRow
|
2020-02-28 15:13:53 +00:00
|
|
|
|
$row = (object)[];
|
2020-02-13 03:27:25 +00:00
|
|
|
|
$user = User::newFromRow( $row );
|
|
|
|
|
|
$this->assertInstanceOf( User::class, $user, 'newFromRow returns a user object' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromRow
|
|
|
|
|
|
* @covers User::loadFromRow
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNewFromRow_bad() {
|
|
|
|
|
|
$this->expectException( InvalidArgumentException::class );
|
|
|
|
|
|
$this->expectExceptionMessage( '$row must be an object' );
|
|
|
|
|
|
User::newFromRow( [] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-03-22 16:52:59 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getBlockedStatus
|
2019-09-20 03:02:12 +00:00
|
|
|
|
* @covers User::getBlockId
|
2018-03-22 16:52:59 +00:00
|
|
|
|
* @covers User::getBlock
|
|
|
|
|
|
* @covers User::blockedBy
|
|
|
|
|
|
* @covers User::blockedFor
|
|
|
|
|
|
* @covers User::isHidden
|
|
|
|
|
|
* @covers User::isBlockedFrom
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testBlockInstanceCache() {
|
|
|
|
|
|
// First, check the user isn't blocked
|
|
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$ut = Title::makeTitle( NS_USER_TALK, $user->getName() );
|
|
|
|
|
|
$this->assertNull( $user->getBlock( false ), 'sanity check' );
|
|
|
|
|
|
$this->assertSame( '', $user->blockedBy(), 'sanity check' );
|
|
|
|
|
|
$this->assertSame( '', $user->blockedFor(), 'sanity check' );
|
2019-09-30 14:14:56 +00:00
|
|
|
|
$this->assertFalse( $user->isHidden(), 'sanity check' );
|
2018-03-22 16:52:59 +00:00
|
|
|
|
$this->assertFalse( $user->isBlockedFrom( $ut ), 'sanity check' );
|
|
|
|
|
|
|
|
|
|
|
|
// Block the user
|
|
|
|
|
|
$blocker = $this->getTestSysop()->getUser();
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [
|
2018-03-22 16:52:59 +00:00
|
|
|
|
'hideName' => true,
|
|
|
|
|
|
'allowUsertalk' => false,
|
|
|
|
|
|
'reason' => 'Because',
|
|
|
|
|
|
] );
|
|
|
|
|
|
$block->setTarget( $user );
|
|
|
|
|
|
$block->setBlocker( $blocker );
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore = MediaWikiServices::getInstance()->getDatabaseBlockStore();
|
|
|
|
|
|
$res = $blockStore->insertBlock( $block );
|
2018-03-22 16:52:59 +00:00
|
|
|
|
$this->assertTrue( (bool)$res['id'], 'sanity check: Failed to insert block' );
|
|
|
|
|
|
|
|
|
|
|
|
// Clear cache and confirm it loaded the block properly
|
|
|
|
|
|
$user->clearInstanceCache();
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$this->assertInstanceOf( DatabaseBlock::class, $user->getBlock( false ) );
|
2018-03-22 16:52:59 +00:00
|
|
|
|
$this->assertSame( $blocker->getName(), $user->blockedBy() );
|
|
|
|
|
|
$this->assertSame( 'Because', $user->blockedFor() );
|
2019-09-30 14:14:56 +00:00
|
|
|
|
$this->assertTrue( $user->isHidden() );
|
2018-03-22 16:52:59 +00:00
|
|
|
|
$this->assertTrue( $user->isBlockedFrom( $ut ) );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $res['id'], $user->getBlockId() );
|
2018-03-22 16:52:59 +00:00
|
|
|
|
|
|
|
|
|
|
// Unblock
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore->deleteBlock( $block );
|
2018-03-22 16:52:59 +00:00
|
|
|
|
|
|
|
|
|
|
// Clear cache and confirm it loaded the not-blocked properly
|
|
|
|
|
|
$user->clearInstanceCache();
|
|
|
|
|
|
$this->assertNull( $user->getBlock( false ) );
|
|
|
|
|
|
$this->assertSame( '', $user->blockedBy() );
|
|
|
|
|
|
$this->assertSame( '', $user->blockedFor() );
|
2019-09-30 14:14:56 +00:00
|
|
|
|
$this->assertFalse( $user->isHidden() );
|
2018-03-22 16:52:59 +00:00
|
|
|
|
$this->assertFalse( $user->isBlockedFrom( $ut ) );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
$this->assertFalse( $user->getBlockId() );
|
2018-03-22 16:52:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-19 18:56:10 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getBlockedStatus
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testCompositeBlocks() {
|
|
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$request = $user->getRequest();
|
|
|
|
|
|
$this->setSessionUser( $user, $request );
|
|
|
|
|
|
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore = MediaWikiServices::getInstance()->getDatabaseBlockStore();
|
2019-03-19 18:56:10 +00:00
|
|
|
|
$ipBlock = new Block( [
|
|
|
|
|
|
'address' => $user->getRequest()->getIP(),
|
2021-06-04 09:05:47 +00:00
|
|
|
|
'by' => $this->getTestSysop()->getUser(),
|
2019-03-19 18:56:10 +00:00
|
|
|
|
'createAccount' => true,
|
|
|
|
|
|
] );
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore->insertBlock( $ipBlock );
|
2019-03-19 18:56:10 +00:00
|
|
|
|
|
|
|
|
|
|
$userBlock = new Block( [
|
|
|
|
|
|
'address' => $user,
|
2021-06-04 09:05:47 +00:00
|
|
|
|
'by' => $this->getTestSysop()->getUser(),
|
2019-03-19 18:56:10 +00:00
|
|
|
|
'createAccount' => false,
|
|
|
|
|
|
] );
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore->insertBlock( $userBlock );
|
2019-03-19 18:56:10 +00:00
|
|
|
|
|
|
|
|
|
|
$block = $user->getBlock();
|
|
|
|
|
|
$this->assertInstanceOf( CompositeBlock::class, $block );
|
|
|
|
|
|
$this->assertTrue( $block->isCreateAccountBlocked() );
|
|
|
|
|
|
$this->assertTrue( $block->appliesToPasswordReset() );
|
|
|
|
|
|
$this->assertTrue( $block->appliesToNamespace( NS_MAIN ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-01 14:11:03 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isBlockedFrom
|
|
|
|
|
|
* @dataProvider provideIsBlockedFrom
|
|
|
|
|
|
* @param string|null $title Title to test.
|
|
|
|
|
|
* @param bool $expect Expected result from User::isBlockedFrom()
|
|
|
|
|
|
* @param array $options Additional test options:
|
|
|
|
|
|
* - 'blockAllowsUTEdit': (bool, default true) Value for $wgBlockAllowsUTEdit
|
2019-05-13 14:18:07 +00:00
|
|
|
|
* - 'allowUsertalk': (bool, default false) Passed to DatabaseBlock::__construct()
|
2018-11-01 14:11:03 +00:00
|
|
|
|
* - 'pageRestrictions': (array|null) If non-empty, page restriction titles for the block.
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsBlockedFrom( $title, $expect, array $options = [] ) {
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgBlockAllowsUTEdit' => $options['blockAllowsUTEdit'] ?? true,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$user = $this->user;
|
2018-11-01 14:11:03 +00:00
|
|
|
|
|
|
|
|
|
|
if ( $title === self::USER_TALK_PAGE ) {
|
|
|
|
|
|
$title = $user->getTalkPage();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$title = Title::newFromText( $title );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$restrictions = [];
|
|
|
|
|
|
foreach ( $options['pageRestrictions'] ?? [] as $pagestr ) {
|
|
|
|
|
|
$page = $this->getExistingTestPage(
|
|
|
|
|
|
$pagestr === self::USER_TALK_PAGE ? $user->getTalkPage() : $pagestr
|
|
|
|
|
|
);
|
|
|
|
|
|
$restrictions[] = new PageRestriction( 0, $page->getId() );
|
|
|
|
|
|
}
|
2018-10-30 18:19:22 +00:00
|
|
|
|
foreach ( $options['namespaceRestrictions'] ?? [] as $ns ) {
|
|
|
|
|
|
$restrictions[] = new NamespaceRestriction( 0, $ns );
|
|
|
|
|
|
}
|
2018-11-01 14:11:03 +00:00
|
|
|
|
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [
|
2018-11-01 14:11:03 +00:00
|
|
|
|
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
|
|
|
|
|
|
'allowUsertalk' => $options['allowUsertalk'] ?? false,
|
|
|
|
|
|
'sitewide' => !$restrictions,
|
|
|
|
|
|
] );
|
|
|
|
|
|
$block->setTarget( $user );
|
|
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
|
|
|
|
|
if ( $restrictions ) {
|
|
|
|
|
|
$block->setRestrictions( $restrictions );
|
|
|
|
|
|
}
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore = MediaWikiServices::getInstance()->getDatabaseBlockStore();
|
|
|
|
|
|
$blockStore->insertBlock( $block );
|
2018-11-01 14:11:03 +00:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
$this->assertSame( $expect, $user->isBlockedFrom( $title ) );
|
|
|
|
|
|
} finally {
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore->deleteBlock( $block );
|
2018-11-01 14:11:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function provideIsBlockedFrom() {
|
|
|
|
|
|
return [
|
2019-02-05 17:20:58 +00:00
|
|
|
|
'Sitewide block, basic operation' => [ 'Test page', true ],
|
|
|
|
|
|
'Sitewide block, not allowing user talk' => [
|
|
|
|
|
|
self::USER_TALK_PAGE, true, [
|
2018-12-03 13:20:47 +00:00
|
|
|
|
'allowUsertalk' => false,
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
2019-02-05 17:20:58 +00:00
|
|
|
|
'Sitewide block, allowing user talk' => [
|
|
|
|
|
|
self::USER_TALK_PAGE, false, [
|
2018-12-03 13:20:47 +00:00
|
|
|
|
'allowUsertalk' => true,
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
2019-02-05 17:20:58 +00:00
|
|
|
|
'Sitewide block, allowing user talk but $wgBlockAllowsUTEdit is false' => [
|
2018-12-03 13:20:47 +00:00
|
|
|
|
self::USER_TALK_PAGE, true, [
|
|
|
|
|
|
'allowUsertalk' => true,
|
|
|
|
|
|
'blockAllowsUTEdit' => false,
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
|
|
|
|
|
'Partial block, blocking the page' => [
|
|
|
|
|
|
'Test page', true, [
|
|
|
|
|
|
'pageRestrictions' => [ 'Test page' ],
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
|
|
|
|
|
'Partial block, not blocking the page' => [
|
|
|
|
|
|
'Test page 2', false, [
|
|
|
|
|
|
'pageRestrictions' => [ 'Test page' ],
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
2019-02-05 17:20:58 +00:00
|
|
|
|
'Partial block, not allowing user talk but user talk page is not blocked' => [
|
2018-12-03 13:20:47 +00:00
|
|
|
|
self::USER_TALK_PAGE, false, [
|
|
|
|
|
|
'allowUsertalk' => false,
|
|
|
|
|
|
'pageRestrictions' => [ 'Test page' ],
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
2019-02-05 17:20:58 +00:00
|
|
|
|
'Partial block, allowing user talk but user talk page is blocked' => [
|
2018-12-03 13:20:47 +00:00
|
|
|
|
self::USER_TALK_PAGE, true, [
|
|
|
|
|
|
'allowUsertalk' => true,
|
|
|
|
|
|
'pageRestrictions' => [ self::USER_TALK_PAGE ],
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
2019-02-05 17:20:58 +00:00
|
|
|
|
'Partial block, user talk page is not blocked but $wgBlockAllowsUTEdit is false' => [
|
2018-12-03 13:20:47 +00:00
|
|
|
|
self::USER_TALK_PAGE, false, [
|
|
|
|
|
|
'allowUsertalk' => false,
|
|
|
|
|
|
'pageRestrictions' => [ 'Test page' ],
|
|
|
|
|
|
'blockAllowsUTEdit' => false,
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
2019-02-05 17:20:58 +00:00
|
|
|
|
'Partial block, user talk page is blocked and $wgBlockAllowsUTEdit is false' => [
|
2018-12-03 13:20:47 +00:00
|
|
|
|
self::USER_TALK_PAGE, true, [
|
|
|
|
|
|
'allowUsertalk' => true,
|
|
|
|
|
|
'pageRestrictions' => [ self::USER_TALK_PAGE ],
|
|
|
|
|
|
'blockAllowsUTEdit' => false,
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
2019-02-05 17:20:58 +00:00
|
|
|
|
'Partial user talk namespace block, not allowing user talk' => [
|
|
|
|
|
|
self::USER_TALK_PAGE, true, [
|
|
|
|
|
|
'allowUsertalk' => false,
|
|
|
|
|
|
'namespaceRestrictions' => [ NS_USER_TALK ],
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
|
|
|
|
|
'Partial user talk namespace block, allowing user talk' => [
|
|
|
|
|
|
self::USER_TALK_PAGE, false, [
|
|
|
|
|
|
'allowUsertalk' => true,
|
|
|
|
|
|
'namespaceRestrictions' => [ NS_USER_TALK ],
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
|
|
|
|
|
'Partial user talk namespace block, where $wgBlockAllowsUTEdit is false' => [
|
|
|
|
|
|
self::USER_TALK_PAGE, true, [
|
|
|
|
|
|
'allowUsertalk' => true,
|
|
|
|
|
|
'namespaceRestrictions' => [ NS_USER_TALK ],
|
|
|
|
|
|
'blockAllowsUTEdit' => false,
|
|
|
|
|
|
]
|
|
|
|
|
|
],
|
2018-11-01 14:11:03 +00:00
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-09-20 03:02:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isBlockedFromEmailuser
|
|
|
|
|
|
* @covers User::isAllowedToCreateAccount
|
|
|
|
|
|
* @dataProvider provideIsBlockedFromAction
|
|
|
|
|
|
* @param bool $blockFromEmail Whether to block email access.
|
|
|
|
|
|
* @param bool $blockFromAccountCreation Whether to block account creation.
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsBlockedFromAction( $blockFromEmail, $blockFromAccountCreation ) {
|
|
|
|
|
|
$user = $this->getTestUser( 'accountcreator' )->getUser();
|
|
|
|
|
|
|
|
|
|
|
|
$block = new DatabaseBlock( [
|
|
|
|
|
|
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
|
|
|
|
|
|
'sitewide' => true,
|
|
|
|
|
|
'blockEmail' => $blockFromEmail,
|
|
|
|
|
|
'createAccount' => $blockFromAccountCreation
|
|
|
|
|
|
] );
|
|
|
|
|
|
$block->setTarget( $user );
|
|
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore = MediaWikiServices::getInstance()->getDatabaseBlockStore();
|
|
|
|
|
|
$blockStore->insertBlock( $block );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
|
|
|
|
|
|
try {
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $blockFromEmail, $user->isBlockedFromEmailuser() );
|
|
|
|
|
|
$this->assertSame( !$blockFromAccountCreation, $user->isAllowedToCreateAccount() );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
} finally {
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore->deleteBlock( $block );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function provideIsBlockedFromAction() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'Block email access and account creation' => [ true, true ],
|
|
|
|
|
|
'Block only email access' => [ true, false ],
|
|
|
|
|
|
'Block only account creation' => [ false, true ],
|
|
|
|
|
|
'Allow email access and account creation' => [ false, false ],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-04 03:23:26 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isBlockedFromUpload
|
|
|
|
|
|
* @dataProvider provideIsBlockedFromUpload
|
|
|
|
|
|
* @param bool $sitewide Whether to block sitewide.
|
|
|
|
|
|
* @param bool $expected Whether the user is expected to be blocked from uploads.
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsBlockedFromUpload( $sitewide, $expected ) {
|
|
|
|
|
|
$block = new DatabaseBlock( [
|
|
|
|
|
|
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
|
|
|
|
|
|
'sitewide' => $sitewide,
|
|
|
|
|
|
] );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$block->setTarget( $this->user );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore = MediaWikiServices::getInstance()->getDatabaseBlockStore();
|
|
|
|
|
|
$blockStore->insertBlock( $block );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
|
|
|
|
|
|
try {
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $expected, $this->user->isBlockedFromUpload() );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
} finally {
|
2020-08-27 09:27:10 +00:00
|
|
|
|
$blockStore->deleteBlock( $block );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function provideIsBlockedFromUpload() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'sitewide blocks block uploads' => [ true, true ],
|
|
|
|
|
|
'partial blocks allow uploads' => [ false, false ],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-06 16:28:51 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getFirstEditTimestamp
|
|
|
|
|
|
* @covers User::getLatestEditTimestamp
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetFirstLatestEditTimestamp() {
|
2021-03-16 21:28:33 +00:00
|
|
|
|
$this->hideDeprecated( 'User::getFirstEditTimestamp' );
|
|
|
|
|
|
$this->hideDeprecated( 'User::getLatestEditTimestamp' );
|
2019-03-06 16:28:51 +00:00
|
|
|
|
$clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
|
2021-02-07 13:10:36 +00:00
|
|
|
|
MWTimestamp::setFakeTime( static function () use ( &$clock ) {
|
2019-03-06 16:28:51 +00:00
|
|
|
|
return $clock += 1000;
|
|
|
|
|
|
} );
|
2019-03-07 21:18:22 +00:00
|
|
|
|
try {
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$user = $this->user;
|
2019-03-07 21:18:22 +00:00
|
|
|
|
$firstRevision = self::makeEdit( $user, 'Help:UserTest_GetEditTimestamp', 'one', 'test' );
|
|
|
|
|
|
$secondRevision = self::makeEdit( $user, 'Help:UserTest_GetEditTimestamp', 'two', 'test' );
|
|
|
|
|
|
// Sanity check: revisions timestamp are different
|
|
|
|
|
|
$this->assertNotEquals( $firstRevision->getTimestamp(), $secondRevision->getTimestamp() );
|
|
|
|
|
|
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( $firstRevision->getTimestamp(), $user->getFirstEditTimestamp() );
|
|
|
|
|
|
$this->assertSame( $secondRevision->getTimestamp(), $user->getLatestEditTimestamp() );
|
2019-03-07 21:18:22 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
MWTimestamp::setFakeTime( false );
|
|
|
|
|
|
}
|
2019-03-06 16:28:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param User $user
|
|
|
|
|
|
* @param string $title
|
|
|
|
|
|
* @param string $content
|
|
|
|
|
|
* @param string $comment
|
|
|
|
|
|
* @return \MediaWiki\Revision\RevisionRecord|null
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static function makeEdit( User $user, $title, $content, $comment ) {
|
|
|
|
|
|
$page = WikiPage::factory( Title::newFromText( $title ) );
|
|
|
|
|
|
$content = ContentHandler::makeContent( $content, $page->getTitle() );
|
|
|
|
|
|
$updater = $page->newPageUpdater( $user );
|
|
|
|
|
|
$updater->setContent( 'main', $content );
|
|
|
|
|
|
return $updater->saveRevision( CommentStoreComment::newUnsavedComment( $comment ) );
|
|
|
|
|
|
}
|
2019-04-30 14:22:48 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::idFromName
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testExistingIdFromName() {
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
$this->user->getId(), User::idFromName( $this->user->getName() ),
|
|
|
|
|
|
'Id is correctly retreived from the cache.'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
$this->user->getId(), User::idFromName( $this->user->getName(), User::READ_LATEST ),
|
|
|
|
|
|
'Id is correctly retreived from the database.'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::idFromName
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNonExistingIdFromName() {
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$this->assertNull( User::idFromName( 'NotExisitngUser' ) );
|
2019-04-30 14:22:48 +00:00
|
|
|
|
}
|
2019-10-28 15:51:05 +00:00
|
|
|
|
|
2019-11-05 22:42:09 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isSystemUser
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsSystemUser() {
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->assertFalse( $this->user->isSystemUser(), 'Normal users are not system users' );
|
2019-11-05 22:42:09 +00:00
|
|
|
|
|
|
|
|
|
|
$user = User::newSystemUser( __METHOD__ );
|
|
|
|
|
|
$this->assertTrue( $user->isSystemUser(), 'Users created with newSystemUser() are system users' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-10-28 15:51:05 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newSystemUser
|
|
|
|
|
|
* @dataProvider provideNewSystemUser
|
|
|
|
|
|
* @param string $exists How/whether to create the user before calling User::newSystemUser
|
|
|
|
|
|
* - 'missing': Do not create the user
|
|
|
|
|
|
* - 'actor': Create an anonymous actor
|
|
|
|
|
|
* - 'user': Create a non-system user
|
|
|
|
|
|
* - 'system': Create a system user
|
|
|
|
|
|
* @param string $options Options to User::newSystemUser
|
|
|
|
|
|
* @param array $testOpts Test options
|
|
|
|
|
|
* @param string $expect 'user', 'exception', or 'null'
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNewSystemUser( $exists, $options, $testOpts, $expect ) {
|
2021-02-10 17:09:22 +00:00
|
|
|
|
$this->filterDeprecated( '/User::newSystemUser options/' );
|
2019-10-28 15:51:05 +00:00
|
|
|
|
$origUser = null;
|
|
|
|
|
|
$actorId = null;
|
|
|
|
|
|
|
|
|
|
|
|
switch ( $exists ) {
|
|
|
|
|
|
case 'missing':
|
|
|
|
|
|
$name = 'TestNewSystemUser ' . TestUserRegistry::getNextId();
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'actor':
|
|
|
|
|
|
$name = 'TestNewSystemUser ' . TestUserRegistry::getNextId();
|
|
|
|
|
|
$this->db->insert( 'actor', [ 'actor_name' => $name ] );
|
|
|
|
|
|
$actorId = (int)$this->db->insertId();
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'user':
|
|
|
|
|
|
$origUser = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$name = $origUser->getName();
|
|
|
|
|
|
$actorId = $origUser->getActorId();
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'system':
|
|
|
|
|
|
$name = 'TestNewSystemUser ' . TestUserRegistry::getNextId();
|
|
|
|
|
|
$user = User::newSystemUser( $name ); // Heh.
|
|
|
|
|
|
$actorId = $user->getActorId();
|
|
|
|
|
|
// Use this hook as a proxy for detecting when a "steal" happens.
|
|
|
|
|
|
$this->setTemporaryHook( 'InvalidateEmailComplete', function () {
|
|
|
|
|
|
$this->fail( 'InvalidateEmailComplete hook should not have been called' );
|
|
|
|
|
|
} );
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$globals = $testOpts['globals'] ?? [];
|
|
|
|
|
|
if ( !empty( $testOpts['reserved'] ) ) {
|
|
|
|
|
|
$globals['wgReservedUsernames'] = [ $name ];
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->setMwGlobals( $globals );
|
2021-05-28 13:38:53 +00:00
|
|
|
|
$userNameUtils = $this->getServiceContainer()->getUserNameUtils();
|
|
|
|
|
|
$this->assertSame( empty( $testOpts['reserved'] ), $userNameUtils->isUsable( $name ) );
|
2021-03-25 19:36:44 +00:00
|
|
|
|
$this->assertTrue( $userNameUtils->isValid( $name ) );
|
2019-10-28 15:51:05 +00:00
|
|
|
|
|
|
|
|
|
|
if ( $expect === 'exception' ) {
|
2020-03-27 19:20:17 +00:00
|
|
|
|
// T248195: Duplicate entry errors will log the exception, don't fail because of that.
|
|
|
|
|
|
$this->setNullLogger( 'DBQuery' );
|
2019-10-28 15:51:05 +00:00
|
|
|
|
$this->expectException( Exception::class );
|
|
|
|
|
|
}
|
|
|
|
|
|
$user = User::newSystemUser( $name, $options );
|
|
|
|
|
|
if ( $expect === 'null' ) {
|
|
|
|
|
|
$this->assertNull( $user );
|
|
|
|
|
|
if ( $origUser ) {
|
|
|
|
|
|
$this->assertNotSame(
|
|
|
|
|
|
User::INVALID_TOKEN, TestingAccessWrapper::newFromObject( $origUser )->mToken
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertNotSame( '', $origUser->getEmail() );
|
2019-11-05 22:42:09 +00:00
|
|
|
|
$this->assertFalse( $origUser->isSystemUser(), 'Normal users should not be system users' );
|
2019-10-28 15:51:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$this->assertInstanceOf( User::class, $user );
|
|
|
|
|
|
$this->assertSame( $name, $user->getName() );
|
|
|
|
|
|
if ( $actorId !== null ) {
|
|
|
|
|
|
$this->assertSame( $actorId, $user->getActorId() );
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->assertSame( User::INVALID_TOKEN, TestingAccessWrapper::newFromObject( $user )->mToken );
|
|
|
|
|
|
$this->assertSame( '', $user->getEmail() );
|
2019-11-05 22:42:09 +00:00
|
|
|
|
$this->assertTrue( $user->isSystemUser(), 'Newly created system users should be system users' );
|
2019-10-28 15:51:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function provideNewSystemUser() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'Basic creation' => [ 'missing', [], [], 'user' ],
|
|
|
|
|
|
'No creation' => [ 'missing', [ 'create' => false ], [], 'null' ],
|
2019-10-11 14:06:13 +00:00
|
|
|
|
'Validation fail' => [
|
|
|
|
|
|
'missing',
|
|
|
|
|
|
[ 'validate' => 'usable' ],
|
|
|
|
|
|
[ 'reserved' => true ],
|
|
|
|
|
|
'null'
|
|
|
|
|
|
],
|
2019-10-28 15:51:05 +00:00
|
|
|
|
'No stealing' => [ 'user', [], [], 'null' ],
|
|
|
|
|
|
'Stealing allowed' => [ 'user', [ 'steal' => true ], [], 'user' ],
|
|
|
|
|
|
'Stealing an already-system user' => [ 'system', [ 'steal' => true ], [], 'user' ],
|
|
|
|
|
|
'Anonymous actor (T236444)' => [ 'actor', [], [ 'reserved' => true ], 'user' ],
|
2021-04-07 17:32:17 +00:00
|
|
|
|
'System user (T236444), reserved' => [ 'system', [], [ 'reserved' => true ], 'user' ],
|
2019-10-28 15:51:05 +00:00
|
|
|
|
'Reserved but no anonymous actor' => [ 'missing', [], [ 'reserved' => true ], 'user' ],
|
|
|
|
|
|
'Anonymous actor but no creation' => [ 'actor', [ 'create' => false ], [], 'null' ],
|
|
|
|
|
|
'Anonymous actor but not reserved' => [ 'actor', [], [], 'exception' ],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
2019-10-11 14:06:13 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2020-02-04 03:23:26 +00:00
|
|
|
|
* @covers User::getDefaultOption
|
2019-10-11 14:06:13 +00:00
|
|
|
|
* @covers User::getDefaultOptions
|
|
|
|
|
|
*/
|
2020-01-17 06:21:28 +00:00
|
|
|
|
public function testGetDefaultOptions() {
|
2021-03-16 15:16:18 +00:00
|
|
|
|
$this->hideDeprecated( 'User::getDefaultOption' );
|
|
|
|
|
|
$this->hideDeprecated( 'User::getDefaultOptions' );
|
2020-01-17 06:21:28 +00:00
|
|
|
|
$this->resetServices();
|
2020-02-04 03:23:26 +00:00
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'UserGetDefaultOptions', static function ( &$defaults ) {
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$defaults['extraoption'] = 42;
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
2019-10-11 14:06:13 +00:00
|
|
|
|
$defaultOptions = User::getDefaultOptions();
|
|
|
|
|
|
$this->assertArrayHasKey( 'search-match-redirect', $defaultOptions );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$this->assertArrayHasKey( 'extraoption', $defaultOptions );
|
|
|
|
|
|
|
|
|
|
|
|
$extraOption = User::getDefaultOption( 'extraoption' );
|
|
|
|
|
|
$this->assertSame( 42, $extraOption );
|
2019-10-11 14:06:13 +00:00
|
|
|
|
}
|
2019-11-23 10:23:57 +00:00
|
|
|
|
|
2021-03-16 15:16:18 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getDefaultOption
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetDefaultOption_deprecated() {
|
|
|
|
|
|
$this->expectDeprecation();
|
|
|
|
|
|
User::getDefaultOption( 'extraoption' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getDefaultOptions
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetDefaultOptions_deprecated() {
|
|
|
|
|
|
$this->expectDeprecation();
|
|
|
|
|
|
User::getDefaultOptions();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-11-23 10:23:57 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getAutomaticGroups
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetAutomaticGroups() {
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed'
|
2020-01-27 13:15:00 +00:00
|
|
|
|
], $this->user->getAutomaticGroups( true ) );
|
|
|
|
|
|
|
2019-11-23 10:23:57 +00:00
|
|
|
|
$user = $this->getTestUser( [ 'bureaucrat', 'test' ] )->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed'
|
|
|
|
|
|
], $user->getAutomaticGroups( true ) );
|
|
|
|
|
|
$user->addGroup( 'something' );
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed'
|
|
|
|
|
|
], $user->getAutomaticGroups( true ) );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
|
2019-11-23 10:23:57 +00:00
|
|
|
|
$user = User::newFromName( 'UTUser1' );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( [ '*' ], $user->getAutomaticGroups( true ) );
|
2019-11-23 10:23:57 +00:00
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgAutopromote' => [
|
|
|
|
|
|
'dummy' => APCOND_EMAILCONFIRMED
|
|
|
|
|
|
]
|
|
|
|
|
|
] );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
|
|
|
|
|
|
$this->user->confirmEmail();
|
2019-11-23 10:23:57 +00:00
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'dummy'
|
2020-01-27 13:15:00 +00:00
|
|
|
|
], $this->user->getAutomaticGroups( true ) );
|
|
|
|
|
|
|
2019-11-23 10:23:57 +00:00
|
|
|
|
$user = $this->getTestUser( [ 'dummy' ] )->getUser();
|
|
|
|
|
|
$user->confirmEmail();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'dummy'
|
|
|
|
|
|
], $user->getAutomaticGroups( true ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getEffectiveGroups
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetEffectiveGroups() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed'
|
|
|
|
|
|
], $user->getEffectiveGroups( true ) );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
|
2019-11-23 10:23:57 +00:00
|
|
|
|
$user = $this->getTestUser( [ 'bureaucrat', 'test' ] )->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed',
|
|
|
|
|
|
'bureaucrat',
|
|
|
|
|
|
'test'
|
|
|
|
|
|
], $user->getEffectiveGroups( true ) );
|
2020-01-27 13:15:00 +00:00
|
|
|
|
|
2019-11-23 10:23:57 +00:00
|
|
|
|
$user = $this->getTestUser( [ 'autoconfirmed', 'test' ] )->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed',
|
|
|
|
|
|
'test'
|
|
|
|
|
|
], $user->getEffectiveGroups( true ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getGroups
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetGroups() {
|
2019-10-24 03:14:31 +00:00
|
|
|
|
$user = $this->getTestUser( [ 'a', 'b' ] )->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [ 'a', 'b' ], $user->getGroups() );
|
2019-11-23 10:23:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getFormerGroups
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetFormerGroups() {
|
2019-10-24 03:14:31 +00:00
|
|
|
|
$user = $this->getTestUser( [ 'a', 'b', 'c' ] )->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [], $user->getFormerGroups() );
|
2019-11-23 10:23:57 +00:00
|
|
|
|
$user->addGroup( 'test' );
|
|
|
|
|
|
$user->removeGroup( 'test' );
|
2019-10-24 03:14:31 +00:00
|
|
|
|
$this->assertArrayEquals( [ 'test' ], $user->getFormerGroups() );
|
2019-11-23 10:23:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::addGroup
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testAddGroup() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( [], $user->getGroups() );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
|
2019-10-24 03:14:31 +00:00
|
|
|
|
$this->assertTrue( $user->addGroup( 'test' ) );
|
|
|
|
|
|
$this->assertArrayEquals( [ 'test' ], $user->getGroups() );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
|
2020-02-13 03:27:25 +00:00
|
|
|
|
$this->assertTrue( $user->addGroup( 'test2' ) );
|
|
|
|
|
|
$this->assertArrayEquals( [ 'test', 'test2' ], $user->getGroups() );
|
|
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'UserAddGroup', static function ( $user, &$group, &$expiry ) {
|
2020-02-04 03:23:26 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
} );
|
2020-02-13 03:27:25 +00:00
|
|
|
|
$this->assertFalse( $user->addGroup( 'test3' ) );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$this->assertArrayEquals(
|
2020-02-13 03:27:25 +00:00
|
|
|
|
[ 'test', 'test2' ],
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$user->getGroups(),
|
|
|
|
|
|
'Hooks can stop addition of a group'
|
|
|
|
|
|
);
|
2019-11-23 10:23:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::removeGroup
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testRemoveGroup() {
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$user = $this->getTestUser( [ 'test', 'test3' ] )->getUser();
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $user->removeGroup( 'test' ) );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( [ 'test3' ], $user->getGroups() );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
$user->removeGroup( 'test2' ),
|
|
|
|
|
|
'A group membership that does not exist cannot be removed'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'UserRemoveGroup', static function ( $user, &$group ) {
|
2020-02-04 03:23:26 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $user->removeGroup( 'test3' ) );
|
2020-01-27 13:25:43 +00:00
|
|
|
|
$this->assertSame( [ 'test3' ], $user->getGroups(), 'Hooks can stop removal of a group' );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-25 13:48:49 +00:00
|
|
|
|
private const CHANGEABLE_GROUPS_TEST_CONFIG = [
|
|
|
|
|
|
'wgGroupPermissions' => [
|
|
|
|
|
|
'doEverything' => [
|
|
|
|
|
|
'userrights' => true,
|
|
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
'wgAddGroups' => [
|
|
|
|
|
|
'sysop' => [ 'rollback' ],
|
|
|
|
|
|
'bureaucrat' => [ 'sysop', 'bureaucrat' ],
|
|
|
|
|
|
],
|
|
|
|
|
|
'wgRemoveGroups' => [
|
|
|
|
|
|
'sysop' => [ 'rollback' ],
|
|
|
|
|
|
'bureaucrat' => [ 'sysop' ],
|
|
|
|
|
|
],
|
|
|
|
|
|
'wgGroupsAddToSelf' => [
|
|
|
|
|
|
'sysop' => [ 'flood' ],
|
|
|
|
|
|
],
|
|
|
|
|
|
'wgGroupsRemoveFromSelf' => [
|
|
|
|
|
|
'flood' => [ 'flood' ],
|
|
|
|
|
|
],
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2020-02-04 03:23:26 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::changeableGroups
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testChangeableGroups() {
|
2021-05-25 13:48:49 +00:00
|
|
|
|
$this->setMwGlobals( self::CHANGEABLE_GROUPS_TEST_CONFIG );
|
2021-05-25 14:01:35 +00:00
|
|
|
|
$this->hideDeprecated( 'User::changeableGroups' );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
|
|
|
|
|
|
$allGroups = User::getAllGroups();
|
|
|
|
|
|
|
|
|
|
|
|
$user = $this->getTestUser( [ 'doEverything' ] )->getUser();
|
|
|
|
|
|
$changeableGroups = $user->changeableGroups();
|
|
|
|
|
|
$this->assertGroupsEquals(
|
|
|
|
|
|
[
|
|
|
|
|
|
'add' => $allGroups,
|
|
|
|
|
|
'remove' => $allGroups,
|
|
|
|
|
|
'add-self' => [],
|
|
|
|
|
|
'remove-self' => [],
|
|
|
|
|
|
],
|
|
|
|
|
|
$changeableGroups
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$user = $this->getTestUser( [ 'bureaucrat', 'sysop' ] )->getUser();
|
|
|
|
|
|
$changeableGroups = $user->changeableGroups();
|
|
|
|
|
|
$this->assertGroupsEquals(
|
|
|
|
|
|
[
|
|
|
|
|
|
'add' => [ 'bureaucrat', 'sysop', 'rollback' ],
|
|
|
|
|
|
'remove' => [ 'sysop', 'rollback' ],
|
|
|
|
|
|
'add-self' => [ 'flood' ],
|
|
|
|
|
|
'remove-self' => [],
|
|
|
|
|
|
],
|
|
|
|
|
|
$changeableGroups
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$user = $this->getTestUser( [ 'flood' ] )->getUser();
|
|
|
|
|
|
$changeableGroups = $user->changeableGroups();
|
|
|
|
|
|
$this->assertGroupsEquals(
|
|
|
|
|
|
[
|
|
|
|
|
|
'add' => [],
|
|
|
|
|
|
'remove' => [],
|
|
|
|
|
|
'add-self' => [],
|
|
|
|
|
|
'remove-self' => [ 'flood' ],
|
|
|
|
|
|
],
|
|
|
|
|
|
$changeableGroups
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-25 13:48:49 +00:00
|
|
|
|
public function provideChangeableByGroup() {
|
|
|
|
|
|
yield 'sysop' => [ 'sysop', [
|
|
|
|
|
|
'add' => [ 'rollback' ],
|
|
|
|
|
|
'remove' => [ 'rollback' ],
|
|
|
|
|
|
'add-self' => [ 'flood' ],
|
|
|
|
|
|
'remove-self' => [],
|
|
|
|
|
|
] ];
|
|
|
|
|
|
yield 'flood' => [ 'flood', [
|
|
|
|
|
|
'add' => [],
|
|
|
|
|
|
'remove' => [],
|
|
|
|
|
|
'add-self' => [],
|
|
|
|
|
|
'remove-self' => [ 'flood' ],
|
|
|
|
|
|
] ];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @dataProvider provideChangeableByGroup
|
|
|
|
|
|
* @covers User::changeableByGroup
|
|
|
|
|
|
* @param string $group
|
|
|
|
|
|
* @param array $expected
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testChangeableByGroup( string $group, array $expected ) {
|
|
|
|
|
|
$this->setMwGlobals( self::CHANGEABLE_GROUPS_TEST_CONFIG );
|
2021-05-25 14:01:35 +00:00
|
|
|
|
$this->hideDeprecated( 'User::changeableByGroup' );
|
2021-05-25 13:48:49 +00:00
|
|
|
|
$this->assertGroupsEquals( $expected, User::changeableByGroup( $group ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-01-27 13:25:43 +00:00
|
|
|
|
private function assertGroupsEquals( array $expected, array $actual ) {
|
2020-02-04 03:23:26 +00:00
|
|
|
|
// assertArrayEquals can compare without requiring the same order,
|
|
|
|
|
|
// but the elements of an array are still required to be in the same order,
|
|
|
|
|
|
// so just compare each element
|
|
|
|
|
|
$this->assertArrayEquals( $expected['add'], $actual['add'] );
|
|
|
|
|
|
$this->assertArrayEquals( $expected['remove'], $actual['remove'] );
|
|
|
|
|
|
$this->assertArrayEquals( $expected['add-self'], $actual['add-self'] );
|
|
|
|
|
|
$this->assertArrayEquals( $expected['remove-self'], $actual['remove-self'] );
|
2019-11-23 10:23:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-11-28 06:27:30 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isWatched
|
2020-04-30 01:22:12 +00:00
|
|
|
|
* @covers User::isTempWatched
|
2019-11-28 06:27:30 +00:00
|
|
|
|
* @covers User::addWatch
|
|
|
|
|
|
* @covers User::removeWatch
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testWatchlist() {
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$user = $this->user;
|
2019-12-19 07:26:37 +00:00
|
|
|
|
$articleTitle = Title::makeTitle( NS_MAIN, 'FooBar' );
|
2019-11-28 06:27:30 +00:00
|
|
|
|
|
2021-04-28 22:04:56 +00:00
|
|
|
|
$this->hideDeprecated( 'User::isWatched' );
|
|
|
|
|
|
$this->hideDeprecated( 'User::isTempWatched' );
|
|
|
|
|
|
$this->hideDeprecated( 'User::addWatch' );
|
|
|
|
|
|
$this->hideDeprecated( 'User::removeWatch' );
|
|
|
|
|
|
|
2019-11-28 06:27:30 +00:00
|
|
|
|
$this->assertFalse( $user->isWatched( $articleTitle ), 'The article has not been watched yet' );
|
|
|
|
|
|
|
|
|
|
|
|
$user->addWatch( $articleTitle );
|
|
|
|
|
|
$this->assertTrue( $user->isWatched( $articleTitle ), 'The article has been watched' );
|
2020-04-30 01:22:12 +00:00
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
$user->isTempWatched( $articleTitle ),
|
|
|
|
|
|
"The article hasn't been temporarily watched"
|
|
|
|
|
|
);
|
2019-11-28 06:27:30 +00:00
|
|
|
|
|
|
|
|
|
|
$user->removeWatch( $articleTitle );
|
|
|
|
|
|
$this->assertFalse( $user->isWatched( $articleTitle ), 'The article has been unwatched' );
|
2020-04-30 01:22:12 +00:00
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
$user->isTempWatched( $articleTitle ),
|
|
|
|
|
|
"The article hasn't been temporarily watched"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$user->addWatch( $articleTitle, true, '2 weeks' );
|
|
|
|
|
|
$this->assertTrue(
|
2021-03-26 22:24:43 +00:00
|
|
|
|
$user->isTempWatched( $articleTitle ), 'The article has been tempoarily watched'
|
2020-04-30 01:22:12 +00:00
|
|
|
|
);
|
2019-11-28 06:27:30 +00:00
|
|
|
|
|
2020-07-20 16:33:42 +00:00
|
|
|
|
$specialTitle = Title::makeTitle( NS_SPECIAL, 'Version' );
|
|
|
|
|
|
$this->assertFalse( $user->isWatched( $specialTitle ), 'Special pages cannot be watched' );
|
|
|
|
|
|
// Assume no exceptions
|
|
|
|
|
|
$user->addWatch( $specialTitle );
|
|
|
|
|
|
$user->removeWatch( $specialTitle );
|
2019-12-19 07:26:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-01-04 09:35:20 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getName
|
|
|
|
|
|
* @covers User::setName
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testUserName() {
|
|
|
|
|
|
$user = User::newFromName( 'DannyS712' );
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
'DannyS712',
|
|
|
|
|
|
$user->getName(),
|
|
|
|
|
|
'Santiy check: Users created using ::newFromName should return the name used'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$user->setName( 'FooBarBaz' );
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
'FooBarBaz',
|
|
|
|
|
|
$user->getName(),
|
|
|
|
|
|
'Changing a username via ::setName should be reflected in ::getName'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getEmail
|
|
|
|
|
|
* @covers User::setEmail
|
|
|
|
|
|
* @covers User::invalidateEmail
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testUserEmail() {
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$user = $this->user;
|
2020-01-04 09:35:20 +00:00
|
|
|
|
|
|
|
|
|
|
$user->setEmail( 'TestEmail@mediawiki.org' );
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
'TestEmail@mediawiki.org',
|
|
|
|
|
|
$user->getEmail(),
|
|
|
|
|
|
'Setting an email via ::setEmail should be reflected in ::getEmail'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$this->setTemporaryHook( 'UserSetEmail', function ( $user, &$email ) {
|
|
|
|
|
|
$this->fail(
|
|
|
|
|
|
'UserSetEmail hook should not be called when the new email ' .
|
|
|
|
|
|
'is the same as the old email.'
|
|
|
|
|
|
);
|
|
|
|
|
|
} );
|
|
|
|
|
|
$user->setEmail( 'TestEmail@mediawiki.org' );
|
|
|
|
|
|
|
2020-04-15 19:30:38 +00:00
|
|
|
|
$this->removeTemporaryHook( 'UserSetEmail' );
|
2020-01-04 09:35:20 +00:00
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'UserSetEmail', static function ( $user, &$email ) {
|
2020-01-04 09:35:20 +00:00
|
|
|
|
$email = 'SettingIntercepted@mediawiki.org';
|
|
|
|
|
|
} );
|
|
|
|
|
|
$user->setEmail( 'NewEmail@mediawiki.org' );
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
'SettingIntercepted@mediawiki.org',
|
|
|
|
|
|
$user->getEmail(),
|
|
|
|
|
|
'Hooks can override setting email addresses'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2021-02-07 13:10:36 +00:00
|
|
|
|
$this->setTemporaryHook( 'UserGetEmail', static function ( $user, &$email ) {
|
2020-01-04 09:35:20 +00:00
|
|
|
|
$email = 'GettingIntercepted@mediawiki.org';
|
|
|
|
|
|
} );
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
'GettingIntercepted@mediawiki.org',
|
|
|
|
|
|
$user->getEmail(),
|
|
|
|
|
|
'Hooks can override getting email address'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2020-04-15 19:30:38 +00:00
|
|
|
|
$this->removeTemporaryHook( 'UserGetEmail' );
|
|
|
|
|
|
$this->removeTemporaryHook( 'UserSetEmail' );
|
|
|
|
|
|
|
2020-01-04 09:35:20 +00:00
|
|
|
|
$user->invalidateEmail();
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
'',
|
|
|
|
|
|
$user->getEmail(),
|
|
|
|
|
|
'After invalidation, a user email should be an empty string'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-13 03:27:25 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::setEmailWithConfirmation
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testSetEmailWithConfirmation_basic() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$startingEmail = 'startingemail@mediawiki.org';
|
|
|
|
|
|
$user->setEmail( $startingEmail );
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgEnableEmail' => false,
|
|
|
|
|
|
'wgEmailAuthentication' => false
|
|
|
|
|
|
] );
|
|
|
|
|
|
$status = $user->setEmailWithConfirmation( 'test1@mediawiki.org' );
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
$status->getErrors()[0]['message'],
|
|
|
|
|
|
'emaildisabled',
|
|
|
|
|
|
'Cannot set email when email is disabled'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
$user->getEmail(),
|
|
|
|
|
|
$startingEmail,
|
|
|
|
|
|
'Email has not changed'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgEnableEmail' => true,
|
|
|
|
|
|
] );
|
|
|
|
|
|
$status = $user->setEmailWithConfirmation( $startingEmail );
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
$status->getValue(),
|
|
|
|
|
|
'Returns true if the email specified is the current email'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
|
$user->getEmail(),
|
|
|
|
|
|
$startingEmail,
|
|
|
|
|
|
'Email has not changed'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-01-27 15:07:25 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isItemLoaded
|
|
|
|
|
|
* @covers User::setItemLoaded
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testItemLoaded() {
|
|
|
|
|
|
$user = User::newFromName( 'DannyS712' );
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
$user->isItemLoaded( 'name', 'only' ),
|
|
|
|
|
|
'Users created by name have user names loaded'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
$user->isItemLoaded( 'all', 'all' ),
|
|
|
|
|
|
'Not everything is loaded yet'
|
|
|
|
|
|
);
|
|
|
|
|
|
$user->load();
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
$user->isItemLoaded( 'FooBar', 'all' ),
|
|
|
|
|
|
'All items now loaded'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::requiresHTTPS
|
|
|
|
|
|
* @dataProvider provideRequiresHTTPS
|
|
|
|
|
|
*/
|
2021-03-28 16:19:05 +00:00
|
|
|
|
public function testRequiresHTTPS( $preference, bool $expected ) {
|
2020-01-27 15:07:25 +00:00
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgSecureLogin' => true,
|
Introduce $wgForceHTTPS
Add $wgForceHTTPS. When set to true:
* It makes the HTTP to HTTPS redirect unconditional and suppresses the
forceHTTPS cookie.
* It makes session cookies be secure.
* In the Action API, it triggers the existing deprecation warning and
avoids more expensive user/session checks.
* In login and signup, it suppresses the old hidden form fields for
protocol switching.
* It hides the prefershttps user preference.
Other changes:
* Factor out the HTTPS redirect in MediaWiki::main() into
maybeDoHttpsRedirect() and shouldDoHttpRedirect(). Improve
documentation.
* User::requiresHTTPS() reflects $wgForceHTTPS whereas the Session
concept of "force HTTPS" does not. The documentation of
User::requiresHTTPS() says that it includes configuration, and
retaining this definition was beneficial for some callers. Whereas
Session::shouldForceHTTPS() was used fairly narrowly as the value
of the forceHTTPS cookie, and injecting configuration into it is not
so easy or beneficial, so I left it as it was, except for clarifying
the documentation.
* Deprecate the following hooks: BeforeHttpsRedirect, UserRequiresHTTPS,
CanIPUseHTTPS. No known extension uses them, and they're not compatible
with the long-term goal of ending support for mixed-protocol wikis.
BeforeHttpsRedirect was documented as unstable from its inception.
CanIPUseHTTPS was a WMF config hack now superseded by GFOC's SNI
sniffing.
* For tests which failed with $wgForceHTTPS=true, I mostly split the
tests, testing each configuration value separately.
* Add ArrayUtils::cartesianProduct() as a helper for generating
combinations of boolean options in the session tests.
Bug: T256095
Change-Id: Iefb5ba55af35350dfc7c050f9fb8f4e8a79751cb
2020-06-24 00:56:46 +00:00
|
|
|
|
'wgForceHTTPS' => false,
|
2020-01-27 15:07:25 +00:00
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'UserWhoMayRequireHTTPS' );
|
|
|
|
|
|
$user->setOption( 'prefershttps', $preference );
|
|
|
|
|
|
$user->saveSettings();
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( $user->getName() );
|
2021-03-28 16:19:05 +00:00
|
|
|
|
$this->assertSame( $expected, $user->requiresHTTPS() );
|
2020-01-27 15:07:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function provideRequiresHTTPS() {
|
|
|
|
|
|
return [
|
2021-03-28 16:19:05 +00:00
|
|
|
|
'Wants, requires' => [ true, true ],
|
|
|
|
|
|
'Does not want, not required' => [ false, false ],
|
2020-01-27 15:07:25 +00:00
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::requiresHTTPS
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testRequiresHTTPS_disabled() {
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgSecureLogin' => false,
|
Introduce $wgForceHTTPS
Add $wgForceHTTPS. When set to true:
* It makes the HTTP to HTTPS redirect unconditional and suppresses the
forceHTTPS cookie.
* It makes session cookies be secure.
* In the Action API, it triggers the existing deprecation warning and
avoids more expensive user/session checks.
* In login and signup, it suppresses the old hidden form fields for
protocol switching.
* It hides the prefershttps user preference.
Other changes:
* Factor out the HTTPS redirect in MediaWiki::main() into
maybeDoHttpsRedirect() and shouldDoHttpRedirect(). Improve
documentation.
* User::requiresHTTPS() reflects $wgForceHTTPS whereas the Session
concept of "force HTTPS" does not. The documentation of
User::requiresHTTPS() says that it includes configuration, and
retaining this definition was beneficial for some callers. Whereas
Session::shouldForceHTTPS() was used fairly narrowly as the value
of the forceHTTPS cookie, and injecting configuration into it is not
so easy or beneficial, so I left it as it was, except for clarifying
the documentation.
* Deprecate the following hooks: BeforeHttpsRedirect, UserRequiresHTTPS,
CanIPUseHTTPS. No known extension uses them, and they're not compatible
with the long-term goal of ending support for mixed-protocol wikis.
BeforeHttpsRedirect was documented as unstable from its inception.
CanIPUseHTTPS was a WMF config hack now superseded by GFOC's SNI
sniffing.
* For tests which failed with $wgForceHTTPS=true, I mostly split the
tests, testing each configuration value separately.
* Add ArrayUtils::cartesianProduct() as a helper for generating
combinations of boolean options in the session tests.
Bug: T256095
Change-Id: Iefb5ba55af35350dfc7c050f9fb8f4e8a79751cb
2020-06-24 00:56:46 +00:00
|
|
|
|
'wgForceHTTPS' => false,
|
2020-01-27 15:07:25 +00:00
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'UserWhoMayRequireHTTP' );
|
|
|
|
|
|
$user->setOption( 'prefershttps', true );
|
|
|
|
|
|
$user->saveSettings();
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( $user->getName() );
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
$user->requiresHTTPS(),
|
|
|
|
|
|
'User preference ignored if wgSecureLogin is false'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Introduce $wgForceHTTPS
Add $wgForceHTTPS. When set to true:
* It makes the HTTP to HTTPS redirect unconditional and suppresses the
forceHTTPS cookie.
* It makes session cookies be secure.
* In the Action API, it triggers the existing deprecation warning and
avoids more expensive user/session checks.
* In login and signup, it suppresses the old hidden form fields for
protocol switching.
* It hides the prefershttps user preference.
Other changes:
* Factor out the HTTPS redirect in MediaWiki::main() into
maybeDoHttpsRedirect() and shouldDoHttpRedirect(). Improve
documentation.
* User::requiresHTTPS() reflects $wgForceHTTPS whereas the Session
concept of "force HTTPS" does not. The documentation of
User::requiresHTTPS() says that it includes configuration, and
retaining this definition was beneficial for some callers. Whereas
Session::shouldForceHTTPS() was used fairly narrowly as the value
of the forceHTTPS cookie, and injecting configuration into it is not
so easy or beneficial, so I left it as it was, except for clarifying
the documentation.
* Deprecate the following hooks: BeforeHttpsRedirect, UserRequiresHTTPS,
CanIPUseHTTPS. No known extension uses them, and they're not compatible
with the long-term goal of ending support for mixed-protocol wikis.
BeforeHttpsRedirect was documented as unstable from its inception.
CanIPUseHTTPS was a WMF config hack now superseded by GFOC's SNI
sniffing.
* For tests which failed with $wgForceHTTPS=true, I mostly split the
tests, testing each configuration value separately.
* Add ArrayUtils::cartesianProduct() as a helper for generating
combinations of boolean options in the session tests.
Bug: T256095
Change-Id: Iefb5ba55af35350dfc7c050f9fb8f4e8a79751cb
2020-06-24 00:56:46 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::requiresHTTPS
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testRequiresHTTPS_forced() {
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgSecureLogin' => true,
|
|
|
|
|
|
'wgForceHTTPS' => true,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'UserWhoMayRequireHTTP' );
|
|
|
|
|
|
$user->setOption( 'prefershttps', false );
|
|
|
|
|
|
$user->saveSettings();
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( $user->getName() );
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
$user->requiresHTTPS(),
|
|
|
|
|
|
'User preference ignored if wgForceHTTPS is true'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-04 03:23:26 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isCreatableName
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsCreatableName() {
|
2021-05-28 13:38:53 +00:00
|
|
|
|
$this->hideDeprecated( 'User::isCreatableName' );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgInvalidUsernameCharacters' => '@',
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2020-02-27 09:40:40 +00:00
|
|
|
|
$longUserName = str_repeat( 'x', 260 );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
User::isCreatableName( $longUserName ),
|
|
|
|
|
|
'longUserName is too long'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
User::isCreatableName( 'Foo@Bar' ),
|
|
|
|
|
|
'User name contains invalid character'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
User::isCreatableName( 'FooBar' ),
|
|
|
|
|
|
'User names with no issues can be created'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isUsableName
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsUsableName() {
|
2021-05-28 13:38:53 +00:00
|
|
|
|
$this->hideDeprecated( 'User::isUsableName' );
|
2020-02-04 03:23:26 +00:00
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgReservedUsernames' => [
|
|
|
|
|
|
'MediaWiki default',
|
|
|
|
|
|
'msg:reserved-user'
|
|
|
|
|
|
],
|
|
|
|
|
|
'wgForceUIMsgAsContentMsg' => [
|
|
|
|
|
|
'reserved-user'
|
|
|
|
|
|
],
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
User::isUsableName( '' ),
|
|
|
|
|
|
'Only valid user names are creatable'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
User::isUsableName( 'MediaWiki default' ),
|
|
|
|
|
|
'Reserved names cannot be used'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
User::isUsableName( 'reserved-user' ),
|
|
|
|
|
|
'Names can also be reserved via msg: '
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
User::isUsableName( 'FooBar' ),
|
|
|
|
|
|
'User names with no issues can be used'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::addToDatabase
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testAddToDatabase_bad() {
|
|
|
|
|
|
$user = new User();
|
|
|
|
|
|
$this->expectException( RuntimeException::class );
|
|
|
|
|
|
$this->expectExceptionMessage(
|
|
|
|
|
|
'User name field is not set.'
|
|
|
|
|
|
);
|
|
|
|
|
|
$user->addToDatabase();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-06 20:05:05 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::pingLimiter
|
|
|
|
|
|
*/
|
2020-08-26 11:18:46 +00:00
|
|
|
|
public function testPingLimiterHook() {
|
2020-02-06 20:05:05 +00:00
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgRateLimits' => [
|
|
|
|
|
|
'edit' => [
|
|
|
|
|
|
'user' => [ 3, 60 ],
|
|
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
// Hook leaves $result false
|
|
|
|
|
|
$this->setTemporaryHook(
|
|
|
|
|
|
'PingLimiter',
|
2021-02-07 13:10:36 +00:00
|
|
|
|
static function ( &$user, $action, &$result, $incrBy ) {
|
2020-02-06 20:05:05 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertFalse(
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user->pingLimiter(),
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'Hooks that just return false leave $result false'
|
|
|
|
|
|
);
|
2020-04-15 19:30:38 +00:00
|
|
|
|
$this->removeTemporaryHook( 'PingLimiter' );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
|
|
|
|
|
|
// Hook sets $result to true
|
|
|
|
|
|
$this->setTemporaryHook(
|
|
|
|
|
|
'PingLimiter',
|
2021-02-07 13:10:36 +00:00
|
|
|
|
static function ( &$user, $action, &$result, $incrBy ) {
|
2020-02-06 20:05:05 +00:00
|
|
|
|
$result = true;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertTrue(
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user->pingLimiter(),
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'Hooks can set $result to true'
|
|
|
|
|
|
);
|
2020-04-15 19:30:38 +00:00
|
|
|
|
$this->removeTemporaryHook( 'PingLimiter' );
|
2020-02-06 20:05:05 +00:00
|
|
|
|
|
|
|
|
|
|
// Unknown action
|
|
|
|
|
|
$this->assertFalse(
|
2020-01-27 13:15:00 +00:00
|
|
|
|
$this->user->pingLimiter( 'FakeActionWithNoRateLimit' ),
|
2020-02-06 20:05:05 +00:00
|
|
|
|
'Actions with no rate limit set do not trip the rate limiter'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-26 11:18:46 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::pingLimiter
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testPingLimiterWithStaleCache() {
|
|
|
|
|
|
global $wgMainCacheType;
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgRateLimits' => [
|
|
|
|
|
|
'edit' => [
|
|
|
|
|
|
'user' => [ 1, 60 ],
|
|
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
$cacheTime = 1600000000.0;
|
|
|
|
|
|
$appTime = 1600000000;
|
|
|
|
|
|
$cache = new HashBagOStuff();
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: make the main object cache a service we can override, T243233
|
|
|
|
|
|
ObjectCache::$instances[$wgMainCacheType] = $cache;
|
|
|
|
|
|
|
|
|
|
|
|
$cache->setMockTime( $cacheTime ); // this is a reference!
|
2021-02-07 13:10:36 +00:00
|
|
|
|
MWTimestamp::setFakeTime( static function () use ( &$appTime ) {
|
2020-08-26 11:18:46 +00:00
|
|
|
|
return (int)$appTime;
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $this->user->pingLimiter(), 'limit not reached' );
|
|
|
|
|
|
$this->assertTrue( $this->user->pingLimiter(), 'limit reached' );
|
|
|
|
|
|
|
|
|
|
|
|
// Make it so that rate limits are expired according to MWTimestamp::time(),
|
|
|
|
|
|
// but not according to $cache->getCurrentTime(), emulating the conditions
|
|
|
|
|
|
// that trigger T246991.
|
|
|
|
|
|
$cacheTime += 10;
|
|
|
|
|
|
$appTime += 100;
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $this->user->pingLimiter(), 'limit expired' );
|
|
|
|
|
|
$this->assertTrue( $this->user->pingLimiter(), 'limit functional after expiry' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::pingLimiter
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testPingLimiterRate() {
|
|
|
|
|
|
global $wgMainCacheType;
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgRateLimits' => [
|
|
|
|
|
|
'edit' => [
|
|
|
|
|
|
'user' => [ 3, 60 ],
|
|
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
$fakeTime = 1600000000;
|
|
|
|
|
|
$cache = new HashBagOStuff();
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: make the main object cache a service we can override, T243233
|
|
|
|
|
|
ObjectCache::$instances[$wgMainCacheType] = $cache;
|
|
|
|
|
|
|
|
|
|
|
|
$cache->setMockTime( $fakeTime ); // this is a reference!
|
2021-02-07 13:10:36 +00:00
|
|
|
|
MWTimestamp::setFakeTime( static function () use ( &$fakeTime ) {
|
2020-08-26 11:18:46 +00:00
|
|
|
|
return (int)$fakeTime;
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
|
|
// The limit is 3 per 60 second. Do 5 edits at an emulated 50 second interval.
|
|
|
|
|
|
// They should all pass. This tests that the counter doesn't just keeps increasing
|
|
|
|
|
|
// but gets reset in an appropriate way.
|
|
|
|
|
|
$this->assertFalse( $this->user->pingLimiter(), 'first ping should pass' );
|
|
|
|
|
|
|
|
|
|
|
|
$fakeTime += 50;
|
|
|
|
|
|
$this->assertFalse( $this->user->pingLimiter(), 'second ping should pass' );
|
|
|
|
|
|
|
|
|
|
|
|
$fakeTime += 50;
|
|
|
|
|
|
$this->assertFalse( $this->user->pingLimiter(), 'third ping should pass' );
|
|
|
|
|
|
|
|
|
|
|
|
$fakeTime += 50;
|
|
|
|
|
|
$this->assertFalse( $this->user->pingLimiter(), 'fourth ping should pass' );
|
|
|
|
|
|
|
|
|
|
|
|
$fakeTime += 50;
|
|
|
|
|
|
$this->assertFalse( $this->user->pingLimiter(), 'fifth ping should pass' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-28 19:05:36 +00:00
|
|
|
|
private function newFakeUser( $name, $ip, $id ) {
|
|
|
|
|
|
$req = new FauxRequest();
|
|
|
|
|
|
$req->setIP( $ip );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( $name, false );
|
|
|
|
|
|
|
|
|
|
|
|
$access = TestingAccessWrapper::newFromObject( $user );
|
|
|
|
|
|
$access->mRequest = $req;
|
|
|
|
|
|
$access->mId = $id;
|
2021-01-07 20:57:19 +00:00
|
|
|
|
$access->mLoadedItems = true;
|
2020-08-28 19:05:36 +00:00
|
|
|
|
|
|
|
|
|
|
$this->overrideUserPermissions( $user, [
|
|
|
|
|
|
'noratelimit' => false,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
return $user;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function newFakeAnon( $ip ) {
|
|
|
|
|
|
return $this->newFakeUser( $ip, $ip, 0 );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::pingLimiter
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testPingLimiterGlobal() {
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgRateLimits' => [
|
|
|
|
|
|
'edit' => [
|
|
|
|
|
|
'anon' => [ 1, 60 ],
|
|
|
|
|
|
],
|
|
|
|
|
|
'purge' => [
|
|
|
|
|
|
'ip' => [ 1, 60 ],
|
|
|
|
|
|
'subnet' => [ 1, 60 ],
|
|
|
|
|
|
],
|
|
|
|
|
|
'rollback' => [
|
|
|
|
|
|
'user' => [ 1, 60 ],
|
|
|
|
|
|
],
|
|
|
|
|
|
'move' => [
|
|
|
|
|
|
'user-global' => [ 1, 60 ],
|
|
|
|
|
|
],
|
|
|
|
|
|
'delete' => [
|
|
|
|
|
|
'ip-all' => [ 1, 60 ],
|
|
|
|
|
|
'subnet-all' => [ 1, 60 ],
|
|
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
// Set up a fake cache for storing limits
|
|
|
|
|
|
$cache = new HashBagOStuff( [ 'keyspace' => 'xwiki' ] );
|
|
|
|
|
|
|
|
|
|
|
|
global $wgMainCacheType;
|
|
|
|
|
|
ObjectCache::$instances[$wgMainCacheType] = $cache;
|
|
|
|
|
|
|
|
|
|
|
|
$cacheAccess = TestingAccessWrapper::newFromObject( $cache );
|
|
|
|
|
|
$cacheAccess->keyspace = 'xwiki';
|
|
|
|
|
|
|
|
|
|
|
|
$this->installMockContralIdProvider();
|
|
|
|
|
|
|
|
|
|
|
|
// Set up some fake users
|
|
|
|
|
|
$anon1 = $this->newFakeAnon( '1.2.3.4' );
|
|
|
|
|
|
$anon2 = $this->newFakeAnon( '1.2.3.8' );
|
|
|
|
|
|
$anon3 = $this->newFakeAnon( '6.7.8.9' );
|
|
|
|
|
|
$anon4 = $this->newFakeAnon( '6.7.8.1' );
|
|
|
|
|
|
|
|
|
|
|
|
// The mock ContralIdProvider uses the local id MOD 10 as the global ID.
|
|
|
|
|
|
// So Frank has global ID 11, and Jane has global ID 56.
|
|
|
|
|
|
// Kara's global ID is 0, which means no global ID.
|
|
|
|
|
|
$frankX1 = $this->newFakeUser( 'Frank', '1.2.3.4', 111 );
|
|
|
|
|
|
$frankX2 = $this->newFakeUser( 'Frank', '1.2.3.8', 111 );
|
|
|
|
|
|
$frankY1 = $this->newFakeUser( 'Frank', '1.2.3.4', 211 );
|
|
|
|
|
|
$janeX1 = $this->newFakeUser( 'Jane', '1.2.3.4', 456 );
|
|
|
|
|
|
$janeX3 = $this->newFakeUser( 'Jane', '6.7.8.9', 456 );
|
|
|
|
|
|
$janeY1 = $this->newFakeUser( 'Jane', '1.2.3.4', 756 );
|
|
|
|
|
|
$karaX1 = $this->newFakeUser( 'Kara', '5.5.5.5', 100 );
|
|
|
|
|
|
$karaY1 = $this->newFakeUser( 'Kara', '5.5.5.5', 200 );
|
|
|
|
|
|
|
|
|
|
|
|
// Test limits on wiki X
|
|
|
|
|
|
$this->assertFalse( $anon1->pingLimiter( 'edit' ), 'First anon edit' );
|
|
|
|
|
|
$this->assertTrue( $anon2->pingLimiter( 'edit' ), 'Second anon edit' );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $anon1->pingLimiter( 'purge' ), 'Anon purge' );
|
|
|
|
|
|
$this->assertTrue( $anon1->pingLimiter( 'purge' ), 'Anon purge via same IP' );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $anon3->pingLimiter( 'purge' ), 'Anon purge via different subnet' );
|
|
|
|
|
|
$this->assertTrue( $anon2->pingLimiter( 'purge' ), 'Anon purge via same subnet' );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $frankX1->pingLimiter( 'rollback' ), 'First rollback' );
|
|
|
|
|
|
$this->assertTrue( $frankX2->pingLimiter( 'rollback' ), 'Second rollback via different IP' );
|
|
|
|
|
|
$this->assertFalse( $janeX1->pingLimiter( 'rollback' ), 'Rlbk by different user, same IP' );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $frankX1->pingLimiter( 'move' ), 'First move' );
|
|
|
|
|
|
$this->assertTrue( $frankX2->pingLimiter( 'move' ), 'Second move via different IP' );
|
|
|
|
|
|
$this->assertFalse( $janeX1->pingLimiter( 'move' ), 'Move by different user, same IP' );
|
|
|
|
|
|
$this->assertFalse( $karaX1->pingLimiter( 'move' ), 'Move by another user' );
|
|
|
|
|
|
$this->assertTrue( $karaX1->pingLimiter( 'move' ), 'Second move by another user' );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $frankX1->pingLimiter( 'delete' ), 'First delete' );
|
|
|
|
|
|
$this->assertTrue( $janeX1->pingLimiter( 'delete' ), 'Delete via same IP' );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $frankX2->pingLimiter( 'delete' ), 'Delete via same subnet' );
|
|
|
|
|
|
$this->assertFalse( $janeX3->pingLimiter( 'delete' ), 'Delete via different subnet' );
|
|
|
|
|
|
|
|
|
|
|
|
// Now test how limits carry over to wiki Y
|
|
|
|
|
|
$cacheAccess->keyspace = 'ywiki';
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $anon3->pingLimiter( 'edit' ), 'Anon edit on wiki Y' );
|
|
|
|
|
|
$this->assertTrue( $anon4->pingLimiter( 'purge' ), 'Anon purge on wiki Y, same subnet' );
|
|
|
|
|
|
$this->assertFalse( $frankY1->pingLimiter( 'rollback' ), 'Rollback on wiki Y, same name' );
|
|
|
|
|
|
$this->assertTrue( $frankY1->pingLimiter( 'move' ), 'Move on wiki Y, same name' );
|
|
|
|
|
|
$this->assertTrue( $janeY1->pingLimiter( 'move' ), 'Move on wiki Y, different user' );
|
|
|
|
|
|
$this->assertTrue( $frankY1->pingLimiter( 'delete' ), 'Delete on wiki Y, same IP' );
|
|
|
|
|
|
|
|
|
|
|
|
// For a user without a global ID, user-global acts as a local restriction
|
|
|
|
|
|
$this->assertFalse( $karaY1->pingLimiter( 'move' ), 'Move by another user' );
|
|
|
|
|
|
$this->assertTrue( $karaY1->pingLimiter( 'move' ), 'Second move by another user' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function installMockContralIdProvider() {
|
|
|
|
|
|
$mockCentralIdLookup = $this->createNoOpMock(
|
|
|
|
|
|
CentralIdLookup::class,
|
|
|
|
|
|
[ 'centralIdFromLocalUser', 'getProviderId' ]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$mockCentralIdLookup->method( 'centralIdFromLocalUser' )
|
2021-02-07 13:10:36 +00:00
|
|
|
|
->willReturnCallback( static function ( User $user ) {
|
2020-08-28 19:05:36 +00:00
|
|
|
|
return $user->getId() % 100;
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgCentralIdLookupProvider' => 'test',
|
|
|
|
|
|
'wgCentralIdLookupProviders' => [
|
|
|
|
|
|
'test' => [
|
2021-02-07 13:10:36 +00:00
|
|
|
|
'factory' => static function () use ( $mockCentralIdLookup ) {
|
2020-08-28 19:05:36 +00:00
|
|
|
|
return $mockCentralIdLookup;
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
] );
|
|
|
|
|
|
}
|
2020-09-24 19:50:32 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::loadFromDatabase
|
|
|
|
|
|
* @covers User::loadDefaults
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testBadUserID() {
|
|
|
|
|
|
$user = User::newFromId( 999999999 );
|
|
|
|
|
|
$this->assertSame( 'Unknown user', $user->getName() );
|
|
|
|
|
|
}
|
2021-01-07 20:57:19 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::probablyCan
|
|
|
|
|
|
* @covers User::definitelyCan
|
|
|
|
|
|
* @covers User::authorizeRead
|
|
|
|
|
|
* @covers User::authorizeWrite
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testAuthorityMethods() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$page = Title::makeTitle( NS_MAIN, 'Test' );
|
|
|
|
|
|
$this->assertFalse( $user->probablyCan( 'create', $page ) );
|
|
|
|
|
|
$this->assertFalse( $user->definitelyCan( 'create', $page ) );
|
|
|
|
|
|
$this->assertFalse( $user->authorizeRead( 'create', $page ) );
|
|
|
|
|
|
$this->assertFalse( $user->authorizeWrite( 'create', $page ) );
|
|
|
|
|
|
|
|
|
|
|
|
$this->overrideUserPermissions( $user, 'createpage' );
|
|
|
|
|
|
$this->assertTrue( $user->probablyCan( 'create', $page ) );
|
|
|
|
|
|
$this->assertTrue( $user->definitelyCan( 'create', $page ) );
|
|
|
|
|
|
$this->assertTrue( $user->authorizeRead( 'create', $page ) );
|
|
|
|
|
|
$this->assertTrue( $user->authorizeWrite( 'create', $page ) );
|
|
|
|
|
|
}
|
2021-02-09 01:09:55 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isAllowed
|
|
|
|
|
|
* @covers User::__sleep
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testSerializationRoudTripWithAuthority() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$isAllowed = $user->isAllowed( 'read' ); // Memoize the Authority
|
|
|
|
|
|
$unserializedUser = unserialize( serialize( $user ) );
|
2021-02-23 15:40:12 +00:00
|
|
|
|
$this->assertSame( $user->getId(), $unserializedUser->getId() );
|
2021-02-09 01:09:55 +00:00
|
|
|
|
$this->assertSame( $isAllowed, $unserializedUser->isAllowed( 'read' ) );
|
|
|
|
|
|
}
|
2011-07-19 21:41:25 +00:00
|
|
|
|
}
|