1<?php 2 3use MediaWiki\Block\AbstractBlock; 4use MediaWiki\Block\BlockPermissionChecker; 5use MediaWiki\Block\BlockUtils; 6use MediaWiki\Block\DatabaseBlock; 7use MediaWiki\Config\ServiceOptions; 8use MediaWiki\Permissions\Authority; 9use MediaWiki\Tests\Unit\Permissions\MockAuthorityTrait; 10use MediaWiki\User\UserIdentity; 11use MediaWiki\User\UserIdentityValue; 12use PHPUnit\Framework\MockObject\MockObject; 13 14/** 15 * @group Blocking 16 * @coversDefaultClass \MediaWiki\Block\BlockPermissionChecker 17 * @author DannyS712 18 */ 19class BlockPermissionCheckerTest extends MediaWikiUnitTestCase { 20 use MockAuthorityTrait; 21 22 /** 23 * @param bool $enableUserEmail 24 * @param array $targetAndType 25 * @param Authority $performer 26 * @return BlockPermissionChecker 27 */ 28 private function getBlockPermissionChecker( 29 bool $enableUserEmail, 30 array $targetAndType, 31 Authority $performer 32 ) { 33 $options = new ServiceOptions( 34 BlockPermissionChecker::CONSTRUCTOR_OPTIONS, 35 [ 'EnableUserEmail' => $enableUserEmail ] 36 ); 37 38 // We don't care about how BlockUtils::parseBlockTarget actually works, just 39 // override with whatever. Only used for a single call in the constructor 40 // for getting target and type 41 $blockUtils = $this->createNoOpMock( BlockUtils::class, [ 'parseBlockTarget' ] ); 42 $blockUtils->expects( $this->once() ) 43 ->method( 'parseBlockTarget' ) 44 ->willReturn( $targetAndType ); 45 46 return new BlockPermissionChecker( 47 $options, 48 $blockUtils, 49 'foo', // input to BlockUtils::parseBlockTarget, not used 50 $performer 51 ); 52 } 53 54 /** 55 * @param bool $isSitewide 56 * @param string $byName 57 * @return DatabaseBlock|MockObject 58 */ 59 private function getBlock( bool $isSitewide, string $byName ) { 60 // Mock DatabaseBlock instead of AbstractBlock because its easier 61 $block = $this->createNoOpMock( 62 DatabaseBlock::class, 63 [ 'isSitewide', 'getBlocker' ] 64 ); 65 $block->method( 'isSitewide' )->willReturn( $isSitewide ); 66 $block->method( 'getBlocker' )->willReturn( new UserIdentityValue( 7, $byName ) ); 67 return $block; 68 } 69 70 public function provideCheckBasePermissions() { 71 // $rights, $checkHideuser, $expect 72 yield 'need block' => [ [], false, 'badaccess-group0' ]; 73 yield 'block enough for not hiding' => [ [ 'block' ], false, true ]; 74 yield 'need hideuser' => [ [ 'block' ], true, 'unblock-hideuser' ]; 75 yield 'can hideuser' => [ [ 'block', 'hideuser' ], true, true ]; 76 } 77 78 /** 79 * @covers ::checkBasePermissions 80 * @dataProvider provideCheckBasePermissions 81 */ 82 public function testCheckBasePermissions( $rights, $checkHideuser, $expect ) { 83 $performer = $this->mockRegisteredAuthorityWithPermissions( $rights ); 84 $blockPermissionChecker = $this->getBlockPermissionChecker( 85 true, // $enableUserEmail, irrelevant 86 [ null, null ], // $targetAndType, irrelevant 87 $performer 88 ); 89 $this->assertSame( 90 $expect, 91 $blockPermissionChecker->checkBasePermissions( $checkHideuser ) 92 ); 93 } 94 95 /** 96 * @covers ::checkBlockPermissions 97 */ 98 public function testNotBlockedPerformer() { 99 // checkBlockPermissions has an early return true if the performer has no block 100 $blockPermissionChecker = $this->getBlockPermissionChecker( 101 true, // $enableUserEmail, irrelevant 102 [ null, null ], // $targetAndType, irrelevant 103 $this->mockRegisteredAuthorityWithoutPermissions( [] ) 104 ); 105 $this->assertTrue( 106 $blockPermissionChecker->checkBlockPermissions() 107 ); 108 } 109 110 /** 111 * @covers ::checkBlockPermissions 112 */ 113 public function testPartialBlockedPerformer() { 114 // checkBlockPermissions has an early return true if the performer is not sitewide blocked 115 $blocker = new UserIdentityValue( 1, 'blocker', UserIdentity::LOCAL ); 116 $performer = $this->mockUserAuthorityWithBlock( $blocker, $this->getBlock( false, '' ) ); 117 118 $blockPermissionChecker = $this->getBlockPermissionChecker( 119 true, // $enableUserEmail, irrelevant 120 [ null, null ], // $targetAndType, irrelevant 121 $performer 122 ); 123 $this->assertTrue( 124 $blockPermissionChecker->checkBlockPermissions() 125 ); 126 } 127 128 public function provideCheckBlockPermissions() { 129 // Blocked admin changing own block 130 yield 'Self blocked' => [ 'blocker', 1, 'blocker', false, true ]; 131 yield 'unblockself' => [ 'another admin', 1, 'blocker', true, true ]; 132 yield 'ipbnounblockself' => [ 'another admin', 1, 'blocker', false, 'ipbnounblockself' ]; 133 134 // Blocked admins can always block the admin who blocked them 135 yield 'Block my blocker' => [ 'another admin', 2, 'another admin', false, true ]; 136 137 // Blocked admin blocking a third party 138 yield 'Block someone else' => [ 'another admin', 3, 'someone else', false, 'ipbblocked' ]; 139 } 140 141 /** 142 * @covers ::checkBlockPermissions 143 * @dataProvider provideCheckBlockPermissions 144 */ 145 public function testCheckBlockPermissions( 146 string $blockedBy, 147 int $targetUserId, 148 string $targetUserName, 149 bool $unblockSelf, 150 $expect 151 ) { 152 // cases for the target being a UserIdentity 153 // performing user has id 1 and the name 'blocker' 154 $block = $this->getBlock( 155 true, // sitewide 156 $blockedBy 157 ); 158 $rights = $unblockSelf ? [ 'unblockself' ] : []; 159 160 $blocker = new UserIdentityValue( 1, 'blocker', UserIdentity::LOCAL ); 161 $performer = $this->mockUserAuthorityWithBlock( $blocker, $block, $rights ); 162 163 $target = new UserIdentityValue( $targetUserId, $targetUserName ); 164 165 $blockPermissionChecker = $this->getBlockPermissionChecker( 166 true, // $enableUserEmail, irrelevant 167 [ $target, AbstractBlock::TYPE_USER ], // $targetAndType, irrelevant 168 $performer 169 ); 170 $this->assertSame( 171 $expect, 172 $blockPermissionChecker->checkBlockPermissions() 173 ); 174 } 175 176 public function provideCheckEmailPermissions() { 177 // $enableEmail, $rights, $expect 178 yield 'Email not enabled, without permissions' => [ false, [], false ]; 179 yield 'Email not enabled, with permissions' => [ false, [ 'blockemail' ], false ]; 180 yield 'Email enabled, without permissions' => [ true, [], false ]; 181 yield 'Email enabled, with permissions' => [ true, [ 'blockemail' ], true ]; 182 } 183 184 /** 185 * @covers ::checkEmailPermissions 186 * @dataProvider provideCheckEmailPermissions 187 */ 188 public function testCheckEmailPermissionOkay( $enableEmail, $rights, $expect ) { 189 $performer = $this->mockRegisteredAuthorityWithPermissions( $rights ); 190 $blockPermissionChecker = $this->getBlockPermissionChecker( 191 $enableEmail, 192 [ null, null ], // $targetAndType, irrelevant 193 $performer 194 ); 195 $this->assertSame( $expect, $blockPermissionChecker->checkEmailPermissions() ); 196 } 197} 198