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
;
13 * @coversDefaultClass \MediaWiki\Block\BlockManager
15 class BlockManagerTest
extends MediaWikiTestCase
{
16 use TestAllServiceOptionsUsed
;
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 );
52 new LoggedServiceOptions(
53 self
::$serviceOptionsAccessLog,
54 BlockManager
::$constructorOptions,
55 MediaWikiServices
::getInstance()->getMainConfig()
57 MediaWikiServices
::getInstance()->getPermissionManager()
62 * @dataProvider provideGetBlockFromCookieValue
63 * @covers ::getBlockFromCookieValue
64 * @covers ::shouldApplyCookieBlock
66 public function testGetBlockFromCookieValue( $options, $expected ) {
67 $blockManager = TestingAccessWrapper
::newFromObject(
68 $this->getBlockManager( [
69 'wgCookieSetOnAutoblock' => true,
70 'wgCookieSetOnIpBlock' => true,
74 $block = new DatabaseBlock( array_merge( [
75 'address' => $options['target'] ?
: $this->user
,
76 'by' => $this->sysopId
,
77 ], $options['blockOptions'] ) );
80 $user = $options['loggedIn'] ?
$this->user
: new User();
81 $user->getRequest()->setCookie( 'BlockID', $block->getCookieValue() );
83 $this->assertSame( $expected, (bool)$blockManager->getBlockFromCookieValue(
91 public static function provideGetBlockFromCookieValue() {
93 'Autoblocking user block' => [
98 'enableAutoblock' => true
103 'Non-autoblocking user block' => [
107 'blockOptions' => [],
111 'IP block for anonymous user' => [
113 'target' => '127.0.0.1',
115 'blockOptions' => [],
119 'IP block for logged in user' => [
121 'target' => '127.0.0.1',
123 'blockOptions' => [],
127 'IP range block for anonymous user' => [
129 'target' => '127.0.0.0/8',
131 'blockOptions' => [],
139 * @dataProvider provideIsLocallyBlockedProxy
140 * @covers ::isLocallyBlockedProxy
142 public function testIsLocallyBlockedProxy( $proxyList, $expected ) {
143 $blockManager = TestingAccessWrapper
::newFromObject(
144 $this->getBlockManager( [
145 'wgProxyList' => $proxyList
150 $this->assertSame( $expected, $blockManager->isLocallyBlockedProxy( $ip ) );
153 public static function provideIsLocallyBlockedProxy() {
155 'Proxy list is empty' => [ [], false ],
156 'Proxy list contains IP' => [ [ '1.2.3.4' ], true ],
157 'Proxy list contains IP as value' => [ [ 'test' => '1.2.3.4' ], true ],
158 'Proxy list contains range that covers IP' => [ [ '1.2.3.0/16' ], true ],
163 * @dataProvider provideIsDnsBlacklisted
164 * @covers ::isDnsBlacklisted
165 * @covers ::inDnsBlacklist
167 public function testIsDnsBlacklisted( $options, $expected ) {
168 $blockManagerConfig = [
169 'wgEnableDnsBlacklist' => true,
170 'wgDnsBlacklistUrls' => $options['blacklist'],
171 'wgProxyWhitelist' => $options['whitelist'],
174 $blockManager = $this->getMockBuilder( BlockManager
::class )
175 ->setConstructorArgs( $this->getBlockManagerConstructorArgs( $blockManagerConfig ) )
176 ->setMethods( [ 'checkHost' ] )
178 $blockManager->method( 'checkHost' )
179 ->will( $this->returnValueMap( [ [
180 $options['dnsblQuery'],
181 $options['dnsblResponse'],
186 $blockManager->isDnsBlacklisted( $options['ip'], $options['checkWhitelist'] )
190 public static function provideIsDnsBlacklisted() {
191 $dnsblFound = [ '127.0.0.2' ];
192 $dnsblNotFound = false;
194 'IP is blacklisted' => [
196 'blacklist' => [ 'dnsbl.test' ],
198 'dnsblQuery' => '1.0.0.127.dnsbl.test',
199 'dnsblResponse' => $dnsblFound,
201 'checkWhitelist' => false,
205 'IP is blacklisted; blacklist has key' => [
207 'blacklist' => [ [ 'dnsbl.test', 'key' ] ],
209 'dnsblQuery' => 'key.1.0.0.127.dnsbl.test',
210 'dnsblResponse' => $dnsblFound,
212 'checkWhitelist' => false,
216 'IP is blacklisted; blacklist is array' => [
218 'blacklist' => [ [ 'dnsbl.test' ] ],
220 'dnsblQuery' => '1.0.0.127.dnsbl.test',
221 'dnsblResponse' => $dnsblFound,
223 'checkWhitelist' => false,
227 'IP is not blacklisted' => [
229 'blacklist' => [ 'dnsbl.test' ],
231 'dnsblQuery' => '4.3.2.1.dnsbl.test',
232 'dnsblResponse' => $dnsblNotFound,
234 'checkWhitelist' => false,
238 'Blacklist is empty' => [
242 'dnsblQuery' => '1.0.0.127.dnsbl.test',
243 'dnsblResponse' => $dnsblFound,
245 'checkWhitelist' => false,
249 'IP is blacklisted and whitelisted; whitelist is not checked' => [
251 'blacklist' => [ 'dnsbl.test' ],
253 'dnsblQuery' => '1.0.0.127.dnsbl.test',
254 'dnsblResponse' => $dnsblFound,
255 'whitelist' => [ '127.0.0.1' ],
256 'checkWhitelist' => false,
260 'IP is blacklisted and whitelisted; whitelist is checked' => [
262 'blacklist' => [ 'dnsbl.test' ],
264 'dnsblQuery' => '1.0.0.127.dnsbl.test',
265 'dnsblResponse' => $dnsblFound,
266 'whitelist' => [ '127.0.0.1' ],
267 'checkWhitelist' => true,
275 * @covers ::getUniqueBlocks
277 public function testGetUniqueBlocks() {
280 $blockManager = TestingAccessWrapper
::newFromObject( $this->getBlockManager( [] ) );
282 $block = $this->getMockBuilder( DatabaseBlock
::class )
283 ->setMethods( [ 'getId' ] )
285 $block->method( 'getId' )
286 ->willReturn( $blockId );
288 $autoblock = $this->getMockBuilder( DatabaseBlock
::class )
289 ->setMethods( [ 'getParentBlockId', 'getType' ] )
291 $autoblock->method( 'getParentBlockId' )
292 ->willReturn( $blockId );
293 $autoblock->method( 'getType' )
294 ->willReturn( DatabaseBlock
::TYPE_AUTO
);
296 $blocks = [ $block, $block, $autoblock, new SystemBlock() ];
298 $this->assertSame( 2, count( $blockManager->getUniqueBlocks( $blocks ) ) );
302 * @dataProvider provideTrackBlockWithCookie
303 * @covers ::trackBlockWithCookie
305 public function testTrackBlockWithCookie( $options, $expectedVal ) {
306 $this->setMwGlobals( 'wgCookiePrefix', '' );
308 $request = new FauxRequest();
309 if ( $options['cookieSet'] ) {
310 $request->setCookie( 'BlockID', 'the value does not matter' );
313 $user = $this->getMockBuilder( User
::class )
314 ->setMethods( [ 'getBlock', 'getRequest' ] )
316 $user->method( 'getBlock' )
317 ->willReturn( $options['block'] );
318 $user->method( 'getRequest' )
319 ->willReturn( $request );
321 // Although the block cookie is set via DeferredUpdates, in command line mode updates are
322 // processed immediately
323 $blockManager = $this->getBlockManager( [
325 'wgCookieSetOnIpBlock' => true,
327 $blockManager->trackBlockWithCookie( $user );
329 /** @var FauxResponse $response */
330 $response = $request->response();
331 $this->assertCount( $expectedVal ?
1 : 0, $response->getCookies() );
332 $this->assertEquals( $expectedVal ?
: null, $response->getCookie( 'BlockID' ) );
335 public function provideTrackBlockWithCookie() {
338 'Block cookie is already set; there is a trackable block' => [
341 'block' => $this->getTrackableBlock( $blockId ),
345 'Block cookie is already set; there is no block' => [
352 'Block cookie is not yet set; there is no block' => [
354 'cookieSet' => false,
359 'Block cookie is not yet set; there is a trackable block' => [
361 'cookieSet' => false,
362 'block' => $this->getTrackableBlock( $blockId ),
366 'Block cookie is not yet set; there is a composite block with a trackable block' => [
368 'cookieSet' => false,
369 'block' => new CompositeBlock( [
370 'originalBlocks' => [
372 $this->getTrackableBlock( $blockId ),
378 'Block cookie is not yet set; there is a composite block but no trackable block' => [
380 'cookieSet' => false,
381 'block' => new CompositeBlock( [
382 'originalBlocks' => [
393 private function getTrackableBlock( $blockId ) {
394 $block = $this->getMockBuilder( DatabaseBlock
::class )
395 ->setMethods( [ 'getType', 'getId' ] )
397 $block->method( 'getType' )
398 ->willReturn( DatabaseBlock
::TYPE_IP
);
399 $block->method( 'getId' )
400 ->willReturn( $blockId );
405 * @dataProvider provideSetBlockCookie
406 * @covers ::setBlockCookie
408 public function testSetBlockCookie( $expiryDelta, $expectedExpiryDelta ) {
409 $this->setMwGlobals( [
410 'wgCookiePrefix' => '',
413 $request = new FauxRequest();
414 $response = $request->response();
416 $blockManager = $this->getBlockManager( [
418 'wgCookieSetOnIpBlock' => true,
421 $now = wfTimestamp();
423 $block = new DatabaseBlock( [
424 'expiry' => $expiryDelta === '' ?
'' : $now +
$expiryDelta
426 $blockManager->setBlockCookie( $block, $response );
427 $cookies = $response->getCookies();
430 $now +
$expectedExpiryDelta,
431 $cookies['BlockID']['expire'],
433 60 // Allow actual to be up to 60 seconds later than expected
437 public static function provideSetBlockCookie() {
438 // Maximum length of a block cookie, defined in BlockManager::setBlockCookie
439 $maxExpiryDelta = ( 24 * 60 * 60 );
441 $longExpiryDelta = ( 48 * 60 * 60 );
442 $shortExpiryDelta = ( 12 * 60 * 60 );
445 'Block has indefinite expiry' => [
449 'Block expiry is later than maximum cookie block expiry' => [
453 'Block expiry is sooner than maximum cookie block expiry' => [
461 * @covers ::shouldTrackBlockWithCookie
463 public function testShouldTrackBlockWithCookieSystemBlock() {
464 $blockManager = TestingAccessWrapper
::newFromObject( $this->getBlockManager( [] ) );
465 $this->assertFalse( $blockManager->shouldTrackBlockWithCookie(
472 * @dataProvider provideShouldTrackBlockWithCookie
473 * @covers ::shouldTrackBlockWithCookie
475 public function testShouldTrackBlockWithCookie( $options, $expected ) {
476 $block = $this->getMockBuilder( DatabaseBlock
::class )
477 ->setMethods( [ 'getType', 'isAutoblocking' ] )
479 $block->method( 'getType' )
480 ->willReturn( $options['type'] );
481 if ( isset( $options['autoblocking'] ) ) {
482 $block->method( 'isAutoblocking' )
483 ->willReturn( $options['autoblocking'] );
486 $blockManager = TestingAccessWrapper
::newFromObject(
487 $this->getBlockManager( $options['blockManagerConfig'] )
492 $blockManager->shouldTrackBlockWithCookie( $block, $options['isAnon'] )
496 public static function provideShouldTrackBlockWithCookie() {
498 'IP block, anonymous user, IP block cookies enabled' => [
500 'type' => DatabaseBlock
::TYPE_IP
,
502 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
506 'IP range block, anonymous user, IP block cookies enabled' => [
508 'type' => DatabaseBlock
::TYPE_RANGE
,
510 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
514 'IP block, anonymous user, IP block cookies disabled' => [
516 'type' => DatabaseBlock
::TYPE_IP
,
518 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => false ],
522 'IP block, logged in user, IP block cookies enabled' => [
524 'type' => DatabaseBlock
::TYPE_IP
,
526 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
530 'User block, anonymous, autoblock cookies enabled, block is autoblocking' => [
532 'type' => DatabaseBlock
::TYPE_USER
,
534 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
535 'autoblocking' => true,
539 'User block, logged in, autoblock cookies enabled, block is autoblocking' => [
541 'type' => DatabaseBlock
::TYPE_USER
,
543 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
544 'autoblocking' => true,
548 'User block, logged in, autoblock cookies disabled, block is autoblocking' => [
550 'type' => DatabaseBlock
::TYPE_USER
,
552 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => false ],
553 'autoblocking' => true,
557 'User block, logged in, autoblock cookies enabled, block is not autoblocking' => [
559 'type' => DatabaseBlock
::TYPE_USER
,
561 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
562 'autoblocking' => false,
566 'Block type is autoblock' => [
568 'type' => DatabaseBlock
::TYPE_AUTO
,
570 'blockManagerConfig' => [],
578 * @covers ::clearBlockCookie
580 public function testClearBlockCookie() {
581 $this->setMwGlobals( [
582 'wgCookiePrefix' => '',
585 $request = new FauxRequest();
586 $response = $request->response();
587 $response->setCookie( 'BlockID', '100' );
588 $this->assertSame( '100', $response->getCookie( 'BlockID' ) );
590 BlockManager
::clearBlockCookie( $response );
591 $this->assertSame( '', $response->getCookie( 'BlockID' ) );
595 * @dataProvider provideGetIdFromCookieValue
596 * @covers ::getIdFromCookieValue
598 public function testGetIdFromCookieValue( $options, $expected ) {
599 $blockManager = $this->getBlockManager( [
600 'wgSecretKey' => $options['secretKey']
604 $blockManager->getIdFromCookieValue( $options['cookieValue'] )
608 public static function provideGetIdFromCookieValue() {
611 $hmac = MWCryptHash
::hmac( $blockId, $secretKey, false );
613 'No secret key is set' => [
616 'cookieValue' => $blockId,
617 'calculatedHmac' => MWCryptHash
::hmac( $blockId, '', false ),
621 'Secret key is set and stored hmac is correct' => [
623 'secretKey' => $secretKey,
624 'cookieValue' => $blockId . '!' . $hmac,
625 'calculatedHmac' => $hmac,
629 'Secret key is set and stored hmac is incorrect' => [
631 'secretKey' => $secretKey,
632 'cookieValue' => $blockId . '!xyz',
633 'calculatedHmac' => $hmac,
641 * @dataProvider provideGetCookieValue
642 * @covers ::getCookieValue
644 public function testGetCookieValue( $options, $expected ) {
645 $blockManager = $this->getBlockManager( [
646 'wgSecretKey' => $options['secretKey']
649 $block = $this->getMockBuilder( DatabaseBlock
::class )
650 ->setMethods( [ 'getId' ] )
652 $block->method( 'getId' )
653 ->willReturn( $options['blockId'] );
657 $blockManager->getCookieValue( $block )
661 public static function provideGetCookieValue() {
664 'Secret key not set' => [
667 'blockId' => $blockId,
668 'hmac' => MWCryptHash
::hmac( $blockId, '', false ),
672 'Secret key set' => [
674 'secretKey' => '123',
675 'blockId' => $blockId,
676 'hmac' => MWCryptHash
::hmac( $blockId, '123', false ),
678 $blockId . '!' . MWCryptHash
::hmac( $blockId, '123', false ) ],
685 public function testAllServiceOptionsUsed() {
686 $this->assertAllServiceOptionsUsed( [ 'ApplyIpBlocksToXff', 'SoftBlockRanges' ] );