3 use MediaWiki\Block\BlockManager
;
4 use MediaWiki\Block\DatabaseBlock
;
5 use MediaWiki\Block\CompositeBlock
;
6 use MediaWiki\Block\SystemBlock
;
7 use MediaWiki\MediaWikiServices
;
8 use Wikimedia\TestingAccessWrapper
;
9 use Psr\Log\LoggerInterface
;
14 * @coversDefaultClass \MediaWiki\Block\BlockManager
16 class BlockManagerTest
extends MediaWikiTestCase
{
17 use TestAllServiceOptionsUsed
;
25 protected function setUp() {
28 $this->user
= $this->getTestUser()->getUser();
29 $this->sysopId
= $this->getTestSysop()->getUser()->getId();
30 $this->blockManagerConfig
= [
31 'wgApplyIpBlocksToXff' => true,
32 'wgCookieSetOnAutoblock' => true,
33 'wgCookieSetOnIpBlock' => true,
34 'wgDnsBlacklistUrls' => [],
35 'wgEnableDnsBlacklist' => true,
37 'wgProxyWhitelist' => [],
38 'wgSecretKey' => false,
39 'wgSoftBlockRanges' => [],
43 private function getBlockManager( $overrideConfig ) {
44 return new BlockManager(
45 ...$this->getBlockManagerConstructorArgs( $overrideConfig )
49 private function getBlockManagerConstructorArgs( $overrideConfig ) {
50 $blockManagerConfig = array_merge( $this->blockManagerConfig
, $overrideConfig );
51 $this->setMwGlobals( $blockManagerConfig );
52 $logger = $this->getMockBuilder( LoggerInterface
::class )->getMock();
54 new LoggedServiceOptions(
55 self
::$serviceOptionsAccessLog,
56 BlockManager
::$constructorOptions,
57 MediaWikiServices
::getInstance()->getMainConfig()
59 MediaWikiServices
::getInstance()->getPermissionManager(),
65 * @dataProvider provideGetBlockFromCookieValue
66 * @covers ::getBlockFromCookieValue
67 * @covers ::shouldApplyCookieBlock
69 public function testGetBlockFromCookieValue( $options, $expected ) {
70 $blockManager = TestingAccessWrapper
::newFromObject(
71 $this->getBlockManager( [
72 'wgCookieSetOnAutoblock' => true,
73 'wgCookieSetOnIpBlock' => true,
77 $block = new DatabaseBlock( array_merge( [
78 'address' => $options['target'] ?
: $this->user
,
79 'by' => $this->sysopId
,
80 ], $options['blockOptions'] ) );
83 $user = $options['loggedIn'] ?
$this->user
: new User();
84 $user->getRequest()->setCookie( 'BlockID', $block->getCookieValue() );
86 $this->assertSame( $expected, (bool)$blockManager->getBlockFromCookieValue(
94 public static function provideGetBlockFromCookieValue() {
96 'Autoblocking user block' => [
101 'enableAutoblock' => true
106 'Non-autoblocking user block' => [
110 'blockOptions' => [],
114 'IP block for anonymous user' => [
116 'target' => '127.0.0.1',
118 'blockOptions' => [],
122 'IP block for logged in user' => [
124 'target' => '127.0.0.1',
126 'blockOptions' => [],
130 'IP range block for anonymous user' => [
132 'target' => '127.0.0.0/8',
134 'blockOptions' => [],
142 * @dataProvider provideIsLocallyBlockedProxy
143 * @covers ::isLocallyBlockedProxy
145 public function testIsLocallyBlockedProxy( $proxyList, $expected ) {
146 $blockManager = TestingAccessWrapper
::newFromObject(
147 $this->getBlockManager( [
148 'wgProxyList' => $proxyList
153 $this->assertSame( $expected, $blockManager->isLocallyBlockedProxy( $ip ) );
156 public static function provideIsLocallyBlockedProxy() {
158 'Proxy list is empty' => [ [], false ],
159 'Proxy list contains IP' => [ [ '1.2.3.4' ], true ],
160 'Proxy list contains IP as value' => [ [ 'test' => '1.2.3.4' ], true ],
161 'Proxy list contains range that covers IP' => [ [ '1.2.3.0/16' ], true ],
166 * @dataProvider provideIsDnsBlacklisted
167 * @covers ::isDnsBlacklisted
168 * @covers ::inDnsBlacklist
170 public function testIsDnsBlacklisted( $options, $expected ) {
171 $blockManagerConfig = [
172 'wgEnableDnsBlacklist' => true,
173 'wgDnsBlacklistUrls' => $options['blacklist'],
174 'wgProxyWhitelist' => $options['whitelist'],
177 $blockManager = $this->getMockBuilder( BlockManager
::class )
178 ->setConstructorArgs( $this->getBlockManagerConstructorArgs( $blockManagerConfig ) )
179 ->setMethods( [ 'checkHost' ] )
181 $blockManager->method( 'checkHost' )
182 ->will( $this->returnValueMap( [ [
183 $options['dnsblQuery'],
184 $options['dnsblResponse'],
189 $blockManager->isDnsBlacklisted( $options['ip'], $options['checkWhitelist'] )
193 public static function provideIsDnsBlacklisted() {
194 $dnsblFound = [ '127.0.0.2' ];
195 $dnsblNotFound = false;
197 'IP is blacklisted' => [
199 'blacklist' => [ 'dnsbl.test' ],
201 'dnsblQuery' => '1.0.0.127.dnsbl.test',
202 'dnsblResponse' => $dnsblFound,
204 'checkWhitelist' => false,
208 'IP is blacklisted; blacklist has key' => [
210 'blacklist' => [ [ 'dnsbl.test', 'key' ] ],
212 'dnsblQuery' => 'key.1.0.0.127.dnsbl.test',
213 'dnsblResponse' => $dnsblFound,
215 'checkWhitelist' => false,
219 'IP is blacklisted; blacklist is array' => [
221 'blacklist' => [ [ 'dnsbl.test' ] ],
223 'dnsblQuery' => '1.0.0.127.dnsbl.test',
224 'dnsblResponse' => $dnsblFound,
226 'checkWhitelist' => false,
230 'IP is not blacklisted' => [
232 'blacklist' => [ 'dnsbl.test' ],
234 'dnsblQuery' => '4.3.2.1.dnsbl.test',
235 'dnsblResponse' => $dnsblNotFound,
237 'checkWhitelist' => false,
241 'Blacklist is empty' => [
245 'dnsblQuery' => '1.0.0.127.dnsbl.test',
246 'dnsblResponse' => $dnsblFound,
248 'checkWhitelist' => false,
252 'IP is blacklisted and whitelisted; whitelist is not checked' => [
254 'blacklist' => [ 'dnsbl.test' ],
256 'dnsblQuery' => '1.0.0.127.dnsbl.test',
257 'dnsblResponse' => $dnsblFound,
258 'whitelist' => [ '127.0.0.1' ],
259 'checkWhitelist' => false,
263 'IP is blacklisted and whitelisted; whitelist is checked' => [
265 'blacklist' => [ 'dnsbl.test' ],
267 'dnsblQuery' => '1.0.0.127.dnsbl.test',
268 'dnsblResponse' => $dnsblFound,
269 'whitelist' => [ '127.0.0.1' ],
270 'checkWhitelist' => true,
278 * @covers ::getUniqueBlocks
280 public function testGetUniqueBlocks() {
283 $blockManager = TestingAccessWrapper
::newFromObject( $this->getBlockManager( [] ) );
285 $block = $this->getMockBuilder( DatabaseBlock
::class )
286 ->setMethods( [ 'getId' ] )
288 $block->method( 'getId' )
289 ->willReturn( $blockId );
291 $autoblock = $this->getMockBuilder( DatabaseBlock
::class )
292 ->setMethods( [ 'getParentBlockId', 'getType' ] )
294 $autoblock->method( 'getParentBlockId' )
295 ->willReturn( $blockId );
296 $autoblock->method( 'getType' )
297 ->willReturn( DatabaseBlock
::TYPE_AUTO
);
299 $blocks = [ $block, $block, $autoblock, new SystemBlock() ];
301 $this->assertSame( 2, count( $blockManager->getUniqueBlocks( $blocks ) ) );
305 * @dataProvider provideTrackBlockWithCookie
306 * @covers ::trackBlockWithCookie
308 public function testTrackBlockWithCookie( $options, $expectedVal ) {
309 $this->setMwGlobals( 'wgCookiePrefix', '' );
311 $request = new FauxRequest();
312 if ( $options['cookieSet'] ) {
313 $request->setCookie( 'BlockID', 'the value does not matter' );
316 $user = $this->getMockBuilder( User
::class )
317 ->setMethods( [ 'getBlock', 'getRequest' ] )
319 $user->method( 'getBlock' )
320 ->willReturn( $options['block'] );
321 $user->method( 'getRequest' )
322 ->willReturn( $request );
324 // Although the block cookie is set via DeferredUpdates, in command line mode updates are
325 // processed immediately
326 $blockManager = $this->getBlockManager( [
328 'wgCookieSetOnIpBlock' => true,
330 $blockManager->trackBlockWithCookie( $user );
332 /** @var FauxResponse $response */
333 $response = $request->response();
334 $this->assertCount( $expectedVal ?
1 : 0, $response->getCookies() );
335 $this->assertEquals( $expectedVal ?
: null, $response->getCookie( 'BlockID' ) );
338 public function provideTrackBlockWithCookie() {
341 'Block cookie is already set; there is a trackable block' => [
344 'block' => $this->getTrackableBlock( $blockId ),
348 'Block cookie is already set; there is no block' => [
355 'Block cookie is not yet set; there is no block' => [
357 'cookieSet' => false,
362 'Block cookie is not yet set; there is a trackable block' => [
364 'cookieSet' => false,
365 'block' => $this->getTrackableBlock( $blockId ),
369 'Block cookie is not yet set; there is a composite block with a trackable block' => [
371 'cookieSet' => false,
372 'block' => new CompositeBlock( [
373 'originalBlocks' => [
375 $this->getTrackableBlock( $blockId ),
381 'Block cookie is not yet set; there is a composite block but no trackable block' => [
383 'cookieSet' => false,
384 'block' => new CompositeBlock( [
385 'originalBlocks' => [
396 private function getTrackableBlock( $blockId ) {
397 $block = $this->getMockBuilder( DatabaseBlock
::class )
398 ->setMethods( [ 'getType', 'getId' ] )
400 $block->method( 'getType' )
401 ->willReturn( DatabaseBlock
::TYPE_IP
);
402 $block->method( 'getId' )
403 ->willReturn( $blockId );
408 * @dataProvider provideSetBlockCookie
409 * @covers ::setBlockCookie
411 public function testSetBlockCookie( $expiryDelta, $expectedExpiryDelta ) {
412 $this->setMwGlobals( [
413 'wgCookiePrefix' => '',
416 $request = new FauxRequest();
417 $response = $request->response();
419 $blockManager = $this->getBlockManager( [
421 'wgCookieSetOnIpBlock' => true,
424 $now = wfTimestamp();
426 $block = new DatabaseBlock( [
427 'expiry' => $expiryDelta === '' ?
'' : $now +
$expiryDelta
429 $blockManager->setBlockCookie( $block, $response );
430 $cookies = $response->getCookies();
433 $now +
$expectedExpiryDelta,
434 $cookies['BlockID']['expire'],
436 60 // Allow actual to be up to 60 seconds later than expected
440 public static function provideSetBlockCookie() {
441 // Maximum length of a block cookie, defined in BlockManager::setBlockCookie
442 $maxExpiryDelta = ( 24 * 60 * 60 );
444 $longExpiryDelta = ( 48 * 60 * 60 );
445 $shortExpiryDelta = ( 12 * 60 * 60 );
448 'Block has indefinite expiry' => [
452 'Block expiry is later than maximum cookie block expiry' => [
456 'Block expiry is sooner than maximum cookie block expiry' => [
464 * @covers ::shouldTrackBlockWithCookie
466 public function testShouldTrackBlockWithCookieSystemBlock() {
467 $blockManager = TestingAccessWrapper
::newFromObject( $this->getBlockManager( [] ) );
468 $this->assertFalse( $blockManager->shouldTrackBlockWithCookie(
475 * @dataProvider provideShouldTrackBlockWithCookie
476 * @covers ::shouldTrackBlockWithCookie
478 public function testShouldTrackBlockWithCookie( $options, $expected ) {
479 $block = $this->getMockBuilder( DatabaseBlock
::class )
480 ->setMethods( [ 'getType', 'isAutoblocking' ] )
482 $block->method( 'getType' )
483 ->willReturn( $options['type'] );
484 if ( isset( $options['autoblocking'] ) ) {
485 $block->method( 'isAutoblocking' )
486 ->willReturn( $options['autoblocking'] );
489 $blockManager = TestingAccessWrapper
::newFromObject(
490 $this->getBlockManager( $options['blockManagerConfig'] )
495 $blockManager->shouldTrackBlockWithCookie( $block, $options['isAnon'] )
499 public static function provideShouldTrackBlockWithCookie() {
501 'IP block, anonymous user, IP block cookies enabled' => [
503 'type' => DatabaseBlock
::TYPE_IP
,
505 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
509 'IP range block, anonymous user, IP block cookies enabled' => [
511 'type' => DatabaseBlock
::TYPE_RANGE
,
513 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
517 'IP block, anonymous user, IP block cookies disabled' => [
519 'type' => DatabaseBlock
::TYPE_IP
,
521 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => false ],
525 'IP block, logged in user, IP block cookies enabled' => [
527 'type' => DatabaseBlock
::TYPE_IP
,
529 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
533 'User block, anonymous, autoblock cookies enabled, block is autoblocking' => [
535 'type' => DatabaseBlock
::TYPE_USER
,
537 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
538 'autoblocking' => true,
542 'User block, logged in, autoblock cookies enabled, block is autoblocking' => [
544 'type' => DatabaseBlock
::TYPE_USER
,
546 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
547 'autoblocking' => true,
551 'User block, logged in, autoblock cookies disabled, block is autoblocking' => [
553 'type' => DatabaseBlock
::TYPE_USER
,
555 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => false ],
556 'autoblocking' => true,
560 'User block, logged in, autoblock cookies enabled, block is not autoblocking' => [
562 'type' => DatabaseBlock
::TYPE_USER
,
564 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
565 'autoblocking' => false,
569 'Block type is autoblock' => [
571 'type' => DatabaseBlock
::TYPE_AUTO
,
573 'blockManagerConfig' => [],
581 * @covers ::clearBlockCookie
583 public function testClearBlockCookie() {
584 $this->setMwGlobals( [
585 'wgCookiePrefix' => '',
588 $request = new FauxRequest();
589 $response = $request->response();
590 $response->setCookie( 'BlockID', '100' );
591 $this->assertSame( '100', $response->getCookie( 'BlockID' ) );
593 BlockManager
::clearBlockCookie( $response );
594 $this->assertSame( '', $response->getCookie( 'BlockID' ) );
598 * @dataProvider provideGetIdFromCookieValue
599 * @covers ::getIdFromCookieValue
601 public function testGetIdFromCookieValue( $options, $expected ) {
602 $blockManager = $this->getBlockManager( [
603 'wgSecretKey' => $options['secretKey']
607 $blockManager->getIdFromCookieValue( $options['cookieValue'] )
611 public static function provideGetIdFromCookieValue() {
614 $hmac = MWCryptHash
::hmac( $blockId, $secretKey, false );
616 'No secret key is set' => [
619 'cookieValue' => $blockId,
620 'calculatedHmac' => MWCryptHash
::hmac( $blockId, '', false ),
624 'Secret key is set and stored hmac is correct' => [
626 'secretKey' => $secretKey,
627 'cookieValue' => $blockId . '!' . $hmac,
628 'calculatedHmac' => $hmac,
632 'Secret key is set and stored hmac is incorrect' => [
634 'secretKey' => $secretKey,
635 'cookieValue' => $blockId . '!xyz',
636 'calculatedHmac' => $hmac,
644 * @dataProvider provideGetCookieValue
645 * @covers ::getCookieValue
647 public function testGetCookieValue( $options, $expected ) {
648 $blockManager = $this->getBlockManager( [
649 'wgSecretKey' => $options['secretKey']
652 $block = $this->getMockBuilder( DatabaseBlock
::class )
653 ->setMethods( [ 'getId' ] )
655 $block->method( 'getId' )
656 ->willReturn( $options['blockId'] );
660 $blockManager->getCookieValue( $block )
664 public static function provideGetCookieValue() {
667 'Secret key not set' => [
670 'blockId' => $blockId,
671 'hmac' => MWCryptHash
::hmac( $blockId, '', false ),
675 'Secret key set' => [
677 'secretKey' => '123',
678 'blockId' => $blockId,
679 'hmac' => MWCryptHash
::hmac( $blockId, '123', false ),
681 $blockId . '!' . MWCryptHash
::hmac( $blockId, '123', false ) ],
688 public function testAllServiceOptionsUsed() {
689 $this->assertAllServiceOptionsUsed( [ 'ApplyIpBlocksToXff', 'SoftBlockRanges' ] );