Services: Convert BlockManager's static to a const now HHVM is gone
[lhc/web/wiklou.git] / tests / phpunit / includes / block / BlockManagerTest.php
1 <?php
2
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;
10
11 /**
12 * @group Blocking
13 * @group Database
14 * @coversDefaultClass \MediaWiki\Block\BlockManager
15 */
16 class BlockManagerTest extends MediaWikiTestCase {
17 use TestAllServiceOptionsUsed;
18
19 /** @var User */
20 protected $user;
21
22 /** @var int */
23 protected $sysopId;
24
25 protected function setUp() {
26 parent::setUp();
27
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,
36 'wgProxyList' => [],
37 'wgProxyWhitelist' => [],
38 'wgSecretKey' => false,
39 'wgSoftBlockRanges' => [],
40 ];
41 }
42
43 private function getBlockManager( $overrideConfig ) {
44 return new BlockManager(
45 ...$this->getBlockManagerConstructorArgs( $overrideConfig )
46 );
47 }
48
49 private function getBlockManagerConstructorArgs( $overrideConfig ) {
50 $blockManagerConfig = array_merge( $this->blockManagerConfig, $overrideConfig );
51 $this->setMwGlobals( $blockManagerConfig );
52 $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
53 return [
54 new LoggedServiceOptions(
55 self::$serviceOptionsAccessLog,
56 BlockManager::CONSTRUCTOR_OPTIONS,
57 MediaWikiServices::getInstance()->getMainConfig()
58 ),
59 MediaWikiServices::getInstance()->getPermissionManager(),
60 $logger
61 ];
62 }
63
64 /**
65 * @dataProvider provideGetBlockFromCookieValue
66 * @covers ::getBlockFromCookieValue
67 * @covers ::shouldApplyCookieBlock
68 */
69 public function testGetBlockFromCookieValue( $options, $expected ) {
70 $blockManager = TestingAccessWrapper::newFromObject(
71 $this->getBlockManager( [
72 'wgCookieSetOnAutoblock' => true,
73 'wgCookieSetOnIpBlock' => true,
74 ] )
75 );
76
77 $block = new DatabaseBlock( array_merge( [
78 'address' => $options['target'] ?: $this->user,
79 'by' => $this->sysopId,
80 ], $options['blockOptions'] ) );
81 $block->insert();
82
83 $user = $options['loggedIn'] ? $this->user : new User();
84 $user->getRequest()->setCookie( 'BlockID', $block->getCookieValue() );
85
86 $this->assertSame( $expected, (bool)$blockManager->getBlockFromCookieValue(
87 $user,
88 $user->getRequest()
89 ) );
90
91 $block->delete();
92 }
93
94 public static function provideGetBlockFromCookieValue() {
95 return [
96 'Autoblocking user block' => [
97 [
98 'target' => '',
99 'loggedIn' => true,
100 'blockOptions' => [
101 'enableAutoblock' => true
102 ],
103 ],
104 true,
105 ],
106 'Non-autoblocking user block' => [
107 [
108 'target' => '',
109 'loggedIn' => true,
110 'blockOptions' => [],
111 ],
112 false,
113 ],
114 'IP block for anonymous user' => [
115 [
116 'target' => '127.0.0.1',
117 'loggedIn' => false,
118 'blockOptions' => [],
119 ],
120 true,
121 ],
122 'IP block for logged in user' => [
123 [
124 'target' => '127.0.0.1',
125 'loggedIn' => true,
126 'blockOptions' => [],
127 ],
128 false,
129 ],
130 'IP range block for anonymous user' => [
131 [
132 'target' => '127.0.0.0/8',
133 'loggedIn' => false,
134 'blockOptions' => [],
135 ],
136 true,
137 ],
138 ];
139 }
140
141 /**
142 * @dataProvider provideIsLocallyBlockedProxy
143 * @covers ::isLocallyBlockedProxy
144 */
145 public function testIsLocallyBlockedProxy( $proxyList, $expected ) {
146 $blockManager = TestingAccessWrapper::newFromObject(
147 $this->getBlockManager( [
148 'wgProxyList' => $proxyList
149 ] )
150 );
151
152 $ip = '1.2.3.4';
153 $this->assertSame( $expected, $blockManager->isLocallyBlockedProxy( $ip ) );
154 }
155
156 public static function provideIsLocallyBlockedProxy() {
157 return [
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 ],
162 ];
163 }
164
165 /**
166 * @dataProvider provideIsDnsBlacklisted
167 * @covers ::isDnsBlacklisted
168 * @covers ::inDnsBlacklist
169 */
170 public function testIsDnsBlacklisted( $options, $expected ) {
171 $blockManagerConfig = [
172 'wgEnableDnsBlacklist' => true,
173 'wgDnsBlacklistUrls' => $options['blacklist'],
174 'wgProxyWhitelist' => $options['whitelist'],
175 ];
176
177 $blockManager = $this->getMockBuilder( BlockManager::class )
178 ->setConstructorArgs( $this->getBlockManagerConstructorArgs( $blockManagerConfig ) )
179 ->setMethods( [ 'checkHost' ] )
180 ->getMock();
181 $blockManager->method( 'checkHost' )
182 ->will( $this->returnValueMap( [ [
183 $options['dnsblQuery'],
184 $options['dnsblResponse'],
185 ] ] ) );
186
187 $this->assertSame(
188 $expected,
189 $blockManager->isDnsBlacklisted( $options['ip'], $options['checkWhitelist'] )
190 );
191 }
192
193 public static function provideIsDnsBlacklisted() {
194 $dnsblFound = [ '127.0.0.2' ];
195 $dnsblNotFound = false;
196 return [
197 'IP is blacklisted' => [
198 [
199 'blacklist' => [ 'dnsbl.test' ],
200 'ip' => '127.0.0.1',
201 'dnsblQuery' => '1.0.0.127.dnsbl.test',
202 'dnsblResponse' => $dnsblFound,
203 'whitelist' => [],
204 'checkWhitelist' => false,
205 ],
206 true,
207 ],
208 'IP is blacklisted; blacklist has key' => [
209 [
210 'blacklist' => [ [ 'dnsbl.test', 'key' ] ],
211 'ip' => '127.0.0.1',
212 'dnsblQuery' => 'key.1.0.0.127.dnsbl.test',
213 'dnsblResponse' => $dnsblFound,
214 'whitelist' => [],
215 'checkWhitelist' => false,
216 ],
217 true,
218 ],
219 'IP is blacklisted; blacklist is array' => [
220 [
221 'blacklist' => [ [ 'dnsbl.test' ] ],
222 'ip' => '127.0.0.1',
223 'dnsblQuery' => '1.0.0.127.dnsbl.test',
224 'dnsblResponse' => $dnsblFound,
225 'whitelist' => [],
226 'checkWhitelist' => false,
227 ],
228 true,
229 ],
230 'IP is not blacklisted' => [
231 [
232 'blacklist' => [ 'dnsbl.test' ],
233 'ip' => '1.2.3.4',
234 'dnsblQuery' => '4.3.2.1.dnsbl.test',
235 'dnsblResponse' => $dnsblNotFound,
236 'whitelist' => [],
237 'checkWhitelist' => false,
238 ],
239 false,
240 ],
241 'Blacklist is empty' => [
242 [
243 'blacklist' => [],
244 'ip' => '127.0.0.1',
245 'dnsblQuery' => '1.0.0.127.dnsbl.test',
246 'dnsblResponse' => $dnsblFound,
247 'whitelist' => [],
248 'checkWhitelist' => false,
249 ],
250 false,
251 ],
252 'IP is blacklisted and whitelisted; whitelist is not checked' => [
253 [
254 'blacklist' => [ 'dnsbl.test' ],
255 'ip' => '127.0.0.1',
256 'dnsblQuery' => '1.0.0.127.dnsbl.test',
257 'dnsblResponse' => $dnsblFound,
258 'whitelist' => [ '127.0.0.1' ],
259 'checkWhitelist' => false,
260 ],
261 true,
262 ],
263 'IP is blacklisted and whitelisted; whitelist is checked' => [
264 [
265 'blacklist' => [ 'dnsbl.test' ],
266 'ip' => '127.0.0.1',
267 'dnsblQuery' => '1.0.0.127.dnsbl.test',
268 'dnsblResponse' => $dnsblFound,
269 'whitelist' => [ '127.0.0.1' ],
270 'checkWhitelist' => true,
271 ],
272 false,
273 ],
274 ];
275 }
276
277 /**
278 * @covers ::getUniqueBlocks
279 */
280 public function testGetUniqueBlocks() {
281 $blockId = 100;
282
283 $blockManager = TestingAccessWrapper::newFromObject( $this->getBlockManager( [] ) );
284
285 $block = $this->getMockBuilder( DatabaseBlock::class )
286 ->setMethods( [ 'getId' ] )
287 ->getMock();
288 $block->method( 'getId' )
289 ->willReturn( $blockId );
290
291 $autoblock = $this->getMockBuilder( DatabaseBlock::class )
292 ->setMethods( [ 'getParentBlockId', 'getType' ] )
293 ->getMock();
294 $autoblock->method( 'getParentBlockId' )
295 ->willReturn( $blockId );
296 $autoblock->method( 'getType' )
297 ->willReturn( DatabaseBlock::TYPE_AUTO );
298
299 $blocks = [ $block, $block, $autoblock, new SystemBlock() ];
300
301 $this->assertSame( 2, count( $blockManager->getUniqueBlocks( $blocks ) ) );
302 }
303
304 /**
305 * @dataProvider provideTrackBlockWithCookie
306 * @covers ::trackBlockWithCookie
307 */
308 public function testTrackBlockWithCookie( $options, $expectedVal ) {
309 $this->setMwGlobals( 'wgCookiePrefix', '' );
310
311 $request = new FauxRequest();
312 if ( $options['cookieSet'] ) {
313 $request->setCookie( 'BlockID', 'the value does not matter' );
314 }
315
316 $user = $this->getMockBuilder( User::class )
317 ->setMethods( [ 'getBlock', 'getRequest' ] )
318 ->getMock();
319 $user->method( 'getBlock' )
320 ->willReturn( $options['block'] );
321 $user->method( 'getRequest' )
322 ->willReturn( $request );
323
324 // Although the block cookie is set via DeferredUpdates, in command line mode updates are
325 // processed immediately
326 $blockManager = $this->getBlockManager( [
327 'wgSecretKey' => '',
328 'wgCookieSetOnIpBlock' => true,
329 ] );
330 $blockManager->trackBlockWithCookie( $user );
331
332 /** @var FauxResponse $response */
333 $response = $request->response();
334 $this->assertCount( $expectedVal ? 1 : 0, $response->getCookies() );
335 $this->assertEquals( $expectedVal ?: null, $response->getCookie( 'BlockID' ) );
336 }
337
338 public function provideTrackBlockWithCookie() {
339 $blockId = 123;
340 return [
341 'Block cookie is already set; there is a trackable block' => [
342 [
343 'cookieSet' => true,
344 'block' => $this->getTrackableBlock( $blockId ),
345 ],
346 null,
347 ],
348 'Block cookie is already set; there is no block' => [
349 [
350 'cookieSet' => true,
351 'block' => null,
352 ],
353 null,
354 ],
355 'Block cookie is not yet set; there is no block' => [
356 [
357 'cookieSet' => false,
358 'block' => null,
359 ],
360 null,
361 ],
362 'Block cookie is not yet set; there is a trackable block' => [
363 [
364 'cookieSet' => false,
365 'block' => $this->getTrackableBlock( $blockId ),
366 ],
367 $blockId,
368 ],
369 'Block cookie is not yet set; there is a composite block with a trackable block' => [
370 [
371 'cookieSet' => false,
372 'block' => new CompositeBlock( [
373 'originalBlocks' => [
374 new SystemBlock(),
375 $this->getTrackableBlock( $blockId ),
376 ]
377 ] ),
378 ],
379 $blockId,
380 ],
381 'Block cookie is not yet set; there is a composite block but no trackable block' => [
382 [
383 'cookieSet' => false,
384 'block' => new CompositeBlock( [
385 'originalBlocks' => [
386 new SystemBlock(),
387 new SystemBlock(),
388 ]
389 ] ),
390 ],
391 null,
392 ],
393 ];
394 }
395
396 private function getTrackableBlock( $blockId ) {
397 $block = $this->getMockBuilder( DatabaseBlock::class )
398 ->setMethods( [ 'getType', 'getId' ] )
399 ->getMock();
400 $block->method( 'getType' )
401 ->willReturn( DatabaseBlock::TYPE_IP );
402 $block->method( 'getId' )
403 ->willReturn( $blockId );
404 return $block;
405 }
406
407 /**
408 * @dataProvider provideSetBlockCookie
409 * @covers ::setBlockCookie
410 */
411 public function testSetBlockCookie( $expiryDelta, $expectedExpiryDelta ) {
412 $this->setMwGlobals( [
413 'wgCookiePrefix' => '',
414 ] );
415
416 $request = new FauxRequest();
417 $response = $request->response();
418
419 $blockManager = $this->getBlockManager( [
420 'wgSecretKey' => '',
421 'wgCookieSetOnIpBlock' => true,
422 ] );
423
424 $now = wfTimestamp();
425
426 $block = new DatabaseBlock( [
427 'expiry' => $expiryDelta === '' ? '' : $now + $expiryDelta
428 ] );
429 $blockManager->setBlockCookie( $block, $response );
430 $cookies = $response->getCookies();
431
432 $this->assertEquals(
433 $now + $expectedExpiryDelta,
434 $cookies['BlockID']['expire'],
435 '',
436 60 // Allow actual to be up to 60 seconds later than expected
437 );
438 }
439
440 public static function provideSetBlockCookie() {
441 // Maximum length of a block cookie, defined in BlockManager::setBlockCookie
442 $maxExpiryDelta = ( 24 * 60 * 60 );
443
444 $longExpiryDelta = ( 48 * 60 * 60 );
445 $shortExpiryDelta = ( 12 * 60 * 60 );
446
447 return [
448 'Block has indefinite expiry' => [
449 '',
450 $maxExpiryDelta,
451 ],
452 'Block expiry is later than maximum cookie block expiry' => [
453 $longExpiryDelta,
454 $maxExpiryDelta,
455 ],
456 'Block expiry is sooner than maximum cookie block expiry' => [
457 $shortExpiryDelta,
458 $shortExpiryDelta,
459 ],
460 ];
461 }
462
463 /**
464 * @covers ::shouldTrackBlockWithCookie
465 */
466 public function testShouldTrackBlockWithCookieSystemBlock() {
467 $blockManager = TestingAccessWrapper::newFromObject( $this->getBlockManager( [] ) );
468 $this->assertFalse( $blockManager->shouldTrackBlockWithCookie(
469 new SystemBlock(),
470 true
471 ) );
472 }
473
474 /**
475 * @dataProvider provideShouldTrackBlockWithCookie
476 * @covers ::shouldTrackBlockWithCookie
477 */
478 public function testShouldTrackBlockWithCookie( $options, $expected ) {
479 $block = $this->getMockBuilder( DatabaseBlock::class )
480 ->setMethods( [ 'getType', 'isAutoblocking' ] )
481 ->getMock();
482 $block->method( 'getType' )
483 ->willReturn( $options['type'] );
484 if ( isset( $options['autoblocking'] ) ) {
485 $block->method( 'isAutoblocking' )
486 ->willReturn( $options['autoblocking'] );
487 }
488
489 $blockManager = TestingAccessWrapper::newFromObject(
490 $this->getBlockManager( $options['blockManagerConfig'] )
491 );
492
493 $this->assertSame(
494 $expected,
495 $blockManager->shouldTrackBlockWithCookie( $block, $options['isAnon'] )
496 );
497 }
498
499 public static function provideShouldTrackBlockWithCookie() {
500 return [
501 'IP block, anonymous user, IP block cookies enabled' => [
502 [
503 'type' => DatabaseBlock::TYPE_IP,
504 'isAnon' => true,
505 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
506 ],
507 true
508 ],
509 'IP range block, anonymous user, IP block cookies enabled' => [
510 [
511 'type' => DatabaseBlock::TYPE_RANGE,
512 'isAnon' => true,
513 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
514 ],
515 true
516 ],
517 'IP block, anonymous user, IP block cookies disabled' => [
518 [
519 'type' => DatabaseBlock::TYPE_IP,
520 'isAnon' => true,
521 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => false ],
522 ],
523 false
524 ],
525 'IP block, logged in user, IP block cookies enabled' => [
526 [
527 'type' => DatabaseBlock::TYPE_IP,
528 'isAnon' => false,
529 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
530 ],
531 false
532 ],
533 'User block, anonymous, autoblock cookies enabled, block is autoblocking' => [
534 [
535 'type' => DatabaseBlock::TYPE_USER,
536 'isAnon' => true,
537 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
538 'autoblocking' => true,
539 ],
540 false
541 ],
542 'User block, logged in, autoblock cookies enabled, block is autoblocking' => [
543 [
544 'type' => DatabaseBlock::TYPE_USER,
545 'isAnon' => false,
546 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
547 'autoblocking' => true,
548 ],
549 true
550 ],
551 'User block, logged in, autoblock cookies disabled, block is autoblocking' => [
552 [
553 'type' => DatabaseBlock::TYPE_USER,
554 'isAnon' => false,
555 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => false ],
556 'autoblocking' => true,
557 ],
558 false
559 ],
560 'User block, logged in, autoblock cookies enabled, block is not autoblocking' => [
561 [
562 'type' => DatabaseBlock::TYPE_USER,
563 'isAnon' => false,
564 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
565 'autoblocking' => false,
566 ],
567 false
568 ],
569 'Block type is autoblock' => [
570 [
571 'type' => DatabaseBlock::TYPE_AUTO,
572 'isAnon' => true,
573 'blockManagerConfig' => [],
574 ],
575 false
576 ]
577 ];
578 }
579
580 /**
581 * @covers ::clearBlockCookie
582 */
583 public function testClearBlockCookie() {
584 $this->setMwGlobals( [
585 'wgCookiePrefix' => '',
586 ] );
587
588 $request = new FauxRequest();
589 $response = $request->response();
590 $response->setCookie( 'BlockID', '100' );
591 $this->assertSame( '100', $response->getCookie( 'BlockID' ) );
592
593 BlockManager::clearBlockCookie( $response );
594 $this->assertSame( '', $response->getCookie( 'BlockID' ) );
595 }
596
597 /**
598 * @dataProvider provideGetIdFromCookieValue
599 * @covers ::getIdFromCookieValue
600 */
601 public function testGetIdFromCookieValue( $options, $expected ) {
602 $blockManager = $this->getBlockManager( [
603 'wgSecretKey' => $options['secretKey']
604 ] );
605 $this->assertEquals(
606 $expected,
607 $blockManager->getIdFromCookieValue( $options['cookieValue'] )
608 );
609 }
610
611 public static function provideGetIdFromCookieValue() {
612 $blockId = 100;
613 $secretKey = '123';
614 $hmac = MWCryptHash::hmac( $blockId, $secretKey, false );
615 return [
616 'No secret key is set' => [
617 [
618 'secretKey' => '',
619 'cookieValue' => $blockId,
620 'calculatedHmac' => MWCryptHash::hmac( $blockId, '', false ),
621 ],
622 $blockId,
623 ],
624 'Secret key is set and stored hmac is correct' => [
625 [
626 'secretKey' => $secretKey,
627 'cookieValue' => $blockId . '!' . $hmac,
628 'calculatedHmac' => $hmac,
629 ],
630 $blockId,
631 ],
632 'Secret key is set and stored hmac is incorrect' => [
633 [
634 'secretKey' => $secretKey,
635 'cookieValue' => $blockId . '!xyz',
636 'calculatedHmac' => $hmac,
637 ],
638 null,
639 ],
640 ];
641 }
642
643 /**
644 * @dataProvider provideGetCookieValue
645 * @covers ::getCookieValue
646 */
647 public function testGetCookieValue( $options, $expected ) {
648 $blockManager = $this->getBlockManager( [
649 'wgSecretKey' => $options['secretKey']
650 ] );
651
652 $block = $this->getMockBuilder( DatabaseBlock::class )
653 ->setMethods( [ 'getId' ] )
654 ->getMock();
655 $block->method( 'getId' )
656 ->willReturn( $options['blockId'] );
657
658 $this->assertEquals(
659 $expected,
660 $blockManager->getCookieValue( $block )
661 );
662 }
663
664 public static function provideGetCookieValue() {
665 $blockId = 100;
666 return [
667 'Secret key not set' => [
668 [
669 'secretKey' => '',
670 'blockId' => $blockId,
671 'hmac' => MWCryptHash::hmac( $blockId, '', false ),
672 ],
673 $blockId,
674 ],
675 'Secret key set' => [
676 [
677 'secretKey' => '123',
678 'blockId' => $blockId,
679 'hmac' => MWCryptHash::hmac( $blockId, '123', false ),
680 ],
681 $blockId . '!' . MWCryptHash::hmac( $blockId, '123', false ) ],
682 ];
683 }
684
685 /**
686 * @coversNothing
687 */
688 public function testAllServiceOptionsUsed() {
689 $this->assertAllServiceOptionsUsed( [ 'ApplyIpBlocksToXff', 'SoftBlockRanges' ] );
690 }
691 }