2011-07-11 18:36:29 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
|
2011-07-16 16:09:00 +00:00
|
|
|
|
define( 'NS_UNITTEST', 5600 );
|
|
|
|
|
|
define( 'NS_UNITTEST_TALK', 5601 );
|
|
|
|
|
|
|
2019-05-13 14:18:07 +00:00
|
|
|
|
use MediaWiki\Block\DatabaseBlock;
|
2019-03-19 18:56:10 +00:00
|
|
|
|
use MediaWiki\Block\CompositeBlock;
|
2018-11-01 14:11:03 +00:00
|
|
|
|
use MediaWiki\Block\Restriction\PageRestriction;
|
2018-10-30 18:19:22 +00:00
|
|
|
|
use MediaWiki\Block\Restriction\NamespaceRestriction;
|
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;
|
2018-01-27 01:48:19 +00:00
|
|
|
|
use MediaWiki\User\UserIdentityValue;
|
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
|
|
|
|
|
|
*/
|
2011-07-11 18:36:29 +00:00
|
|
|
|
class UserTest extends MediaWikiTestCase {
|
2018-11-01 14:11:03 +00:00
|
|
|
|
|
|
|
|
|
|
/** Constant for self::testIsBlockedFrom */
|
|
|
|
|
|
const USER_TALK_PAGE = '<user talk page>';
|
|
|
|
|
|
|
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,
|
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
|
|
|
|
|
2017-05-18 20:16:55 +00:00
|
|
|
|
$this->user = $this->getTestUser( [ 'unittesters' ] )->getUser();
|
2019-10-28 15:51:05 +00:00
|
|
|
|
|
|
|
|
|
|
TestingAccessWrapper::newFromClass( User::class )->reservedUsernames = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected function tearDown() : void {
|
|
|
|
|
|
parent::tearDown();
|
|
|
|
|
|
TestingAccessWrapper::newFromClass( User::class )->reservedUsernames = false;
|
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,
|
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,
|
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
|
|
|
|
|
|
|
|
|
|
# For the options test
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$wgGroupPermissions['*'] = [
|
2019-09-20 03:02:12 +00:00
|
|
|
|
'editmyoptions' => true
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
# For patrol tests
|
|
|
|
|
|
$wgGroupPermissions['patroller'] = [
|
|
|
|
|
|
'patrol' => true,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
# For account creation when blocked test
|
|
|
|
|
|
$wgGroupPermissions['accountcreator'] = [
|
|
|
|
|
|
'createaccount' => true,
|
|
|
|
|
|
'ipblock-exempt' => true
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
# For bot test
|
|
|
|
|
|
$wgGroupPermissions['bot'] = [
|
|
|
|
|
|
'bot' => 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 ) {
|
|
|
|
|
|
$this->setMwGlobals( 'wgUser', $user );
|
|
|
|
|
|
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() {
|
|
|
|
|
|
$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() {
|
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' );
|
|
|
|
|
|
|
|
|
|
|
|
// Add a hook manipluating the rights
|
2019-04-09 06:58:04 +00:00
|
|
|
|
$this->setTemporaryHook( 'UserGetRights', 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
|
|
|
|
|
|
|
|
|
|
$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 )
|
2016-07-07 21:24:50 +00:00
|
|
|
|
->setMethods( [ 'getAllowedUserRights', 'deregisterSession', 'getSessionId' ] )
|
|
|
|
|
|
->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 )
|
|
|
|
|
|
->setMethods( [ 'getSession' ] )
|
|
|
|
|
|
->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
|
|
|
|
*/
|
2011-12-12 06:03:01 +00:00
|
|
|
|
public function testGetGroupsWithPermission( $expected, $right ) {
|
|
|
|
|
|
$result = User::getGroupsWithPermission( $right );
|
2011-07-16 16:09:00 +00:00
|
|
|
|
sort( $result );
|
|
|
|
|
|
sort( $expected );
|
2011-10-11 10:02:50 +00:00
|
|
|
|
|
2011-12-12 06:03:01 +00:00
|
|
|
|
$this->assertEquals( $expected, $result, "Groups with permission $right" );
|
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
|
|
|
|
|
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() );
|
|
|
|
|
|
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$this->assertFalse( $user->useRCPatrol() );
|
|
|
|
|
|
$this->assertFalse( $user->useNPPatrol() );
|
|
|
|
|
|
$this->assertFalse( $user->useFilePatrol() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getGroups
|
|
|
|
|
|
* @covers User::isBot
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testBot() {
|
|
|
|
|
|
$user = $this->getTestUser( 'bot' )->getUser();
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $user->getGroups(), [ 'bot' ] );
|
|
|
|
|
|
$this->assertTrue( $user->isBot() );
|
|
|
|
|
|
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$this->assertFalse( $user->isBot() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-01 16:31:07 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @dataProvider provideIPs
|
|
|
|
|
|
* @covers User::isIP
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsIP( $value, $result, $message ) {
|
|
|
|
|
|
$this->assertEquals( $this->user->isIP( $value ), $result, $message );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 ) {
|
|
|
|
|
|
$this->assertEquals( $this->user->isValidUserName( $username ), $result, $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
|
2019-09-20 03:02:12 +00:00
|
|
|
|
* @covers User::setEditCountInternal
|
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
|
2012-11-08 17:05:46 +00:00
|
|
|
|
$page = WikiPage::factory( Title::newFromText( '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
|
|
|
|
}
|
|
|
|
|
|
|
2014-04-24 12:50:36 +00:00
|
|
|
|
$this->assertEquals(
|
|
|
|
|
|
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
|
|
|
|
|
2014-04-24 12:50:36 +00:00
|
|
|
|
$this->assertEquals(
|
|
|
|
|
|
4,
|
|
|
|
|
|
$user->getEditCount(),
|
|
|
|
|
|
'After increasing the edit count manually, the user edit count should be 4'
|
|
|
|
|
|
);
|
2019-09-20 03:02:12 +00:00
|
|
|
|
|
|
|
|
|
|
// Update the edit count
|
|
|
|
|
|
$user->setEditCountInternal( 42 );
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
|
42,
|
|
|
|
|
|
$user->getEditCount(),
|
|
|
|
|
|
'After setting the edit count manually, the user edit count should be 42'
|
|
|
|
|
|
);
|
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();
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
|
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
|
|
|
|
|
|
* @covers User::getOption
|
2012-10-29 20:07:49 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testOptions() {
|
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' );
|
2017-01-09 10:56:41 +00:00
|
|
|
|
$user->setOption( 'rclimit', 200 );
|
2018-03-23 01:14:41 +00:00
|
|
|
|
$user->setOption( 'wpwatchlistdays', '0' );
|
2012-10-29 20:07:49 +00:00
|
|
|
|
$user->saveSettings();
|
|
|
|
|
|
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = User::newFromName( $user->getName() );
|
2016-11-17 00:38:09 +00:00
|
|
|
|
$user->load( User::READ_LATEST );
|
|
|
|
|
|
$this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) );
|
2017-01-09 10:56:41 +00:00
|
|
|
|
$this->assertEquals( 200, $user->getOption( 'rclimit' ) );
|
2016-11-17 00:38:09 +00:00
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( $user->getName() );
|
|
|
|
|
|
MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
|
2014-09-20 16:56:04 +00:00
|
|
|
|
$this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) );
|
2017-01-09 10:56:41 +00:00
|
|
|
|
$this->assertEquals( 200, $user->getOption( 'rclimit' ) );
|
2018-03-23 01:14:41 +00:00
|
|
|
|
|
|
|
|
|
|
// Check that an option saved as a string '0' is returned as an integer.
|
|
|
|
|
|
$user = User::newFromName( $user->getName() );
|
|
|
|
|
|
$user->load( User::READ_LATEST );
|
|
|
|
|
|
$this->assertSame( 0, $user->getOption( 'wpwatchlistdays' ) );
|
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.
|
2013-10-21 21:09:13 +00:00
|
|
|
|
* @covers User::loadOptions
|
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' );
|
2017-01-09 10:56:41 +00:00
|
|
|
|
$this->assertEquals( $wgDefaultUserOptions['rclimit'], $this->user->getOption( 'rclimit' ) );
|
2014-09-20 16:56:04 +00:00
|
|
|
|
$this->assertEquals( '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,
|
2016-02-17 09:09:32 +00:00
|
|
|
|
],
|
|
|
|
|
|
'default' => [
|
2015-04-23 01:48:48 +00:00
|
|
|
|
'MinimalPasswordLength' => 6,
|
|
|
|
|
|
'PasswordCannotMatchUsername' => true,
|
|
|
|
|
|
'PasswordCannotMatchBlacklist' => 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',
|
|
|
|
|
|
'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist',
|
|
|
|
|
|
'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength',
|
2016-02-17 09:09:32 +00:00
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
] );
|
2015-04-23 01:48:48 +00:00
|
|
|
|
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = static::getTestUser()->getUser();
|
|
|
|
|
|
|
2014-03-12 01:47:29 +00:00
|
|
|
|
// Sanity
|
|
|
|
|
|
$this->assertTrue( $user->isValidPassword( 'Password1234' ) );
|
|
|
|
|
|
|
|
|
|
|
|
// Minimum length
|
|
|
|
|
|
$this->assertFalse( $user->isValidPassword( 'a' ) );
|
|
|
|
|
|
$this->assertFalse( $user->checkPasswordValidity( 'a' )->isGood() );
|
2014-12-26 16:29:15 +00:00
|
|
|
|
$this->assertTrue( $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 );
|
2014-12-26 16:29:15 +00:00
|
|
|
|
$this->assertFalse( $user->isValidPassword( $longPass ) );
|
|
|
|
|
|
$this->assertFalse( $user->checkPasswordValidity( $longPass )->isGood() );
|
|
|
|
|
|
$this->assertFalse( $user->checkPasswordValidity( $longPass )->isOK() );
|
|
|
|
|
|
|
2014-03-12 01:47:29 +00:00
|
|
|
|
// Matches username
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$this->assertFalse( $user->checkPasswordValidity( $user->getName() )->isGood() );
|
|
|
|
|
|
$this->assertTrue( $user->checkPasswordValidity( $user->getName() )->isOK() );
|
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
|
|
|
|
|
|
*/
|
2016-04-16 17:36:33 +00:00
|
|
|
|
public function testGetCanonicalName( $name, $expectedArray ) {
|
|
|
|
|
|
// fake interwiki map for the 'Interwiki prefix' testcase
|
|
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgHooks', [
|
|
|
|
|
|
'InterwikiLoadPrefix' => [
|
|
|
|
|
|
function ( $prefix, &$iwdata ) {
|
|
|
|
|
|
if ( $prefix === 'interwiki' ) {
|
|
|
|
|
|
$iwdata = [
|
|
|
|
|
|
'iw_url' => 'http://example.com/',
|
|
|
|
|
|
'iw_local' => 0,
|
|
|
|
|
|
'iw_trans' => 0,
|
|
|
|
|
|
];
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2014-08-25 18:24:10 +00:00
|
|
|
|
foreach ( $expectedArray as $validate => $expected ) {
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
|
$expected,
|
2016-04-16 17:36:33 +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
|
|
|
|
|
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
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetId() {
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = static::getTestUser()->getUser();
|
2015-04-02 01:15:50 +00:00
|
|
|
|
$this->assertTrue( $user->getId() > 0 );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'UserWithNoId' );
|
|
|
|
|
|
$this->assertEquals( $user->getId(), 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
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testLoggedIn() {
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
2019-04-28 11:07:18 +00:00
|
|
|
|
$this->assertTrue( $user->isRegistered() );
|
2015-04-02 01:15:50 +00:00
|
|
|
|
$this->assertTrue( $user->isLoggedIn() );
|
|
|
|
|
|
$this->assertFalse( $user->isAnon() );
|
|
|
|
|
|
|
|
|
|
|
|
// Non-existent users are perceived as anonymous
|
|
|
|
|
|
$user = User::newFromName( 'UTNonexistent' );
|
2019-04-28 11:07:18 +00:00
|
|
|
|
$this->assertFalse( $user->isRegistered() );
|
2015-04-02 01:15:50 +00:00
|
|
|
|
$this->assertFalse( $user->isLoggedIn() );
|
|
|
|
|
|
$this->assertTrue( $user->isAnon() );
|
|
|
|
|
|
|
|
|
|
|
|
$user = new User;
|
2019-04-28 11:07:18 +00:00
|
|
|
|
$this->assertFalse( $user->isRegistered() );
|
2015-04-02 01:15:50 +00:00
|
|
|
|
$this->assertFalse( $user->isLoggedIn() );
|
|
|
|
|
|
$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 );
|
|
|
|
|
|
$this->assertEquals( $realName, $user->getRealName() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-04-07 20:50:00 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::checkAndSetTouched
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testCheckAndSetTouched() {
|
2016-05-18 09:19:20 +00:00
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
|
|
|
|
$user = TestingAccessWrapper::newFromObject( $user );
|
2015-04-07 20:50:00 +00:00
|
|
|
|
$this->assertTrue( $user->isLoggedIn() );
|
|
|
|
|
|
|
|
|
|
|
|
$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
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @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
|
|
|
|
|
|
|
|
|
|
$users = User::findUsersByGroup( 'foo' );
|
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' );
|
|
|
|
|
|
$this->assertEquals( 1, iterator_count( $users ) );
|
|
|
|
|
|
$users->rewind();
|
|
|
|
|
|
$this->assertTrue( $user->equals( $users->current() ) );
|
|
|
|
|
|
|
|
|
|
|
|
// arguments have OR relationship
|
|
|
|
|
|
$user2 = $this->getMutableTestUser( [ 'bar' ] )->getUser();
|
|
|
|
|
|
$users = User::findUsersByGroup( [ 'foo', 'bar' ] );
|
|
|
|
|
|
$this->assertEquals( 2, iterator_count( $users ) );
|
|
|
|
|
|
$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' ] );
|
|
|
|
|
|
$this->assertEquals( 1, iterator_count( $users ) );
|
|
|
|
|
|
$users->rewind();
|
|
|
|
|
|
$this->assertTrue( $user->equals( $users->current() ) );
|
|
|
|
|
|
}
|
2013-02-07 21:56:54 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* When a user is autoblocked a cookie is set with which to track them
|
|
|
|
|
|
* in case they log out and change IP addresses.
|
|
|
|
|
|
* @link https://phabricator.wikimedia.org/T5233
|
2019-02-01 19:44:21 +00:00
|
|
|
|
* @covers User::trackBlockWithCookie
|
2013-02-07 21:56:54 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testAutoblockCookies() {
|
|
|
|
|
|
// Set up the bits of global configuration that we use.
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgCookieSetOnAutoblock' => true,
|
|
|
|
|
|
'wgCookiePrefix' => 'wmsitetitle',
|
2017-01-04 03:38:27 +00:00
|
|
|
|
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
|
2013-02-07 21:56:54 +00:00
|
|
|
|
] );
|
|
|
|
|
|
|
2017-09-18 18:57:17 +00:00
|
|
|
|
// Unregister the hooks for proper unit testing
|
|
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgHooks', [
|
|
|
|
|
|
'PerformRetroactiveAutoblock' => []
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2019-11-18 17:08:54 +00:00
|
|
|
|
$blockManager = MediaWikiServices::getInstance()->getBlockManager();
|
|
|
|
|
|
|
2013-02-07 21:56:54 +00:00
|
|
|
|
// 1. Log in a test user, and block them.
|
|
|
|
|
|
$user1tmp = $this->getTestUser()->getUser();
|
|
|
|
|
|
$request1 = new FauxRequest();
|
|
|
|
|
|
$request1->getSession()->setUser( $user1tmp );
|
2016-12-20 12:08:38 +00:00
|
|
|
|
$expiryFiveHours = wfTimestamp() + ( 5 * 60 * 60 );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [
|
2013-02-07 21:56:54 +00:00
|
|
|
|
'enableAutoblock' => true,
|
2016-12-20 12:08:38 +00:00
|
|
|
|
'expiry' => wfTimestamp( TS_MW, $expiryFiveHours ),
|
2013-02-07 21:56:54 +00:00
|
|
|
|
] );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$block->setTarget( $user1tmp );
|
2017-09-18 18:03:04 +00:00
|
|
|
|
$res = $block->insert();
|
|
|
|
|
|
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$user1 = User::newFromSession( $request1 );
|
|
|
|
|
|
$user1->load();
|
2019-11-18 17:08:54 +00:00
|
|
|
|
$blockManager->trackBlockWithCookie( $user1, $request1->response() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
|
|
|
|
|
|
// Confirm that the block has been applied as required.
|
|
|
|
|
|
$this->assertTrue( $user1->isLoggedIn() );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$this->assertInstanceOf( DatabaseBlock::class, $user1->getBlock() );
|
|
|
|
|
|
$this->assertEquals( DatabaseBlock::TYPE_USER, $block->getType() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$this->assertTrue( $block->isAutoblocking() );
|
|
|
|
|
|
$this->assertGreaterThanOrEqual( 1, $block->getId() );
|
|
|
|
|
|
|
|
|
|
|
|
// Test for the desired cookie name, value, and expiry.
|
|
|
|
|
|
$cookies = $request1->response()->getCookies();
|
|
|
|
|
|
$this->assertArrayHasKey( 'wmsitetitleBlockID', $cookies );
|
2016-12-20 12:08:38 +00:00
|
|
|
|
$this->assertEquals( $expiryFiveHours, $cookies['wmsitetitleBlockID']['expire'] );
|
2019-11-18 17:08:54 +00:00
|
|
|
|
$cookieId = $blockManager->getIdFromCookieValue(
|
2019-06-06 18:00:20 +00:00
|
|
|
|
$cookies['wmsitetitleBlockID']['value']
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertEquals( $block->getId(), $cookieId );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
|
|
|
|
|
|
// 2. Create a new request, set the cookies, and see if the (anon) user is blocked.
|
|
|
|
|
|
$request2 = new FauxRequest();
|
2019-11-18 17:08:54 +00:00
|
|
|
|
$request2->setCookie( 'BlockID', $blockManager->getCookieValue( $block ) );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$user2 = User::newFromSession( $request2 );
|
|
|
|
|
|
$user2->load();
|
2019-11-18 17:08:54 +00:00
|
|
|
|
$blockManager->trackBlockWithCookie( $user2, $request2->response() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$this->assertNotEquals( $user1->getId(), $user2->getId() );
|
|
|
|
|
|
$this->assertNotEquals( $user1->getToken(), $user2->getToken() );
|
|
|
|
|
|
$this->assertTrue( $user2->isAnon() );
|
|
|
|
|
|
$this->assertFalse( $user2->isLoggedIn() );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$this->assertInstanceOf( DatabaseBlock::class, $user2->getBlock() );
|
2017-09-18 18:57:17 +00:00
|
|
|
|
// Non-strict type-check.
|
|
|
|
|
|
$this->assertEquals( true, $user2->getBlock()->isAutoblocking(), 'Autoblock does not work' );
|
2018-08-13 17:07:14 +00:00
|
|
|
|
// Can't directly compare the objects because of member type differences.
|
2013-02-07 21:56:54 +00:00
|
|
|
|
// One day this will work: $this->assertEquals( $block, $user2->getBlock() );
|
|
|
|
|
|
$this->assertEquals( $block->getId(), $user2->getBlock()->getId() );
|
|
|
|
|
|
$this->assertEquals( $block->getExpiry(), $user2->getBlock()->getExpiry() );
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Finally, set up a request as a new user, and the block should still be applied.
|
|
|
|
|
|
$user3tmp = $this->getTestUser()->getUser();
|
|
|
|
|
|
$request3 = new FauxRequest();
|
|
|
|
|
|
$request3->getSession()->setUser( $user3tmp );
|
|
|
|
|
|
$request3->setCookie( 'BlockID', $block->getId() );
|
|
|
|
|
|
$user3 = User::newFromSession( $request3 );
|
|
|
|
|
|
$user3->load();
|
2019-11-18 17:08:54 +00:00
|
|
|
|
$blockManager->trackBlockWithCookie( $user3, $request3->response() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$this->assertTrue( $user3->isLoggedIn() );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$this->assertInstanceOf( DatabaseBlock::class, $user3->getBlock() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$this->assertEquals( true, $user3->getBlock()->isAutoblocking() ); // Non-strict type-check.
|
|
|
|
|
|
|
|
|
|
|
|
// Clean up.
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Make sure that no cookie is set to track autoblocked users
|
|
|
|
|
|
* when $wgCookieSetOnAutoblock is false.
|
2019-02-01 19:44:21 +00:00
|
|
|
|
* @covers User::trackBlockWithCookie
|
2013-02-07 21:56:54 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testAutoblockCookiesDisabled() {
|
|
|
|
|
|
// Set up the bits of global configuration that we use.
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgCookieSetOnAutoblock' => false,
|
|
|
|
|
|
'wgCookiePrefix' => 'wm_no_cookies',
|
2017-01-04 03:38:27 +00:00
|
|
|
|
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
|
2013-02-07 21:56:54 +00:00
|
|
|
|
] );
|
|
|
|
|
|
|
2017-09-18 18:57:17 +00:00
|
|
|
|
// Unregister the hooks for proper unit testing
|
|
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgHooks', [
|
|
|
|
|
|
'PerformRetroactiveAutoblock' => []
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2013-02-07 21:56:54 +00:00
|
|
|
|
// 1. Log in a test user, and block them.
|
|
|
|
|
|
$testUser = $this->getTestUser()->getUser();
|
|
|
|
|
|
$request1 = new FauxRequest();
|
|
|
|
|
|
$request1->getSession()->setUser( $testUser );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [ 'enableAutoblock' => true ] );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$block->setTarget( $testUser );
|
2017-09-18 18:03:04 +00:00
|
|
|
|
$res = $block->insert();
|
|
|
|
|
|
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$user = User::newFromSession( $request1 );
|
|
|
|
|
|
$user->load();
|
2019-09-07 23:44:46 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getBlockManager()
|
|
|
|
|
|
->trackBlockWithCookie( $user, $request1->response() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
|
|
|
|
|
|
// 2. Test that the cookie IS NOT present.
|
|
|
|
|
|
$this->assertTrue( $user->isLoggedIn() );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$this->assertInstanceOf( DatabaseBlock::class, $user->getBlock() );
|
|
|
|
|
|
$this->assertEquals( DatabaseBlock::TYPE_USER, $block->getType() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$this->assertTrue( $block->isAutoblocking() );
|
|
|
|
|
|
$this->assertGreaterThanOrEqual( 1, $user->getBlockId() );
|
|
|
|
|
|
$this->assertGreaterThanOrEqual( $block->getId(), $user->getBlockId() );
|
|
|
|
|
|
$cookies = $request1->response()->getCookies();
|
|
|
|
|
|
$this->assertArrayNotHasKey( 'wm_no_cookiesBlockID', $cookies );
|
|
|
|
|
|
|
|
|
|
|
|
// Clean up.
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* When a user is autoblocked and a cookie is set to track them, the expiry time of the cookie
|
2016-12-20 12:08:38 +00:00
|
|
|
|
* should match the block's expiry, to a maximum of 24 hours. If the expiry time is changed,
|
|
|
|
|
|
* the cookie's should change with it.
|
2019-02-01 19:44:21 +00:00
|
|
|
|
* @covers User::trackBlockWithCookie
|
2013-02-07 21:56:54 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testAutoblockCookieInfiniteExpiry() {
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgCookieSetOnAutoblock' => true,
|
|
|
|
|
|
'wgCookiePrefix' => 'wm_infinite_block',
|
2017-01-04 03:38:27 +00:00
|
|
|
|
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
|
2013-02-07 21:56:54 +00:00
|
|
|
|
] );
|
2017-09-18 18:57:17 +00:00
|
|
|
|
|
|
|
|
|
|
// Unregister the hooks for proper unit testing
|
|
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgHooks', [
|
|
|
|
|
|
'PerformRetroactiveAutoblock' => []
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2013-02-07 21:56:54 +00:00
|
|
|
|
// 1. Log in a test user, and block them indefinitely.
|
|
|
|
|
|
$user1Tmp = $this->getTestUser()->getUser();
|
|
|
|
|
|
$request1 = new FauxRequest();
|
|
|
|
|
|
$request1->getSession()->setUser( $user1Tmp );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [ 'enableAutoblock' => true, 'expiry' => 'infinity' ] );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$block->setTarget( $user1Tmp );
|
2017-09-18 18:03:04 +00:00
|
|
|
|
$res = $block->insert();
|
|
|
|
|
|
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$user1 = User::newFromSession( $request1 );
|
|
|
|
|
|
$user1->load();
|
2019-09-07 23:44:46 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getBlockManager()
|
|
|
|
|
|
->trackBlockWithCookie( $user1, $request1->response() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
|
|
|
|
|
|
// 2. Test the cookie's expiry timestamp.
|
|
|
|
|
|
$this->assertTrue( $user1->isLoggedIn() );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$this->assertInstanceOf( DatabaseBlock::class, $user1->getBlock() );
|
|
|
|
|
|
$this->assertEquals( DatabaseBlock::TYPE_USER, $block->getType() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$this->assertTrue( $block->isAutoblocking() );
|
|
|
|
|
|
$this->assertGreaterThanOrEqual( 1, $user1->getBlockId() );
|
|
|
|
|
|
$cookies = $request1->response()->getCookies();
|
2016-12-20 12:08:38 +00:00
|
|
|
|
// Test the cookie's expiry to the nearest minute.
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$this->assertArrayHasKey( 'wm_infinite_blockBlockID', $cookies );
|
2016-12-20 12:08:38 +00:00
|
|
|
|
$expOneDay = wfTimestamp() + ( 24 * 60 * 60 );
|
2016-12-20 12:13:56 +00:00
|
|
|
|
// Check for expiry dates in a 10-second window, to account for slow testing.
|
2017-01-04 03:29:35 +00:00
|
|
|
|
$this->assertEquals(
|
|
|
|
|
|
$expOneDay,
|
|
|
|
|
|
$cookies['wm_infinite_blockBlockID']['expire'],
|
|
|
|
|
|
'Expiry date',
|
|
|
|
|
|
5.0
|
2016-12-20 12:13:56 +00:00
|
|
|
|
);
|
2013-02-07 21:56:54 +00:00
|
|
|
|
|
2016-12-20 12:08:38 +00:00
|
|
|
|
// 3. Change the block's expiry (to 2 hours), and the cookie's should be changed also.
|
|
|
|
|
|
$newExpiry = wfTimestamp() + 2 * 60 * 60;
|
2019-03-22 15:16:40 +00:00
|
|
|
|
$block->setExpiry( wfTimestamp( TS_MW, $newExpiry ) );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$block->update();
|
|
|
|
|
|
$user2tmp = $this->getTestUser()->getUser();
|
|
|
|
|
|
$request2 = new FauxRequest();
|
|
|
|
|
|
$request2->getSession()->setUser( $user2tmp );
|
|
|
|
|
|
$user2 = User::newFromSession( $request2 );
|
|
|
|
|
|
$user2->load();
|
2019-09-07 23:44:46 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getBlockManager()
|
|
|
|
|
|
->trackBlockWithCookie( $user2, $request2->response() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$cookies = $request2->response()->getCookies();
|
2016-12-20 12:08:38 +00:00
|
|
|
|
$this->assertEquals( wfTimestamp( TS_MW, $newExpiry ), $block->getExpiry() );
|
2013-02-07 21:56:54 +00:00
|
|
|
|
$this->assertEquals( $newExpiry, $cookies['wm_infinite_blockBlockID']['expire'] );
|
|
|
|
|
|
|
|
|
|
|
|
// Clean up.
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
2016-11-23 19:51:30 +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
|
2019-03-18 21:50:48 +00:00
|
|
|
|
$wgUser = new User();
|
2016-11-23 19:51:30 +00:00
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '192.168.0.1' );
|
2019-03-19 18:56:10 +00:00
|
|
|
|
$this->setSessionUser( $wgUser, $request );
|
2016-11-23 19:51:30 +00:00
|
|
|
|
$this->assertNull( $wgUser->getBlock() );
|
|
|
|
|
|
|
|
|
|
|
|
// IP is in $wgSoftBlockRanges
|
2019-03-18 21:50:48 +00:00
|
|
|
|
$wgUser = new User();
|
2016-11-23 19:51:30 +00:00
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '10.20.30.40' );
|
2019-03-19 18:56:10 +00:00
|
|
|
|
$this->setSessionUser( $wgUser, $request );
|
2016-11-23 19:51:30 +00:00
|
|
|
|
$block = $wgUser->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
|
|
|
|
$wgUser = $this->getTestUser()->getUser();
|
|
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '10.20.30.40' );
|
2019-03-19 18:56:10 +00:00
|
|
|
|
$this->setSessionUser( $wgUser, $request );
|
2016-11-23 19:51:30 +00:00
|
|
|
|
$this->assertFalse( $wgUser->isAnon(), 'sanity check' );
|
|
|
|
|
|
$this->assertNull( $wgUser->getBlock() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-04 03:38:27 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Test that a modified BlockID cookie doesn't actually load the relevant block (T152951).
|
2019-02-01 19:44:21 +00:00
|
|
|
|
* @covers User::trackBlockWithCookie
|
2017-01-04 03:38:27 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testAutoblockCookieInauthentic() {
|
|
|
|
|
|
// Set up the bits of global configuration that we use.
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgCookieSetOnAutoblock' => true,
|
|
|
|
|
|
'wgCookiePrefix' => 'wmsitetitle',
|
|
|
|
|
|
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2017-09-18 18:57:17 +00:00
|
|
|
|
// Unregister the hooks for proper unit testing
|
|
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgHooks', [
|
|
|
|
|
|
'PerformRetroactiveAutoblock' => []
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2017-01-04 03:38:27 +00:00
|
|
|
|
// 1. Log in a blocked test user.
|
|
|
|
|
|
$user1tmp = $this->getTestUser()->getUser();
|
|
|
|
|
|
$request1 = new FauxRequest();
|
|
|
|
|
|
$request1->getSession()->setUser( $user1tmp );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [ 'enableAutoblock' => true ] );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
2017-01-04 03:38:27 +00:00
|
|
|
|
$block->setTarget( $user1tmp );
|
2017-09-18 18:03:04 +00:00
|
|
|
|
$res = $block->insert();
|
|
|
|
|
|
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
|
2017-01-04 03:38:27 +00:00
|
|
|
|
|
|
|
|
|
|
// 2. Create a new request, set the cookie to an invalid value, and make sure the (anon)
|
|
|
|
|
|
// user not blocked.
|
|
|
|
|
|
$request2 = new FauxRequest();
|
|
|
|
|
|
$request2->setCookie( 'BlockID', $block->getId() . '!zzzzzzz' );
|
|
|
|
|
|
$user2 = User::newFromSession( $request2 );
|
|
|
|
|
|
$user2->load();
|
|
|
|
|
|
$this->assertTrue( $user2->isAnon() );
|
|
|
|
|
|
$this->assertFalse( $user2->isLoggedIn() );
|
2019-04-23 17:51:54 +00:00
|
|
|
|
$this->assertNull( $user2->getBlock() );
|
2017-01-04 03:38:27 +00:00
|
|
|
|
|
|
|
|
|
|
// Clean up.
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* The BlockID cookie is normally verified with a HMAC, but not if wgSecretKey is not set.
|
|
|
|
|
|
* This checks that a non-authenticated cookie still works.
|
2019-02-01 19:44:21 +00:00
|
|
|
|
* @covers User::trackBlockWithCookie
|
2017-01-04 03:38:27 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testAutoblockCookieNoSecretKey() {
|
|
|
|
|
|
// Set up the bits of global configuration that we use.
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgCookieSetOnAutoblock' => true,
|
|
|
|
|
|
'wgCookiePrefix' => 'wmsitetitle',
|
|
|
|
|
|
'wgSecretKey' => null,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2017-09-18 18:57:17 +00:00
|
|
|
|
// Unregister the hooks for proper unit testing
|
|
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgHooks', [
|
|
|
|
|
|
'PerformRetroactiveAutoblock' => []
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2017-01-04 03:38:27 +00:00
|
|
|
|
// 1. Log in a blocked test user.
|
|
|
|
|
|
$user1tmp = $this->getTestUser()->getUser();
|
|
|
|
|
|
$request1 = new FauxRequest();
|
|
|
|
|
|
$request1->getSession()->setUser( $user1tmp );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [ 'enableAutoblock' => true ] );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
2017-01-04 03:38:27 +00:00
|
|
|
|
$block->setTarget( $user1tmp );
|
2017-09-18 18:03:04 +00:00
|
|
|
|
$res = $block->insert();
|
|
|
|
|
|
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
|
2017-01-04 03:38:27 +00:00
|
|
|
|
$user1 = User::newFromSession( $request1 );
|
|
|
|
|
|
$user1->load();
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$this->assertInstanceOf( DatabaseBlock::class, $user1->getBlock() );
|
2017-01-04 03:38:27 +00:00
|
|
|
|
|
|
|
|
|
|
// 2. Create a new request, set the cookie to just the block ID, and the user should
|
|
|
|
|
|
// still get blocked when they log in again.
|
|
|
|
|
|
$request2 = new FauxRequest();
|
|
|
|
|
|
$request2->setCookie( 'BlockID', $block->getId() );
|
|
|
|
|
|
$user2 = User::newFromSession( $request2 );
|
|
|
|
|
|
$user2->load();
|
|
|
|
|
|
$this->assertNotEquals( $user1->getId(), $user2->getId() );
|
|
|
|
|
|
$this->assertNotEquals( $user1->getToken(), $user2->getToken() );
|
|
|
|
|
|
$this->assertTrue( $user2->isAnon() );
|
|
|
|
|
|
$this->assertFalse( $user2->isLoggedIn() );
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$this->assertInstanceOf( DatabaseBlock::class, $user2->getBlock() );
|
2017-01-04 03:38:27 +00:00
|
|
|
|
$this->assertEquals( true, $user2->getBlock()->isAutoblocking() ); // Non-strict type-check.
|
|
|
|
|
|
|
|
|
|
|
|
// Clean up.
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
2017-02-02 01:23:01 +00:00
|
|
|
|
|
2017-12-25 07:27:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::isPingLimitable
|
|
|
|
|
|
*/
|
2017-02-02 01:23:01 +00:00
|
|
|
|
public function testIsPingLimitable() {
|
|
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '1.2.3.4' );
|
|
|
|
|
|
$user = User::newFromSession( $request );
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
|
|
|
|
|
|
$this->assertTrue( $user->isPingLimitable() );
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( 'wgRateLimitsExcludedIPs', [ '1.2.3.4' ] );
|
|
|
|
|
|
$this->assertFalse( $user->isPingLimitable() );
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( 'wgRateLimitsExcludedIPs', [ '1.2.3.0/8' ] );
|
|
|
|
|
|
$this->assertFalse( $user->isPingLimitable() );
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
|
2019-08-20 20:59:49 +00:00
|
|
|
|
$this->overrideUserPermissions( $user, 'noratelimit' );
|
|
|
|
|
|
$this->assertFalse( $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' ],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
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,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
$db = wfGetDB( DB_MASTER );
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$userQuery = User::getQueryInfo();
|
|
|
|
|
|
$row = $db->selectRow(
|
|
|
|
|
|
$userQuery['tables'],
|
|
|
|
|
|
$userQuery['fields'],
|
|
|
|
|
|
[ 'user_id' => $this->getTestUser()->getUser()->getId() ],
|
|
|
|
|
|
__METHOD__,
|
|
|
|
|
|
[],
|
|
|
|
|
|
$userQuery['joins']
|
|
|
|
|
|
);
|
|
|
|
|
|
$row->user_editcount = $editCount;
|
|
|
|
|
|
$row->user_registration = $db->timestamp( time() - $memberSince * 86400 );
|
|
|
|
|
|
$user = User::newFromRow( $row );
|
2017-02-10 14:18:02 +00:00
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( $expLevel, $user->getExperienceLevel() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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' ],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2019-04-05 19:13:17 +00:00
|
|
|
|
* @dataProvider provideIsLocallyBlockedProxy
|
2017-04-15 23:52:23 +00:00
|
|
|
|
* @covers User::isLocallyBlockedProxy
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testIsLocallyBlockedProxy( $ip, $blockListEntry ) {
|
2019-04-05 19:13:17 +00:00
|
|
|
|
$this->hideDeprecated( 'User::isLocallyBlockedProxy' );
|
|
|
|
|
|
|
2017-04-15 23:52:23 +00:00
|
|
|
|
$this->setMwGlobals(
|
|
|
|
|
|
'wgProxyList', []
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertFalse( User::isLocallyBlockedProxy( $ip ) );
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals(
|
|
|
|
|
|
'wgProxyList',
|
|
|
|
|
|
[
|
|
|
|
|
|
$blockListEntry
|
|
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
|
|
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals(
|
|
|
|
|
|
'wgProxyList',
|
|
|
|
|
|
[
|
|
|
|
|
|
'test' => $blockListEntry
|
|
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
|
|
|
|
|
|
}
|
2017-09-12 17:12:29 +00:00
|
|
|
|
|
2019-02-01 19:44:21 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromActorId
|
|
|
|
|
|
*/
|
2017-09-12 17:12:29 +00:00
|
|
|
|
public function testActorId() {
|
2018-10-15 22:20:50 +00:00
|
|
|
|
$domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
|
2017-09-12 17:12:29 +00:00
|
|
|
|
$this->hideDeprecated( 'User::selectFields' );
|
|
|
|
|
|
|
|
|
|
|
|
// Newly-created user has an actor ID
|
|
|
|
|
|
$user = User::createNew( 'UserTestActorId1' );
|
|
|
|
|
|
$id = $user->getId();
|
|
|
|
|
|
$this->assertTrue( $user->getActorId() > 0, 'User::createNew sets an actor ID' );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'UserTestActorId2' );
|
|
|
|
|
|
$user->addToDatabase();
|
|
|
|
|
|
$this->assertTrue( $user->getActorId() > 0, 'User::addToDatabase sets an actor ID' );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( 'UserTestActorId1' );
|
|
|
|
|
|
$this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by name' );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromId( $id );
|
|
|
|
|
|
$this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by ID' );
|
|
|
|
|
|
|
2019-04-04 19:23:41 +00:00
|
|
|
|
$user2 = User::newFromActorId( $user->getActorId() );
|
|
|
|
|
|
$this->assertEquals( $user->getId(), $user2->getId(),
|
|
|
|
|
|
'User::newFromActorId works for an existing user' );
|
|
|
|
|
|
|
|
|
|
|
|
$row = $this->db->selectRow( 'user', User::selectFields(), [ 'user_id' => $id ], __METHOD__ );
|
|
|
|
|
|
$user = User::newFromRow( $row );
|
|
|
|
|
|
$this->assertTrue( $user->getActorId() > 0,
|
|
|
|
|
|
'Actor ID can be retrieved for user loaded with User::selectFields()' );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromId( $id );
|
|
|
|
|
|
$user->setName( 'UserTestActorId4-renamed' );
|
|
|
|
|
|
$user->saveSettings();
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
|
$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 );
|
|
|
|
|
|
$this->assertFalse( $user->getActorId() > 0, 'Anonymous user has no actor ID by default' );
|
|
|
|
|
|
$this->assertTrue( $user->getActorId( $this->db ) > 0,
|
|
|
|
|
|
'Actor ID can be created for an anonymous user' );
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::newFromName( $ip, false );
|
|
|
|
|
|
$this->assertTrue( $user->getActorId() > 0, 'Actor ID can be loaded for an anonymous user' );
|
|
|
|
|
|
$user2 = User::newFromActorId( $user->getActorId() );
|
|
|
|
|
|
$this->assertEquals( $user->getName(), $user2->getName(),
|
|
|
|
|
|
'User::newFromActorId works for an anonymous user' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-01 19:44:21 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromAnyId
|
|
|
|
|
|
*/
|
2017-09-12 17:12:29 +00:00
|
|
|
|
public function testNewFromAnyId() {
|
|
|
|
|
|
// Registered user
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
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 );
|
|
|
|
|
|
$user->getActorId( $this->db ); // Make sure an actor ID exists
|
|
|
|
|
|
|
|
|
|
|
|
$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
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
|
|
|
|
|
|
$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' );
|
|
|
|
|
|
|
|
|
|
|
|
// Actor only
|
|
|
|
|
|
$identity = new UserIdentityValue( 0, '', $user->getActorId() );
|
|
|
|
|
|
$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' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-09-20 03:02:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::newFromName
|
|
|
|
|
|
* @covers User::getName
|
|
|
|
|
|
* @covers User::getUserPage
|
|
|
|
|
|
* @covers User::getTalkPage
|
|
|
|
|
|
* @covers User::getTitleKey
|
|
|
|
|
|
* @dataProvider provideNewFromName
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testNewFromName( $name, $titleKey ) {
|
|
|
|
|
|
$user = User::newFromName( $name );
|
|
|
|
|
|
$this->assertEquals( $user->getName(), $name );
|
|
|
|
|
|
$this->assertEquals( $user->getUserPage(), Title::makeTitle( NS_USER, $name ) );
|
|
|
|
|
|
$this->assertEquals( $user->getTalkPage(), Title::makeTitle( NS_USER_TALK, $name ) );
|
|
|
|
|
|
$this->assertEquals( $user->getTitleKey(), $titleKey );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function provideNewFromName() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
[ 'Example1', 'Example1' ],
|
|
|
|
|
|
[ 'Mediawiki easter egg', 'Mediawiki_easter_egg' ],
|
|
|
|
|
|
[ 'See T22281 for more', 'See_T22281_for_more' ],
|
|
|
|
|
|
[ 'DannyS712', 'DannyS712' ],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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 );
|
|
|
|
|
|
$res = $block->insert();
|
|
|
|
|
|
$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 ) );
|
2019-09-20 03:02:12 +00:00
|
|
|
|
$this->assertEquals( $res['id'], $user->getBlockId() );
|
2018-03-22 16:52:59 +00:00
|
|
|
|
|
|
|
|
|
|
// Unblock
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
|
|
|
|
|
|
// 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 );
|
|
|
|
|
|
|
|
|
|
|
|
$ipBlock = new Block( [
|
|
|
|
|
|
'address' => $user->getRequest()->getIP(),
|
|
|
|
|
|
'by' => $this->getTestSysop()->getUser()->getId(),
|
|
|
|
|
|
'createAccount' => true,
|
|
|
|
|
|
] );
|
|
|
|
|
|
$ipBlock->insert();
|
|
|
|
|
|
|
|
|
|
|
|
$userBlock = new Block( [
|
|
|
|
|
|
'address' => $user,
|
|
|
|
|
|
'by' => $this->getTestSysop()->getUser()->getId(),
|
|
|
|
|
|
'createAccount' => false,
|
|
|
|
|
|
] );
|
|
|
|
|
|
$userBlock->insert();
|
|
|
|
|
|
|
|
|
|
|
|
$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,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
|
|
|
|
|
|
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 );
|
|
|
|
|
|
}
|
|
|
|
|
|
$block->insert();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
$this->assertSame( $expect, $user->isBlockedFrom( $title ) );
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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() );
|
|
|
|
|
|
$block->insert();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
$this->assertEquals( $user->isBlockedFromEmailuser(), $blockFromEmail );
|
|
|
|
|
|
$this->assertEquals( $user->isAllowedToCreateAccount(), !$blockFromAccountCreation );
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 ],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-05-17 05:55:02 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Block cookie should be set for IP Blocks if
|
|
|
|
|
|
* wgCookieSetOnIpBlock is set to true
|
2019-02-01 19:44:21 +00:00
|
|
|
|
* @covers User::trackBlockWithCookie
|
2018-05-17 05:55:02 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testIpBlockCookieSet() {
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgCookieSetOnIpBlock' => true,
|
|
|
|
|
|
'wgCookiePrefix' => 'wiki',
|
|
|
|
|
|
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
// setup block
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [
|
2018-05-17 05:55:02 +00:00
|
|
|
|
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 5 * 60 * 60 ) ),
|
|
|
|
|
|
] );
|
|
|
|
|
|
$block->setTarget( '1.2.3.4' );
|
|
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
|
|
|
|
|
$block->insert();
|
|
|
|
|
|
|
|
|
|
|
|
// setup request
|
|
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '1.2.3.4' );
|
|
|
|
|
|
|
|
|
|
|
|
// get user
|
|
|
|
|
|
$user = User::newFromSession( $request );
|
2019-09-07 23:44:46 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getBlockManager()
|
|
|
|
|
|
->trackBlockWithCookie( $user, $request->response() );
|
2018-05-17 05:55:02 +00:00
|
|
|
|
|
|
|
|
|
|
// test cookie was set
|
|
|
|
|
|
$cookies = $request->response()->getCookies();
|
|
|
|
|
|
$this->assertArrayHasKey( 'wikiBlockID', $cookies );
|
|
|
|
|
|
|
|
|
|
|
|
// clean up
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Block cookie should NOT be set when wgCookieSetOnIpBlock
|
|
|
|
|
|
* is disabled
|
2019-02-01 19:44:21 +00:00
|
|
|
|
* @covers User::trackBlockWithCookie
|
2018-05-17 05:55:02 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testIpBlockCookieNotSet() {
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgCookieSetOnIpBlock' => false,
|
|
|
|
|
|
'wgCookiePrefix' => 'wiki',
|
|
|
|
|
|
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
// setup block
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [
|
2018-05-17 05:55:02 +00:00
|
|
|
|
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 5 * 60 * 60 ) ),
|
|
|
|
|
|
] );
|
|
|
|
|
|
$block->setTarget( '1.2.3.4' );
|
|
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
|
|
|
|
|
$block->insert();
|
|
|
|
|
|
|
|
|
|
|
|
// setup request
|
|
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '1.2.3.4' );
|
|
|
|
|
|
|
|
|
|
|
|
// get user
|
|
|
|
|
|
$user = User::newFromSession( $request );
|
2019-09-07 23:44:46 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getBlockManager()
|
|
|
|
|
|
->trackBlockWithCookie( $user, $request->response() );
|
2018-05-17 05:55:02 +00:00
|
|
|
|
|
|
|
|
|
|
// test cookie was not set
|
|
|
|
|
|
$cookies = $request->response()->getCookies();
|
|
|
|
|
|
$this->assertArrayNotHasKey( 'wikiBlockID', $cookies );
|
|
|
|
|
|
|
|
|
|
|
|
// clean up
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* When an ip user is blocked and then they log in, cookie block
|
|
|
|
|
|
* should be invalid and the cookie removed.
|
2019-02-01 19:44:21 +00:00
|
|
|
|
* @covers User::trackBlockWithCookie
|
2018-05-17 05:55:02 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function testIpBlockCookieIgnoredWhenUserLoggedIn() {
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgAutoblockExpiry' => 8000,
|
|
|
|
|
|
'wgCookieSetOnIpBlock' => true,
|
|
|
|
|
|
'wgCookiePrefix' => 'wiki',
|
|
|
|
|
|
'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
2019-11-18 17:08:54 +00:00
|
|
|
|
$blockManager = MediaWikiServices::getInstance()->getBlockManager();
|
|
|
|
|
|
|
2018-05-17 05:55:02 +00:00
|
|
|
|
// setup block
|
2019-05-13 14:18:07 +00:00
|
|
|
|
$block = new DatabaseBlock( [
|
2018-05-17 05:55:02 +00:00
|
|
|
|
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
|
|
|
|
|
|
] );
|
|
|
|
|
|
$block->setTarget( '1.2.3.4' );
|
|
|
|
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
|
|
|
|
|
$block->insert();
|
|
|
|
|
|
|
|
|
|
|
|
// setup request
|
|
|
|
|
|
$request = new FauxRequest();
|
|
|
|
|
|
$request->setIP( '1.2.3.4' );
|
|
|
|
|
|
$request->getSession()->setUser( $this->getTestUser()->getUser() );
|
2019-11-18 17:08:54 +00:00
|
|
|
|
$request->setCookie( 'BlockID', $blockManager->getCookieValue( $block ) );
|
2018-05-17 05:55:02 +00:00
|
|
|
|
|
|
|
|
|
|
// setup user
|
|
|
|
|
|
$user = User::newFromSession( $request );
|
|
|
|
|
|
|
|
|
|
|
|
// logged in users should be inmune to cookie block of type ip/range
|
2019-04-23 17:51:54 +00:00
|
|
|
|
$this->assertNull( $user->getBlock() );
|
2018-05-17 05:55:02 +00:00
|
|
|
|
|
|
|
|
|
|
// clean up
|
|
|
|
|
|
$block->delete();
|
|
|
|
|
|
}
|
2019-03-06 16:28:51 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getFirstEditTimestamp
|
|
|
|
|
|
* @covers User::getLatestEditTimestamp
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetFirstLatestEditTimestamp() {
|
|
|
|
|
|
$clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
|
|
|
|
|
|
MWTimestamp::setFakeTime( function () use ( &$clock ) {
|
|
|
|
|
|
return $clock += 1000;
|
|
|
|
|
|
} );
|
2019-03-07 21:18:22 +00:00
|
|
|
|
try {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$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() );
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( $firstRevision->getTimestamp(), $user->getFirstEditTimestamp() );
|
|
|
|
|
|
$this->assertEquals( $secondRevision->getTimestamp(), $user->getLatestEditTimestamp() );
|
|
|
|
|
|
} 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->assertTrue(
|
|
|
|
|
|
array_key_exists( $this->user->getName(), User::$idCacheByName ),
|
|
|
|
|
|
'Test user should already be in the id cache.'
|
|
|
|
|
|
);
|
|
|
|
|
|
$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() {
|
|
|
|
|
|
$this->assertFalse(
|
|
|
|
|
|
array_key_exists( 'NotExisitngUser', User::$idCacheByName ),
|
|
|
|
|
|
'Non exisitng user should not be in the id cache.'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertSame( null, User::idFromName( 'NotExisitngUser' ) );
|
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
|
array_key_exists( 'NotExisitngUser', User::$idCacheByName ),
|
|
|
|
|
|
'Username will be cached when requested once.'
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->assertSame( null, User::idFromName( 'NotExisitngUser' ) );
|
|
|
|
|
|
}
|
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 ) {
|
|
|
|
|
|
$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 );
|
|
|
|
|
|
$this->assertTrue( User::isValidUserName( $name ) );
|
|
|
|
|
|
$this->assertSame( empty( $testOpts['reserved'] ), User::isUsableName( $name ) );
|
|
|
|
|
|
|
|
|
|
|
|
if ( $expect === 'exception' ) {
|
|
|
|
|
|
$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() );
|
|
|
|
|
|
}
|
|
|
|
|
|
} 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() );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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' ],
|
|
|
|
|
|
'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
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getDefaultOptions
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetDefaultOptions() {
|
|
|
|
|
|
User::resetGetDefaultOptionsForTestsOnly();
|
|
|
|
|
|
$defaultOptions = User::getDefaultOptions();
|
|
|
|
|
|
$this->assertArrayHasKey( 'search-match-redirect', $defaultOptions );
|
|
|
|
|
|
}
|
2019-11-23 10:23:57 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getAutomaticGroups
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetAutomaticGroups() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed'
|
|
|
|
|
|
], $user->getAutomaticGroups( true ) );
|
|
|
|
|
|
$user = $this->getTestUser( [ 'bureaucrat', 'test' ] )->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed'
|
|
|
|
|
|
], $user->getAutomaticGroups( true ) );
|
|
|
|
|
|
$user->addGroup( 'something' );
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed'
|
|
|
|
|
|
], $user->getAutomaticGroups( true ) );
|
|
|
|
|
|
$user = User::newFromName( 'UTUser1' );
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
], $user->getAutomaticGroups( true ) );
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
|
'wgAutopromote' => [
|
|
|
|
|
|
'dummy' => APCOND_EMAILCONFIRMED
|
|
|
|
|
|
]
|
|
|
|
|
|
] );
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$user->confirmEmail();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'dummy'
|
|
|
|
|
|
], $user->getAutomaticGroups( true ) );
|
|
|
|
|
|
$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 ) );
|
|
|
|
|
|
$user = $this->getTestUser( [ 'bureaucrat', 'test' ] )->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed',
|
|
|
|
|
|
'bureaucrat',
|
|
|
|
|
|
'test'
|
|
|
|
|
|
], $user->getEffectiveGroups( true ) );
|
|
|
|
|
|
$user = $this->getTestUser( [ 'autoconfirmed', 'test' ] )->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [
|
|
|
|
|
|
'*',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'autoconfirmed',
|
|
|
|
|
|
'test'
|
|
|
|
|
|
], $user->getEffectiveGroups( true ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getGroups
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetGroups() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$reflectionClass = new ReflectionClass( 'User' );
|
|
|
|
|
|
$reflectionProperty = $reflectionClass->getProperty( 'mLoadedItems' );
|
|
|
|
|
|
$reflectionProperty->setAccessible( true );
|
|
|
|
|
|
$reflectionProperty->setValue( $user, true );
|
|
|
|
|
|
$reflectionProperty = $reflectionClass->getProperty( 'mGroupMemberships' );
|
|
|
|
|
|
$reflectionProperty->setAccessible( true );
|
|
|
|
|
|
$reflectionProperty->setValue( $user, [ 'a' => 1, 'b' => 2 ] );
|
|
|
|
|
|
$this->assertArrayEquals( [ 'a', 'b' ], $user->getGroups() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::getFormerGroups
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testGetFormerGroups() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$reflectionClass = new ReflectionClass( 'User' );
|
|
|
|
|
|
$reflectionProperty = $reflectionClass->getProperty( 'mFormerGroups' );
|
|
|
|
|
|
$reflectionProperty->setAccessible( true );
|
|
|
|
|
|
$reflectionProperty->setValue( $user, [ 1, 2, 3 ] );
|
|
|
|
|
|
$this->assertArrayEquals( [ 1, 2, 3 ], $user->getFormerGroups() );
|
|
|
|
|
|
$reflectionProperty->setValue( $user, null );
|
|
|
|
|
|
$this->assertArrayEquals( [], $user->getFormerGroups() );
|
|
|
|
|
|
$user->addGroup( 'test' );
|
|
|
|
|
|
$user->removeGroup( 'test' );
|
|
|
|
|
|
$reflectionProperty->setValue( $user, null );
|
|
|
|
|
|
$this->assertArrayEquals( [ 'test' ], $user->getFormerGroups() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::addGroup
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testAddGroup() {
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
$this->assertArrayEquals( [], $user->getGroups() );
|
|
|
|
|
|
$user->addGroup( 'test' );
|
|
|
|
|
|
$this->assertArrayEquals( [ 'test' ], $user->getGroups() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @covers User::removeGroup
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function testRemoveGroup() {
|
|
|
|
|
|
$user = $this->getTestUser( [ 'test' ] )->getUser();
|
|
|
|
|
|
$user->removeGroup( 'test' );
|
|
|
|
|
|
$this->assertArrayEquals( [], $user->getGroups() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-07-19 21:41:25 +00:00
|
|
|
|
}
|