1<?php 2 3use MediaWiki\Block\BlockRestrictionStore; 4use MediaWiki\Block\DatabaseBlock; 5use MediaWiki\Block\Restriction\ActionRestriction; 6use MediaWiki\Block\Restriction\NamespaceRestriction; 7use MediaWiki\Block\Restriction\PageRestriction; 8use MediaWiki\MediaWikiServices; 9use Wikimedia\Rdbms\LoadBalancer; 10use Wikimedia\TestingAccessWrapper; 11 12/** 13 * @group Blocking 14 * @group Database 15 * @coversDefaultClass SpecialBlock 16 */ 17class SpecialBlockTest extends SpecialPageTestBase { 18 /** 19 * @inheritDoc 20 */ 21 protected function newSpecialPage() { 22 $services = MediaWikiServices::getInstance(); 23 return new SpecialBlock( 24 $services->getBlockUtils(), 25 $services->getBlockPermissionCheckerFactory(), 26 $services->getBlockUserFactory(), 27 $services->getUserNameUtils(), 28 $services->getUserNamePrefixSearch(), 29 $services->getBlockActionInfo(), 30 $services->getTitleFormatter(), 31 $services->getNamespaceInfo() 32 ); 33 } 34 35 protected function tearDown(): void { 36 $this->resetTables(); 37 parent::tearDown(); 38 } 39 40 /** 41 * @covers ::getFormFields() 42 */ 43 public function testGetFormFields() { 44 $this->setMwGlobals( [ 45 'wgBlockAllowsUTEdit' => true, 46 'wgEnablePartialActionBlocks' => true, 47 ] ); 48 $page = $this->newSpecialPage(); 49 $wrappedPage = TestingAccessWrapper::newFromObject( $page ); 50 $fields = $wrappedPage->getFormFields(); 51 $this->assertIsArray( $fields ); 52 $this->assertArrayHasKey( 'Target', $fields ); 53 $this->assertArrayHasKey( 'Expiry', $fields ); 54 $this->assertArrayHasKey( 'Reason', $fields ); 55 $this->assertArrayHasKey( 'CreateAccount', $fields ); 56 $this->assertArrayHasKey( 'DisableUTEdit', $fields ); 57 $this->assertArrayHasKey( 'AutoBlock', $fields ); 58 $this->assertArrayHasKey( 'HardBlock', $fields ); 59 $this->assertArrayHasKey( 'PreviousTarget', $fields ); 60 $this->assertArrayHasKey( 'Confirm', $fields ); 61 $this->assertArrayHasKey( 'EditingRestriction', $fields ); 62 $this->assertArrayHasKey( 'PageRestrictions', $fields ); 63 $this->assertArrayHasKey( 'NamespaceRestrictions', $fields ); 64 $this->assertArrayHasKey( 'ActionRestrictions', $fields ); 65 } 66 67 /** 68 * @covers ::getFormFields() 69 */ 70 public function testGetFormFieldsActionRestrictionDisabled() { 71 $this->setMwGlobals( [ 72 'wgEnablePartialActionBlocks' => false, 73 ] ); 74 $page = $this->newSpecialPage(); 75 $wrappedPage = TestingAccessWrapper::newFromObject( $page ); 76 $fields = $wrappedPage->getFormFields(); 77 $this->assertArrayNotHasKey( 'ActionRestrictions', $fields ); 78 } 79 80 /** 81 * @covers ::maybeAlterFormDefaults() 82 */ 83 public function testMaybeAlterFormDefaults() { 84 $this->setMwGlobals( [ 85 'wgBlockAllowsUTEdit' => true, 86 ] ); 87 88 $block = $this->insertBlock(); 89 90 // Refresh the block from the database. 91 $block = DatabaseBlock::newFromTarget( $block->getTargetUserIdentity() ); 92 93 $page = $this->newSpecialPage(); 94 95 $wrappedPage = TestingAccessWrapper::newFromObject( $page ); 96 $wrappedPage->target = $block->getTargetUserIdentity(); 97 $fields = $wrappedPage->getFormFields(); 98 99 $this->assertSame( $block->getTargetName(), $fields['Target']['default'] ); 100 $this->assertSame( $block->isHardblock(), $fields['HardBlock']['default'] ); 101 $this->assertSame( $block->isCreateAccountBlocked(), $fields['CreateAccount']['default'] ); 102 $this->assertSame( $block->isAutoblocking(), $fields['AutoBlock']['default'] ); 103 $this->assertSame( !$block->isUsertalkEditAllowed(), $fields['DisableUTEdit']['default'] ); 104 $this->assertSame( $block->getReasonComment()->text, $fields['Reason']['default'] ); 105 $this->assertSame( 'infinite', $fields['Expiry']['default'] ); 106 } 107 108 /** 109 * @covers ::maybeAlterFormDefaults() 110 */ 111 public function testMaybeAlterFormDefaultsPartial() { 112 $this->setMwGlobals( [ 113 'wgEnablePartialActionBlocks' => true, 114 ] ); 115 $badActor = $this->getTestUser()->getUser(); 116 $sysop = $this->getTestSysop()->getUser(); 117 $pageSaturn = $this->getExistingTestPage( 'Saturn' ); 118 $pageMars = $this->getExistingTestPage( 'Mars' ); 119 $actionId = 100; 120 121 $block = new DatabaseBlock( [ 122 'address' => $badActor->getName(), 123 'user' => $badActor->getId(), 124 'by' => $sysop, 125 'expiry' => 'infinity', 126 'sitewide' => 0, 127 'enableAutoblock' => true, 128 ] ); 129 130 $block->setRestrictions( [ 131 new PageRestriction( 0, $pageSaturn->getId() ), 132 new PageRestriction( 0, $pageMars->getId() ), 133 new NamespaceRestriction( 0, NS_TALK ), 134 // Deleted page. 135 new PageRestriction( 0, 999999 ), 136 new ActionRestriction( 0, $actionId ), 137 ] ); 138 139 MediaWikiServices::getInstance()->getDatabaseBlockStore()->insertBlock( $block ); 140 141 // Refresh the block from the database. 142 $block = DatabaseBlock::newFromTarget( $block->getTargetUserIdentity() ); 143 144 $page = $this->newSpecialPage(); 145 146 $wrappedPage = TestingAccessWrapper::newFromObject( $page ); 147 $wrappedPage->target = $block->getTargetUserIdentity(); 148 $fields = $wrappedPage->getFormFields(); 149 150 $titles = [ 151 $pageMars->getTitle()->getPrefixedText(), 152 $pageSaturn->getTitle()->getPrefixedText(), 153 ]; 154 155 $this->assertSame( $block->getTargetName(), $fields['Target']['default'] ); 156 $this->assertSame( 'partial', $fields['EditingRestriction']['default'] ); 157 $this->assertSame( implode( "\n", $titles ), $fields['PageRestrictions']['default'] ); 158 $this->assertSame( [ $actionId ], $fields['ActionRestrictions']['default'] ); 159 } 160 161 /** 162 * @covers ::processForm() 163 */ 164 public function testProcessForm() { 165 $badActor = $this->getTestUser()->getUserIdentity(); 166 $context = RequestContext::getMain(); 167 $context->setUser( $this->getTestSysop()->getUser() ); 168 169 $page = $this->newSpecialPage(); 170 $reason = 'test'; 171 $expiry = 'infinity'; 172 $data = [ 173 'Target' => (string)$badActor, 174 'Expiry' => 'infinity', 175 'Reason' => [ 176 $reason, 177 ], 178 'Confirm' => '1', 179 'CreateAccount' => '0', 180 'DisableUTEdit' => '0', 181 'DisableEmail' => '0', 182 'HardBlock' => '0', 183 'AutoBlock' => '1', 184 'HideUser' => '0', 185 'Watch' => '0', 186 ]; 187 $result = $page->processForm( $data, $context ); 188 189 $this->assertTrue( $result ); 190 191 $block = DatabaseBlock::newFromTarget( $badActor ); 192 $this->assertSame( $reason, $block->getReasonComment()->text ); 193 $this->assertSame( $expiry, $block->getExpiry() ); 194 } 195 196 /** 197 * @covers ::processForm() 198 */ 199 public function testProcessFormExisting() { 200 $badActor = $this->getTestUser()->getUser(); 201 $sysop = $this->getTestSysop()->getUser(); 202 $context = RequestContext::getMain(); 203 $context->setUser( $sysop ); 204 205 // Create a block that will be updated. 206 $block = new DatabaseBlock( [ 207 'address' => $badActor->getName(), 208 'user' => $badActor->getId(), 209 'by' => $sysop, 210 'expiry' => 'infinity', 211 'sitewide' => 0, 212 'enableAutoblock' => false, 213 ] ); 214 MediaWikiServices::getInstance()->getDatabaseBlockStore()->insertBlock( $block ); 215 216 $page = $this->newSpecialPage(); 217 $reason = 'test'; 218 $expiry = 'infinity'; 219 $data = [ 220 'Target' => (string)$badActor, 221 'Expiry' => 'infinity', 222 'Reason' => [ 223 $reason, 224 ], 225 'Confirm' => '1', 226 'CreateAccount' => '0', 227 'DisableUTEdit' => '0', 228 'DisableEmail' => '0', 229 'HardBlock' => '0', 230 'AutoBlock' => '1', 231 'HideUser' => '0', 232 'Watch' => '0', 233 ]; 234 $result = $page->processForm( $data, $context ); 235 236 $this->assertTrue( $result ); 237 238 $block = DatabaseBlock::newFromTarget( $badActor ); 239 $this->assertSame( $reason, $block->getReasonComment()->text ); 240 $this->assertSame( $expiry, $block->getExpiry() ); 241 $this->assertTrue( $block->isAutoblocking() ); 242 } 243 244 /** 245 * @covers ::processForm() 246 */ 247 public function testProcessFormRestrictions() { 248 $this->setMwGlobals( [ 249 'wgEnablePartialActionBlocks' => true, 250 ] ); 251 252 $badActor = $this->getTestUser()->getUser(); 253 $context = RequestContext::getMain(); 254 $context->setUser( $this->getTestSysop()->getUser() ); 255 256 $pageSaturn = $this->getExistingTestPage( 'Saturn' ); 257 $pageMars = $this->getExistingTestPage( 'Mars' ); 258 $actionId = 100; 259 260 $titles = [ 261 $pageSaturn->getTitle()->getText(), 262 $pageMars->getTitle()->getText(), 263 ]; 264 265 $page = $this->newSpecialPage(); 266 $reason = 'test'; 267 $expiry = 'infinity'; 268 $data = [ 269 'Target' => (string)$badActor, 270 'Expiry' => 'infinity', 271 'Reason' => [ 272 $reason, 273 ], 274 'Confirm' => '1', 275 'CreateAccount' => '0', 276 'DisableUTEdit' => '0', 277 'DisableEmail' => '0', 278 'HardBlock' => '0', 279 'AutoBlock' => '1', 280 'HideUser' => '0', 281 'Watch' => '0', 282 'EditingRestriction' => 'partial', 283 'PageRestrictions' => implode( "\n", $titles ), 284 'NamespaceRestrictions' => '', 285 'ActionRestrictions' => [ $actionId ], 286 ]; 287 $result = $page->processForm( $data, $context ); 288 289 $this->assertTrue( $result ); 290 291 $block = DatabaseBlock::newFromTarget( $badActor ); 292 $this->assertSame( $reason, $block->getReasonComment()->text ); 293 $this->assertSame( $expiry, $block->getExpiry() ); 294 $this->assertCount( 3, $block->getRestrictions() ); 295 $this->assertTrue( $this->getBlockRestrictionStore()->equals( $block->getRestrictions(), [ 296 new PageRestriction( $block->getId(), $pageMars->getId() ), 297 new PageRestriction( $block->getId(), $pageSaturn->getId() ), 298 new ActionRestriction( $block->getId(), $actionId ), 299 ] ) ); 300 } 301 302 /** 303 * @covers ::processForm() 304 */ 305 public function testProcessFormRestrictionsChange() { 306 $badActor = $this->getTestUser()->getUser(); 307 $context = RequestContext::getMain(); 308 $context->setUser( $this->getTestSysop()->getUser() ); 309 310 $pageSaturn = $this->getExistingTestPage( 'Saturn' ); 311 $pageMars = $this->getExistingTestPage( 'Mars' ); 312 313 $titles = [ 314 $pageSaturn->getTitle()->getText(), 315 $pageMars->getTitle()->getText(), 316 ]; 317 318 // Create a partial block. 319 $page = $this->newSpecialPage(); 320 $reason = 'test'; 321 $expiry = 'infinity'; 322 $data = [ 323 'Target' => (string)$badActor, 324 'Expiry' => 'infinity', 325 'Reason' => [ 326 $reason, 327 ], 328 'Confirm' => '1', 329 'CreateAccount' => '1', 330 'DisableUTEdit' => '0', 331 'DisableEmail' => '0', 332 'HardBlock' => '0', 333 'AutoBlock' => '1', 334 'HideUser' => '0', 335 'Watch' => '0', 336 'EditingRestriction' => 'partial', 337 'PageRestrictions' => implode( "\n", $titles ), 338 'NamespaceRestrictions' => '', 339 ]; 340 $result = $page->processForm( $data, $context ); 341 342 $this->assertTrue( $result ); 343 344 $block = DatabaseBlock::newFromTarget( $badActor ); 345 $this->assertSame( $reason, $block->getReasonComment()->text ); 346 $this->assertSame( $expiry, $block->getExpiry() ); 347 $this->assertFalse( $block->isSitewide() ); 348 $this->assertTrue( $block->isCreateAccountBlocked() ); 349 $this->assertCount( 2, $block->getRestrictions() ); 350 $this->assertTrue( $this->getBlockRestrictionStore()->equals( $block->getRestrictions(), [ 351 new PageRestriction( $block->getId(), $pageMars->getId() ), 352 new PageRestriction( $block->getId(), $pageSaturn->getId() ), 353 ] ) ); 354 355 // Remove a page from the partial block. 356 $data['PageRestrictions'] = $pageMars->getTitle()->getText(); 357 $result = $page->processForm( $data, $context ); 358 359 $this->assertTrue( $result ); 360 361 $block = DatabaseBlock::newFromTarget( $badActor ); 362 $this->assertSame( $reason, $block->getReasonComment()->text ); 363 $this->assertSame( $expiry, $block->getExpiry() ); 364 $this->assertFalse( $block->isSitewide() ); 365 $this->assertTrue( $block->isCreateAccountBlocked() ); 366 $this->assertCount( 1, $block->getRestrictions() ); 367 $this->assertTrue( $this->getBlockRestrictionStore()->equals( $block->getRestrictions(), [ 368 new PageRestriction( $block->getId(), $pageMars->getId() ), 369 ] ) ); 370 371 // Remove the last page from the block. 372 $data['PageRestrictions'] = ''; 373 $result = $page->processForm( $data, $context ); 374 375 $this->assertTrue( $result ); 376 377 $block = DatabaseBlock::newFromTarget( $badActor ); 378 $this->assertSame( $reason, $block->getReasonComment()->text ); 379 $this->assertSame( $expiry, $block->getExpiry() ); 380 $this->assertFalse( $block->isSitewide() ); 381 $this->assertTrue( $block->isCreateAccountBlocked() ); 382 $this->assertSame( [], $block->getRestrictions() ); 383 384 // Change to sitewide. 385 $data['EditingRestriction'] = 'sitewide'; 386 $result = $page->processForm( $data, $context ); 387 388 $this->assertTrue( $result ); 389 390 $block = DatabaseBlock::newFromTarget( $badActor ); 391 $this->assertSame( $reason, $block->getReasonComment()->text ); 392 $this->assertSame( $expiry, $block->getExpiry() ); 393 $this->assertTrue( $block->isSitewide() ); 394 $this->assertSame( [], $block->getRestrictions() ); 395 396 // Ensure that there are no restrictions where the blockId is 0. 397 $count = $this->db->selectRowCount( 398 'ipblocks_restrictions', 399 '*', 400 [ 'ir_ipb_id' => 0 ], 401 __METHOD__ 402 ); 403 $this->assertSame( 0, $count ); 404 } 405 406 /** 407 * @dataProvider provideProcessFormUserTalkEditFlag 408 * @covers ::processForm() 409 */ 410 public function testProcessFormUserTalkEditFlag( $options, $expected ) { 411 $this->setMwGlobals( [ 412 'wgBlockAllowsUTEdit' => $options['configAllowsUserTalkEdit'], 413 ] ); 414 415 $performer = $this->getTestSysop()->getUser(); 416 $target = $this->getTestUser()->getUser(); 417 418 $context = new DerivativeContext( RequestContext::getMain() ); 419 $context->setUser( $performer ); 420 421 $data = [ 422 'Target' => $target, 423 'PreviousTarget' => $target, 424 'Expiry' => 'infinity', 425 'CreateAccount' => '1', 426 'DisableEmail' => '0', 427 'HardBlock' => '0', 428 'AutoBlock' => '0', 429 'Watch' => '0', 430 'Confirm' => '1', 431 'DisableUTEdit' => $options['optionBlocksUserTalkEdit'], 432 ]; 433 434 if ( !$options['userTalkNamespaceBlocked'] ) { 435 $data['EditingRestriction'] = 'partial'; 436 $data['PageRestrictions'] = ''; 437 $data['NamespaceRestrictions'] = ''; 438 } 439 440 $result = $this->newSpecialPage()->processForm( 441 $data, 442 $context 443 ); 444 445 if ( $expected === 'ipb-prevent-user-talk-edit' ) { 446 $this->assertSame( $expected, $result->getErrorsArray()[0][0] ); 447 } else { 448 $block = DatabaseBlock::newFromTarget( $target ); 449 $this->assertSame( $expected, $block->isUsertalkEditAllowed() ); 450 } 451 } 452 453 /** 454 * Test cases for whether own user talk edit is allowed, with different combinations of: 455 * - whether user talk namespace blocked 456 * - config BlockAllowsUTEdit true/false 457 * - block option specifying whether to block own user talk edit 458 * For more about the desired behaviour, see T252892. 459 * 460 * @return array 461 */ 462 public function provideProcessFormUserTalkEditFlag() { 463 return [ 464 'Always allowed if user talk namespace not blocked' => [ 465 [ 466 'userTalkNamespaceBlocked' => false, 467 'configAllowsUserTalkEdit' => true, 468 'optionBlocksUserTalkEdit' => false, 469 ], 470 true, 471 ], 472 'Always allowed if user talk namespace not blocked (config is false)' => [ 473 [ 474 'userTalkNamespaceBlocked' => false, 475 'configAllowsUserTalkEdit' => false, 476 'optionBlocksUserTalkEdit' => false, 477 ], 478 true, 479 ], 480 'Error if user talk namespace not blocked, but option blocks user talk edit' => [ 481 [ 482 'userTalkNamespaceBlocked' => false, 483 'configAllowsUserTalkEdit' => true, 484 'optionBlocksUserTalkEdit' => true, 485 ], 486 'ipb-prevent-user-talk-edit', 487 ], 488 'Always blocked if user talk namespace blocked and wgBlockAllowsUTEdit is false' => [ 489 [ 490 'userTalkNamespaceBlocked' => true, 491 'configAllowsUserTalkEdit' => false, 492 'optionBlocksUserTalkEdit' => false, 493 ], 494 false, 495 ], 496 'Option used if user talk namespace blocked and config is true (blocked)' => [ 497 [ 498 'userTalkNamespaceBlocked' => true, 499 'configAllowsUserTalkEdit' => true, 500 'optionBlocksUserTalkEdit' => true, 501 ], 502 false, 503 ], 504 'Option used if user talk namespace blocked and config is true (not blocked)' => [ 505 [ 506 'userTalkNamespaceBlocked' => true, 507 'configAllowsUserTalkEdit' => true, 508 'optionBlocksUserTalkEdit' => false, 509 ], 510 true, 511 ], 512 ]; 513 } 514 515 /** 516 * @dataProvider provideProcessFormErrors 517 * @covers ::processForm() 518 */ 519 public function testProcessFormErrors( $data, $expected, $options = [] ) { 520 $this->setMwGlobals( [ 521 'wgBlockAllowsUTEdit' => true, 522 ] ); 523 524 $performer = $this->getTestSysop()->getUser(); 525 $target = !empty( $options['blockingSelf'] ) ? $performer : '1.2.3.4'; 526 $defaultData = [ 527 'Target' => $target, 528 'PreviousTarget' => $target, 529 'Expiry' => 'infinity', 530 'CreateAccount' => '0', 531 'DisableUTEdit' => '0', 532 'DisableEmail' => '0', 533 'HardBlock' => '0', 534 'AutoBlock' => '0', 535 'Confirm' => '0', 536 'Watch' => '0', 537 ]; 538 539 $context = new DerivativeContext( RequestContext::getMain() ); 540 $context->setUser( $performer ); 541 542 $result = $this->newSpecialPage()->processForm( 543 array_merge( $defaultData, $data ), 544 $context 545 ); 546 547 if ( $result instanceof Status ) { 548 $result = $result->getErrorsArray(); 549 } 550 $error = is_array( $result[0] ) ? $result[0][0] : $result[0]; 551 $this->assertEquals( $expected, $error ); 552 } 553 554 public function provideProcessFormErrors() { 555 return [ 556 'Invalid expiry' => [ 557 [ 558 'Expiry' => 'invalid', 559 ], 560 'ipb_expiry_invalid', 561 ], 562 'Expiry is in the past' => [ 563 [ 564 'Expiry' => 'yesterday', 565 ], 566 'ipb_expiry_old', 567 ], 568 'Bad ip address' => [ 569 [ 570 'Target' => '1.2.3.4/1234', 571 ], 572 'badipaddress', 573 ], 574 'Edit user talk page invalid with no restrictions' => [ 575 [ 576 'EditingRestriction' => 'partial', 577 'DisableUTEdit' => '1', 578 'PageRestrictions' => '', 579 'NamespaceRestrictions' => '', 580 ], 581 'ipb-prevent-user-talk-edit', 582 ], 583 'Edit user talk page invalid with namespace restriction !== NS_USER_TALK ' => [ 584 [ 585 'EditingRestriction' => 'partial', 586 'DisableUTEdit' => '1', 587 'PageRestrictions' => '', 588 'NamespaceRestrictions' => NS_USER, 589 ], 590 'ipb-prevent-user-talk-edit', 591 ], 592 'Blocking self and target changed' => [ 593 [ 594 'PreviousTarget' => 'other', 595 'Confirm' => '1', 596 ], 597 'ipb-blockingself', 598 [ 599 'blockingSelf' => true, 600 ], 601 ], 602 'Blocking self and no confirm' => [ 603 [], 604 'ipb-blockingself', 605 [ 606 'blockingSelf' => true, 607 ], 608 ], 609 'Empty expiry' => [ 610 [ 611 'Expiry' => '', 612 ], 613 'ipb_expiry_invalid', 614 ], 615 'Expiry valid but longer than 50 chars' => [ 616 [ 617 'Expiry' => '30th September 9999 19:19:19.532453 Europe/Amsterdam', 618 ], 619 'ipb_expiry_invalid', 620 ], 621 ]; 622 } 623 624 /** 625 * @dataProvider provideProcessFormErrorsReblock 626 * @covers ::processForm() 627 */ 628 public function testProcessFormErrorsReblock( $data, $permissions, $expected ) { 629 $this->setMwGlobals( [ 630 'wgBlockAllowsUTEdit' => true, 631 ] ); 632 633 $performer = $this->getTestSysop()->getUser(); 634 $this->overrideUserPermissions( $performer, $permissions ); 635 $blockedUser = $this->getTestUser()->getUser(); 636 637 $block = new DatabaseBlock( [ 638 'address' => $blockedUser, 639 'by' => $performer, 640 'hideName' => true, 641 ] ); 642 MediaWikiServices::getInstance()->getDatabaseBlockStore()->insertBlock( $block ); 643 644 // Matches the existing block 645 $defaultData = [ 646 'Target' => $blockedUser->getName(), 647 'PreviousTarget' => $blockedUser->getName(), 648 'Expiry' => 'infinity', 649 'DisableUTEdit' => '1', 650 'CreateAccount' => '0', 651 'DisableEmail' => '0', 652 'HardBlock' => '0', 653 'AutoBlock' => '0', 654 'HideUser' => '1', 655 'Confirm' => '1', 656 'Watch' => '0', 657 ]; 658 659 $context = new DerivativeContext( RequestContext::getMain() ); 660 $context->setUser( $performer ); 661 662 $result = $this->newSpecialPage()->processForm( 663 array_merge( $defaultData, $data ), 664 $context 665 ); 666 667 if ( $result instanceof Status ) { 668 $result = $result->getErrorsArray(); 669 } 670 $error = is_array( $result[0] ) ? $result[0][0] : $result[0]; 671 $this->assertEquals( $expected, $error ); 672 } 673 674 public function provideProcessFormErrorsReblock() { 675 return [ 676 'Reblock user with Confirm false' => [ 677 [ 678 // Avoid error for hiding user with confirm false 679 'HideUser' => '0', 680 'Confirm' => '0', 681 ], 682 [ 'block', 'hideuser' ], 683 'ipb_already_blocked', 684 ], 685 'Reblock user with Reblock false' => [ 686 [ 'Reblock' => '0' ], 687 [ 'block', 'hideuser' ], 688 'ipb_already_blocked', 689 ], 690 'Reblock with confirm True but target has changed' => [ 691 [ 'PreviousTarget' => '1.2.3.4' ], 692 [ 'block', 'hideuser' ], 693 'ipb_already_blocked', 694 ], 695 'Reblock with same block' => [ 696 [ 'HideUser' => '1' ], 697 [ 'block', 'hideuser' ], 698 'ipb_already_blocked', 699 ], 700 'Reblock hidden user with wrong permissions' => [ 701 [ 'HideUser' => '0' ], 702 [ 'block', 'hideuser' => false ], 703 'cant-see-hidden-user', 704 ], 705 ]; 706 } 707 708 /** 709 * @dataProvider provideProcessFormErrorsHideUser 710 * @covers ::processForm() 711 */ 712 public function testProcessFormErrorsHideUser( $data, $permissions, $expected ) { 713 $performer = $this->getTestSysop()->getUser(); 714 $this->overrideUserPermissions( $performer, array_merge( $permissions, [ 'block' ] ) ); 715 716 $defaultData = [ 717 'Target' => $this->getTestUser()->getUser(), 718 'HideUser' => '1', 719 'Expiry' => 'infinity', 720 'Confirm' => '1', 721 'CreateAccount' => '0', 722 'DisableUTEdit' => '0', 723 'DisableEmail' => '0', 724 'HardBlock' => '0', 725 'AutoBlock' => '0', 726 'Watch' => '0', 727 ]; 728 729 $context = new DerivativeContext( RequestContext::getMain() ); 730 $context->setUser( $performer ); 731 732 $result = $this->newSpecialPage()->processForm( 733 array_merge( $defaultData, $data ), 734 $context 735 ); 736 737 if ( $result instanceof Status ) { 738 $result = $result->getErrorsArray(); 739 } 740 $error = is_array( $result[0] ) ? $result[0][0] : $result[0]; 741 $this->assertEquals( $expected, $error ); 742 } 743 744 public function provideProcessFormErrorsHideUser() { 745 return [ 746 'HideUser with wrong permissions' => [ 747 [], 748 [ 'hideuser' => '0' ], 749 'badaccess-group0', 750 ], 751 'Hideuser with partial block' => [ 752 [ 'EditingRestriction' => 'partial' ], 753 [ 'hideuser' ], 754 'ipb_hide_partial', 755 ], 756 'Hideuser with finite expiry' => [ 757 [ 'Expiry' => '1 hour' ], 758 [ 'hideuser' ], 759 'ipb_expiry_temp', 760 ], 761 'Hideuser with no confirm' => [ 762 [ 'Confirm' => '0' ], 763 [ 'hideuser' ], 764 'ipb-confirmhideuser', 765 ], 766 ]; 767 } 768 769 /** 770 * @covers ::processForm() 771 */ 772 public function testProcessFormErrorsHideUserProlific() { 773 $this->setMwGlobals( [ 'wgHideUserContribLimit' => 0 ] ); 774 775 $performer = $this->getTestSysop()->getUser(); 776 $this->overrideUserPermissions( $performer, [ 'block', 'hideuser' ] ); 777 778 $userToBlock = $this->getTestUser()->getUser(); 779 $pageSaturn = $this->getExistingTestPage( 'Saturn' ); 780 $pageSaturn->doUserEditContent( 781 ContentHandler::makeContent( 'content', $pageSaturn->getTitle() ), 782 $userToBlock, 783 'summary' 784 ); 785 786 $context = new DerivativeContext( RequestContext::getMain() ); 787 $context->setUser( $performer ); 788 789 $result = $this->newSpecialPage()->processForm( 790 [ 791 'Target' => $userToBlock, 792 'CreateAccount' => '1', 793 'HideUser' => '1', 794 'Expiry' => 'infinity', 795 'Confirm' => '1', 796 'DisableUTEdit' => '0', 797 'DisableEmail' => '0', 798 'HardBlock' => '0', 799 'AutoBlock' => '0', 800 'Watch' => '0', 801 ], 802 $context 803 ); 804 805 if ( $result instanceof Status ) { 806 $result = $result->getErrorsArray(); 807 } 808 $error = is_array( $result[0] ) ? $result[0][0] : $result[0]; 809 $this->assertEquals( 'ipb_hide_invalid', $error ); 810 } 811 812 /** 813 * TODO: Move to BlockPermissionCheckerTest 814 * 815 * @dataProvider provideCheckUnblockSelf 816 * @covers ::checkUnblockSelf 817 */ 818 public function testCheckUnblockSelf( 819 $blockedUser, 820 $blockPerformer, 821 $adjustPerformer, 822 $adjustTarget, 823 $sitewide, 824 $expectedResult, 825 $reason 826 ) { 827 $this->hideDeprecated( 'SpecialBlock::checkUnblockSelf' ); 828 829 $this->setMwGlobals( [ 830 'wgBlockDisablesLogin' => false, 831 ] ); 832 $this->setGroupPermissions( 'sysop', 'unblockself', true ); 833 $this->setGroupPermissions( 'user', 'block', true ); 834 // Getting errors about creating users in db in provider. 835 // Need to use mutable to ensure different named testusers. 836 $users = [ 837 'u1' => $this->getMutableTestUser( 'sysop' )->getUser(), 838 'u2' => $this->getMutableTestUser( 'sysop' )->getUser(), 839 'u3' => $this->getMutableTestUser( 'sysop' )->getUser(), 840 'u4' => $this->getMutableTestUser( 'sysop' )->getUser(), 841 'nonsysop' => $this->getTestUser()->getUser() 842 ]; 843 foreach ( [ 'blockedUser', 'blockPerformer', 'adjustPerformer', 'adjustTarget' ] as $var ) { 844 $$var = $users[$$var]; 845 } 846 847 $block = new DatabaseBlock( [ 848 'address' => $blockedUser->getName(), 849 'user' => $blockedUser->getId(), 850 'by' => $blockPerformer, 851 'expiry' => 'infinity', 852 'sitewide' => $sitewide, 853 'enableAutoblock' => true, 854 ] ); 855 856 MediaWikiServices::getInstance()->getDatabaseBlockStore()->insertBlock( $block ); 857 858 $this->assertSame( 859 SpecialBlock::checkUnblockSelf( $adjustTarget, $adjustPerformer ), 860 $expectedResult, 861 $reason 862 ); 863 } 864 865 public function provideCheckUnblockSelf() { 866 // 'blockedUser', 'blockPerformer', 'adjustPerformer', 'adjustTarget' 867 return [ 868 [ 'u1', 'u2', 'u3', 'u4', 1, true, 'Unrelated users' ], 869 [ 'u1', 'u2', 'u1', 'u4', 1, 'ipbblocked', 'Block unrelated while blocked' ], 870 [ 'u1', 'u2', 'u1', 'u4', 0, true, 'Block unrelated while partial blocked' ], 871 [ 'u1', 'u2', 'u1', 'u1', 1, true, 'Has unblockself' ], 872 [ 'nonsysop', 'u2', 'nonsysop', 'nonsysop', 1, 'ipbnounblockself', 'no unblockself' ], 873 [ 'nonsysop', 'nonsysop', 'nonsysop', 'nonsysop', 1, true, 874 'no unblockself but can de-selfblock' 875 ], 876 [ 'u1', 'u2', 'u1', 'u2', 1, true, 'Can block user who blocked' ], 877 ]; 878 } 879 880 /** 881 * @dataProvider provideGetTargetAndType 882 * @covers ::getTargetAndType 883 */ 884 public function testGetTargetAndType( $par, $requestData, $expectedTarget ) { 885 $request = $requestData ? new FauxRequest( $requestData ) : null; 886 $page = $this->newSpecialPage(); 887 list( $target, $type ) = $page->getTargetAndType( $par, $request ); 888 $this->assertSame( $target, $expectedTarget ); 889 } 890 891 public function provideGetTargetAndType() { 892 $invalidTarget = ''; 893 return [ 894 'Choose \'wpTarget\' parameter first' => [ 895 '2.2.2.0/24', 896 [ 897 'wpTarget' => '1.1.1.0/24', 898 'ip' => '3.3.3.0/24', 899 'wpBlockAddress' => '4.4.4.0/24', 900 ], 901 '1.1.1.0/24', 902 ], 903 'Choose subpage parameter second' => [ 904 '2.2.2.0/24', 905 [ 906 'wpTarget' => $invalidTarget, 907 'ip' => '3.3.3.0/24', 908 'wpBlockAddress' => '4.4.4.0/24', 909 ], 910 '2.2.2.0/24', 911 ], 912 'Choose \'ip\' parameter third' => [ 913 $invalidTarget, 914 [ 915 'wpTarget' => $invalidTarget, 916 'ip' => '3.3.3.0/24', 917 'wpBlockAddress' => '4.4.4.0/24', 918 ], 919 '3.3.3.0/24', 920 ], 921 'Choose \'wpBlockAddress\' parameter fourth' => [ 922 $invalidTarget, 923 [ 924 'wpTarget' => $invalidTarget, 925 'ip' => $invalidTarget, 926 'wpBlockAddress' => '4.4.4.0/24', 927 ], 928 '4.4.4.0/24', 929 ], 930 'No web request' => [ 931 '2.2.2.0/24', 932 false, 933 '2.2.2.0/24', 934 ], 935 'No valid request data or subpage parameter' => [ 936 null, 937 [], 938 null, 939 ], 940 ]; 941 } 942 943 protected function insertBlock() { 944 $badActor = $this->getTestUser()->getUser(); 945 $sysop = $this->getTestSysop()->getUser(); 946 947 $block = new DatabaseBlock( [ 948 'address' => $badActor->getName(), 949 'user' => $badActor->getId(), 950 'by' => $sysop, 951 'expiry' => 'infinity', 952 'sitewide' => 1, 953 'enableAutoblock' => true, 954 ] ); 955 956 MediaWikiServices::getInstance()->getDatabaseBlockStore()->insertBlock( $block ); 957 958 return $block; 959 } 960 961 protected function resetTables() { 962 $this->db->delete( 'ipblocks', '*', __METHOD__ ); 963 $this->db->delete( 'ipblocks_restrictions', '*', __METHOD__ ); 964 } 965 966 /** 967 * Get a BlockRestrictionStore instance 968 * 969 * @return BlockRestrictionStore 970 */ 971 private function getBlockRestrictionStore(): BlockRestrictionStore { 972 $loadBalancer = $this->getMockBuilder( LoadBalancer::class ) 973 ->disableOriginalConstructor() 974 ->getMock(); 975 976 return new BlockRestrictionStore( $loadBalancer ); 977 } 978} 979