3 use MediaWiki\Block\BlockManager
;
4 use MediaWiki\Block\DatabaseBlock
;
5 use MediaWiki\Block\CompositeBlock
;
6 use MediaWiki\Block\SystemBlock
;
7 use MediaWiki\Config\ServiceOptions
;
8 use MediaWiki\MediaWikiServices
;
9 use Wikimedia\TestingAccessWrapper
;
14 * @coversDefaultClass \MediaWiki\Block\BlockManager
16 class BlockManagerTest
extends MediaWikiTestCase
{
24 protected function setUp() {
27 $this->user
= $this->getTestUser()->getUser();
28 $this->sysopId
= $this->getTestSysop()->getUser()->getId();
29 $this->blockManagerConfig
= [
30 'wgApplyIpBlocksToXff' => true,
31 'wgCookieSetOnAutoblock' => true,
32 'wgCookieSetOnIpBlock' => true,
33 'wgDnsBlacklistUrls' => [],
34 'wgEnableDnsBlacklist' => true,
36 'wgProxyWhitelist' => [],
37 'wgSecretKey' => false,
38 'wgSoftBlockRanges' => [],
42 private function getBlockManager( $overrideConfig ) {
43 return new BlockManager(
44 ...$this->getBlockManagerConstructorArgs( $overrideConfig )
48 private function getBlockManagerConstructorArgs( $overrideConfig ) {
49 $blockManagerConfig = array_merge( $this->blockManagerConfig
, $overrideConfig );
50 $this->setMwGlobals( $blockManagerConfig );
51 $this->overrideMwServices();
54 BlockManager
::$constructorOptions,
55 MediaWikiServices
::getInstance()->getMainConfig()
58 $this->user
->getRequest()
63 * @dataProvider provideGetBlockFromCookieValue
64 * @covers ::getBlockFromCookieValue
65 * @covers ::shouldApplyCookieBlock
67 public function testGetBlockFromCookieValue( $options, $expected ) {
68 $blockManager = TestingAccessWrapper
::newFromObject(
69 $this->getBlockManager( [
70 'wgCookieSetOnAutoblock' => true,
71 'wgCookieSetOnIpBlock' => true,
75 $block = new DatabaseBlock( array_merge( [
76 'address' => $options['target'] ?
: $this->user
,
77 'by' => $this->sysopId
,
78 ], $options['blockOptions'] ) );
81 $user = $options['loggedIn'] ?
$this->user
: new User();
82 $user->getRequest()->setCookie( 'BlockID', $block->getCookieValue() );
84 $this->assertSame( $expected, (bool)$blockManager->getBlockFromCookieValue(
92 public static function provideGetBlockFromCookieValue() {
94 'Autoblocking user block' => [
99 'enableAutoblock' => true
104 'Non-autoblocking user block' => [
108 'blockOptions' => [],
112 'IP block for anonymous user' => [
114 'target' => '127.0.0.1',
116 'blockOptions' => [],
120 'IP block for logged in user' => [
122 'target' => '127.0.0.1',
124 'blockOptions' => [],
128 'IP range block for anonymous user' => [
130 'target' => '127.0.0.0/8',
132 'blockOptions' => [],
140 * @dataProvider provideIsLocallyBlockedProxy
141 * @covers ::isLocallyBlockedProxy
143 public function testIsLocallyBlockedProxy( $proxyList, $expected ) {
144 $blockManager = TestingAccessWrapper
::newFromObject(
145 $this->getBlockManager( [
146 'wgProxyList' => $proxyList
151 $this->assertSame( $expected, $blockManager->isLocallyBlockedProxy( $ip ) );
154 public static function provideIsLocallyBlockedProxy() {
156 'Proxy list is empty' => [ [], false ],
157 'Proxy list contains IP' => [ [ '1.2.3.4' ], true ],
158 'Proxy list contains IP as value' => [ [ 'test' => '1.2.3.4' ], true ],
159 'Proxy list contains range that covers IP' => [ [ '1.2.3.0/16' ], true ],
164 * @covers ::isLocallyBlockedProxy
166 public function testIsLocallyBlockedProxyDeprecated() {
169 $this->hideDeprecated(
170 'IP addresses in the keys of $wgProxyList (found the following IP ' .
171 'addresses in keys: ' . $proxy . ', please move them to values)'
174 $blockManager = TestingAccessWrapper
::newFromObject(
175 $this->getBlockManager( [
176 'wgProxyList' => [ $proxy => 'test' ]
181 $this->assertTrue( $blockManager->isLocallyBlockedProxy( $ip ) );
185 * @dataProvider provideIsDnsBlacklisted
186 * @covers ::isDnsBlacklisted
187 * @covers ::inDnsBlacklist
189 public function testIsDnsBlacklisted( $options, $expected ) {
190 $blockManagerConfig = [
191 'wgEnableDnsBlacklist' => true,
192 'wgDnsBlacklistUrls' => $options['blacklist'],
193 'wgProxyWhitelist' => $options['whitelist'],
196 $blockManager = $this->getMockBuilder( BlockManager
::class )
197 ->setConstructorArgs( $this->getBlockManagerConstructorArgs( $blockManagerConfig ) )
198 ->setMethods( [ 'checkHost' ] )
200 $blockManager->method( 'checkHost' )
201 ->will( $this->returnValueMap( [ [
202 $options['dnsblQuery'],
203 $options['dnsblResponse'],
208 $blockManager->isDnsBlacklisted( $options['ip'], $options['checkWhitelist'] )
212 public static function provideIsDnsBlacklisted() {
213 $dnsblFound = [ '127.0.0.2' ];
214 $dnsblNotFound = false;
216 'IP is blacklisted' => [
218 'blacklist' => [ 'dnsbl.test' ],
220 'dnsblQuery' => '1.0.0.127.dnsbl.test',
221 'dnsblResponse' => $dnsblFound,
223 'checkWhitelist' => false,
227 'IP is blacklisted; blacklist has key' => [
229 'blacklist' => [ [ 'dnsbl.test', 'key' ] ],
231 'dnsblQuery' => 'key.1.0.0.127.dnsbl.test',
232 'dnsblResponse' => $dnsblFound,
234 'checkWhitelist' => false,
238 'IP is blacklisted; blacklist is array' => [
240 'blacklist' => [ [ 'dnsbl.test' ] ],
242 'dnsblQuery' => '1.0.0.127.dnsbl.test',
243 'dnsblResponse' => $dnsblFound,
245 'checkWhitelist' => false,
249 'IP is not blacklisted' => [
251 'blacklist' => [ 'dnsbl.test' ],
253 'dnsblQuery' => '4.3.2.1.dnsbl.test',
254 'dnsblResponse' => $dnsblNotFound,
256 'checkWhitelist' => false,
260 'Blacklist is empty' => [
264 'dnsblQuery' => '1.0.0.127.dnsbl.test',
265 'dnsblResponse' => $dnsblFound,
267 'checkWhitelist' => false,
271 'IP is blacklisted and whitelisted; whitelist is not checked' => [
273 'blacklist' => [ 'dnsbl.test' ],
275 'dnsblQuery' => '1.0.0.127.dnsbl.test',
276 'dnsblResponse' => $dnsblFound,
277 'whitelist' => [ '127.0.0.1' ],
278 'checkWhitelist' => false,
282 'IP is blacklisted and whitelisted; whitelist is checked' => [
284 'blacklist' => [ 'dnsbl.test' ],
286 'dnsblQuery' => '1.0.0.127.dnsbl.test',
287 'dnsblResponse' => $dnsblFound,
288 'whitelist' => [ '127.0.0.1' ],
289 'checkWhitelist' => true,
297 * @covers ::getUniqueBlocks
299 public function testGetUniqueBlocks() {
302 $blockManager = TestingAccessWrapper
::newFromObject( $this->getBlockManager( [] ) );
304 $block = $this->getMockBuilder( DatabaseBlock
::class )
305 ->setMethods( [ 'getId' ] )
307 $block->method( 'getId' )
308 ->willReturn( $blockId );
310 $autoblock = $this->getMockBuilder( DatabaseBlock
::class )
311 ->setMethods( [ 'getParentBlockId', 'getType' ] )
313 $autoblock->method( 'getParentBlockId' )
314 ->willReturn( $blockId );
315 $autoblock->method( 'getType' )
316 ->willReturn( DatabaseBlock
::TYPE_AUTO
);
318 $blocks = [ $block, $block, $autoblock, new SystemBlock() ];
320 $this->assertSame( 2, count( $blockManager->getUniqueBlocks( $blocks ) ) );
324 * @dataProvider provideTrackBlockWithCookie
325 * @covers ::trackBlockWithCookie
327 public function testTrackBlockWithCookie( $options, $expectedVal ) {
328 $this->setMwGlobals( 'wgCookiePrefix', '' );
330 $request = new FauxRequest();
331 if ( $options['cookieSet'] ) {
332 $request->setCookie( 'BlockID', 'the value does not matter' );
335 $user = $this->getMockBuilder( User
::class )
336 ->setMethods( [ 'getBlock', 'getRequest' ] )
338 $user->method( 'getBlock' )
339 ->willReturn( $options['block'] );
340 $user->method( 'getRequest' )
341 ->willReturn( $request );
343 // Although the block cookie is set via DeferredUpdates, in command line mode updates are
344 // processed immediately
345 $blockManager = $this->getBlockManager( [
347 'wgCookieSetOnIpBlock' => true,
349 $blockManager->trackBlockWithCookie( $user );
351 /** @var FauxResponse $response */
352 $response = $request->response();
353 $this->assertCount( $expectedVal ?
1 : 0, $response->getCookies() );
354 $this->assertEquals( $expectedVal ?
: null, $response->getCookie( 'BlockID' ) );
357 public function provideTrackBlockWithCookie() {
360 'Block cookie is already set; there is a trackable block' => [
363 'block' => $this->getTrackableBlock( $blockId ),
367 'Block cookie is already set; there is no block' => [
374 'Block cookie is not yet set; there is no block' => [
376 'cookieSet' => false,
381 'Block cookie is not yet set; there is a trackable block' => [
383 'cookieSet' => false,
384 'block' => $this->getTrackableBlock( $blockId ),
388 'Block cookie is not yet set; there is a composite block with a trackable block' => [
390 'cookieSet' => false,
391 'block' => new CompositeBlock( [
392 'originalBlocks' => [
394 $this->getTrackableBlock( $blockId ),
400 'Block cookie is not yet set; there is a composite block but no trackable block' => [
402 'cookieSet' => false,
403 'block' => new CompositeBlock( [
404 'originalBlocks' => [
415 private function getTrackableBlock( $blockId ) {
416 $block = $this->getMockBuilder( DatabaseBlock
::class )
417 ->setMethods( [ 'getType', 'getId' ] )
419 $block->method( 'getType' )
420 ->willReturn( DatabaseBlock
::TYPE_IP
);
421 $block->method( 'getId' )
422 ->willReturn( $blockId );
427 * @dataProvider provideSetBlockCookie
428 * @covers ::setBlockCookie
430 public function testSetBlockCookie( $expiryDelta, $expectedExpiryDelta ) {
431 $this->setMwGlobals( [
432 'wgCookiePrefix' => '',
435 $request = new FauxRequest();
436 $response = $request->response();
438 $blockManager = $this->getBlockManager( [
440 'wgCookieSetOnIpBlock' => true,
443 $now = wfTimestamp();
445 $block = new DatabaseBlock( [
446 'expiry' => $expiryDelta === '' ?
'' : $now +
$expiryDelta
448 $blockManager->setBlockCookie( $block, $response );
449 $cookies = $response->getCookies();
452 $now +
$expectedExpiryDelta,
453 $cookies['BlockID']['expire'],
455 60 // Allow actual to be up to 60 seconds later than expected
459 public static function provideSetBlockCookie() {
460 // Maximum length of a block cookie, defined in BlockManager::setBlockCookie
461 $maxExpiryDelta = ( 24 * 60 * 60 );
463 $longExpiryDelta = ( 48 * 60 * 60 );
464 $shortExpiryDelta = ( 12 * 60 * 60 );
467 'Block has indefinite expiry' => [
471 'Block expiry is later than maximum cookie block expiry' => [
475 'Block expiry is sooner than maximum cookie block expiry' => [
483 * @covers ::shouldTrackBlockWithCookie
485 public function testShouldTrackBlockWithCookieSystemBlock() {
486 $blockManager = TestingAccessWrapper
::newFromObject( $this->getBlockManager( [] ) );
487 $this->assertFalse( $blockManager->shouldTrackBlockWithCookie(
494 * @dataProvider provideShouldTrackBlockWithCookie
495 * @covers ::shouldTrackBlockWithCookie
497 public function testShouldTrackBlockWithCookie( $options, $expected ) {
498 $block = $this->getMockBuilder( DatabaseBlock
::class )
499 ->setMethods( [ 'getType', 'isAutoblocking' ] )
501 $block->method( 'getType' )
502 ->willReturn( $options['type'] );
503 if ( isset( $options['autoblocking'] ) ) {
504 $block->method( 'isAutoblocking' )
505 ->willReturn( $options['autoblocking'] );
508 $blockManager = TestingAccessWrapper
::newFromObject(
509 $this->getBlockManager( $options['blockManagerConfig'] )
514 $blockManager->shouldTrackBlockWithCookie( $block, $options['isAnon'] )
518 public static function provideShouldTrackBlockWithCookie() {
520 'IP block, anonymous user, IP block cookies enabled' => [
522 'type' => DatabaseBlock
::TYPE_IP
,
524 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
528 'IP range block, anonymous user, IP block cookies enabled' => [
530 'type' => DatabaseBlock
::TYPE_RANGE
,
532 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
536 'IP block, anonymous user, IP block cookies disabled' => [
538 'type' => DatabaseBlock
::TYPE_IP
,
540 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => false ],
544 'IP block, logged in user, IP block cookies enabled' => [
546 'type' => DatabaseBlock
::TYPE_IP
,
548 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
552 'User block, anonymous, autoblock cookies enabled, block is autoblocking' => [
554 'type' => DatabaseBlock
::TYPE_USER
,
556 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
557 'autoblocking' => true,
561 'User block, logged in, autoblock cookies enabled, block is autoblocking' => [
563 'type' => DatabaseBlock
::TYPE_USER
,
565 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
566 'autoblocking' => true,
570 'User block, logged in, autoblock cookies disabled, block is autoblocking' => [
572 'type' => DatabaseBlock
::TYPE_USER
,
574 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => false ],
575 'autoblocking' => true,
579 'User block, logged in, autoblock cookies enabled, block is not autoblocking' => [
581 'type' => DatabaseBlock
::TYPE_USER
,
583 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
584 'autoblocking' => false,
588 'Block type is autoblock' => [
590 'type' => DatabaseBlock
::TYPE_AUTO
,
592 'blockManagerConfig' => [],
600 * @covers ::clearBlockCookie
602 public function testClearBlockCookie() {
603 $this->setMwGlobals( [
604 'wgCookiePrefix' => '',
607 $request = new FauxRequest();
608 $response = $request->response();
609 $response->setCookie( 'BlockID', '100' );
610 $this->assertSame( '100', $response->getCookie( 'BlockID' ) );
612 BlockManager
::clearBlockCookie( $response );
613 $this->assertSame( '', $response->getCookie( 'BlockID' ) );
617 * @dataProvider provideGetIdFromCookieValue
618 * @covers ::getIdFromCookieValue
620 public function testGetIdFromCookieValue( $options, $expected ) {
621 $blockManager = $this->getBlockManager( [
622 'wgSecretKey' => $options['secretKey']
626 $blockManager->getIdFromCookieValue( $options['cookieValue'] )
630 public static function provideGetIdFromCookieValue() {
633 $hmac = MWCryptHash
::hmac( $blockId, $secretKey, false );
635 'No secret key is set' => [
638 'cookieValue' => $blockId,
639 'calculatedHmac' => MWCryptHash
::hmac( $blockId, '', false ),
643 'Secret key is set and stored hmac is correct' => [
645 'secretKey' => $secretKey,
646 'cookieValue' => $blockId . '!' . $hmac,
647 'calculatedHmac' => $hmac,
651 'Secret key is set and stored hmac is incorrect' => [
653 'secretKey' => $secretKey,
654 'cookieValue' => $blockId . '!xyz',
655 'calculatedHmac' => $hmac,
663 * @dataProvider provideGetCookieValue
664 * @covers ::getCookieValue
666 public function testGetCookieValue( $options, $expected ) {
667 $blockManager = $this->getBlockManager( [
668 'wgSecretKey' => $options['secretKey']
671 $block = $this->getMockBuilder( DatabaseBlock
::class )
672 ->setMethods( [ 'getId' ] )
674 $block->method( 'getId' )
675 ->willReturn( $options['blockId'] );
679 $blockManager->getCookieValue( $block )
683 public static function provideGetCookieValue() {
686 'Secret key not set' => [
689 'blockId' => $blockId,
690 'hmac' => MWCryptHash
::hmac( $blockId, '', false ),
694 'Secret key set' => [
696 'secretKey' => '123',
697 'blockId' => $blockId,
698 'hmac' => MWCryptHash
::hmac( $blockId, '123', false ),
700 $blockId . '!' . MWCryptHash
::hmac( $blockId, '123', false ) ],