3 namespace MediaWiki\Tests\Unit\Permissions
;
5 use MediaWiki\Block\Block
;
6 use MediaWiki\Block\BlockErrorFormatter
;
7 use MediaWiki\Block\SystemBlock
;
8 use MediaWiki\Context\IContextSource
;
9 use MediaWiki\Language\Language
;
10 use MediaWiki\Message\Message
;
11 use MediaWiki\Permissions\Authority
;
12 use MediaWiki\Permissions\PermissionManager
;
13 use MediaWiki\Permissions\PermissionStatus
;
14 use MediaWiki\Permissions\RateLimiter
;
15 use MediaWiki\Permissions\RateLimitSubject
;
16 use MediaWiki\Permissions\SimpleAuthority
;
17 use MediaWiki\Permissions\UltimateAuthority
;
18 use MediaWiki\Permissions\UserAuthority
;
19 use MediaWiki\Request\FauxRequest
;
20 use MediaWiki\Request\WebRequest
;
21 use MediaWiki\User\User
;
22 use MediaWiki\User\UserIdentity
;
23 use MediaWiki\User\UserIdentityValue
;
24 use PHPUnit\Framework\MockObject\MockObject
;
28 * Various useful Authority mocks.
29 * @stable to use (since 1.37)
31 trait MockAuthorityTrait
{
34 * Create mock ultimate Authority for anon user.
38 private function mockAnonUltimateAuthority(): Authority
{
39 return new UltimateAuthority( new UserIdentityValue( 0, '127.0.0.1' ) );
43 * Create mock ultimate Authority for a temp user.
47 private function mockTempUltimateAuthority(): Authority
{
48 return new UltimateAuthority( new UserIdentityValue( 42, '~2024-1' ), true );
52 * Create mock ultimate Authority for registered user.
56 private function mockRegisteredUltimateAuthority(): Authority
{
57 return new UltimateAuthority( new UserIdentityValue( 9999, 'Petr' ) );
61 * Create mock Authority for anon user with no permissions.
65 private function mockAnonNullAuthority(): Authority
{
66 return new SimpleAuthority( new UserIdentityValue( 0, '127.0.0.1' ), [] );
70 * Create mock Authority for a temp user with no permissions.
74 private function mockTempNullAuthority(): Authority
{
75 return new SimpleAuthority( new UserIdentityValue( 42, '~2024-1' ), [], true );
79 * Create mock Authority for a registered user with no permissions.
83 private function mockRegisteredNullAuthority(): Authority
{
84 return new SimpleAuthority( new UserIdentityValue( 9999, 'Petr' ), [] );
88 * Create a mock Authority for anon user with $permissions.
90 * @param array $permissions
93 private function mockAnonAuthorityWithPermissions( array $permissions ): Authority
{
94 return new SimpleAuthority( new UserIdentityValue( 0, '127.0.0.1' ), $permissions );
98 * Create a mock Authority for a temp user with $permissions.
100 * @param array $permissions
103 private function mockTempAuthorityWithPermissions( array $permissions ): Authority
{
104 return new SimpleAuthority( new UserIdentityValue( 42, '~2024-1' ), $permissions, true );
108 * Create a mock Authority for a registered user with $permissions.
110 * @param array $permissions
113 private function mockRegisteredAuthorityWithPermissions( array $permissions ): Authority
{
114 return new SimpleAuthority( new UserIdentityValue( 9999, 'Petr' ), $permissions );
118 * Create a mock Authority for a $user with $permissions.
120 * @param UserIdentity $user
121 * @param array $permissions
122 * @param bool $isTemp
125 private function mockUserAuthorityWithPermissions(
130 return new SimpleAuthority( $user, $permissions, $isTemp );
134 * Create a mock Authority for $user with $block and $permissions.
136 * @param UserIdentity $user
137 * @param Block $block
138 * @param array $permissions
139 * @param bool $isTemp
143 private function mockUserAuthorityWithBlock(
146 array $permissions = [],
149 return $this->mockAuthority(
151 static function ( $permission ) use ( $permissions ) {
152 return in_array( $permission, $permissions );
160 * Create a mock Authority for an anon user with all but $permissions
161 * @param array $permissions
164 private function mockAnonAuthorityWithoutPermissions( array $permissions ): Authority
{
165 return $this->mockUserAuthorityWithoutPermissions(
166 new UserIdentityValue( 0, '127.0.0.1' ),
172 * Create a mock Authority for a temp user with all but $permissions
173 * @param array $permissions
176 private function mockTempAuthorityWithoutPermissions( array $permissions ): Authority
{
177 return $this->mockUserAuthorityWithoutPermissions(
178 new UserIdentityValue( 42, '~2024-1' ),
185 * Create a mock Authority for a registered user with all but $permissions
186 * @param array $permissions
189 private function mockRegisteredAuthorityWithoutPermissions( array $permissions ): Authority
{
190 return $this->mockUserAuthorityWithoutPermissions(
191 new UserIdentityValue( 9999, 'Petr' ),
197 * Create a mock Authority for a $user with all but $permissions
198 * @param UserIdentity $user
199 * @param array $permissions
200 * @param bool $isTemp
203 private function mockUserAuthorityWithoutPermissions(
208 return $this->mockAuthority(
210 static function ( $permission ) use ( $permissions ) {
211 return !in_array( $permission, $permissions );
219 * Create mock Authority for anon user where permissions are determined by $callback.
221 * @param callable $permissionCallback
224 private function mockAnonAuthority( callable
$permissionCallback ): Authority
{
225 return $this->mockAuthority(
226 new UserIdentityValue( 0, '127.0.0.1' ),
232 * Create mock Authority for a temp user where permissions are determined by $callback.
234 * @param callable $permissionCallback
237 private function mockTempAuthority( callable
$permissionCallback ): Authority
{
238 return $this->mockAuthority(
239 new UserIdentityValue( 42, '~2024-1' ),
247 * Create mock Authority for registered user where permissions are determined by $callback.
249 * @param callable $permissionCallback
252 private function mockRegisteredAuthority( callable
$permissionCallback ): Authority
{
253 return $this->mockAuthority(
254 new UserIdentityValue( 9999, 'Petr' ),
260 * Create mock Authority for $user where permissions are determined by $callback.
262 * @param UserIdentity $user
263 * @param callable $permissionCallback ( string $permission, PageIdentity $page = null )
264 * @param Block|null $block
265 * @param bool $isTemp
269 private function mockAuthority(
271 callable
$permissionCallback,
272 ?Block
$block = null,
275 $mock = $this->createMock( Authority
::class );
276 $mock->method( 'getUser' )->willReturn( $user );
277 $methods = [ 'isAllowed', 'probablyCan', 'definitelyCan', 'authorizeRead', 'authorizeWrite' ];
278 foreach ( $methods as $method ) {
279 $mock->method( $method )->willReturnCallback( $permissionCallback );
281 $mock->method( 'isAllowedAny' )
282 ->willReturnCallback( static function ( ...$permissions ) use ( $permissionCallback ) {
283 foreach ( $permissions as $permission ) {
284 if ( $permissionCallback( $permission ) ) {
290 $mock->method( 'isAllowedAll' )
291 ->willReturnCallback( static function ( ...$permissions ) use ( $permissionCallback ) {
292 foreach ( $permissions as $permission ) {
293 if ( !$permissionCallback( $permission ) ) {
299 $mock->method( 'getBlock' )->willReturn( $block );
300 $mock->method( 'isTemp' )->willReturn( $isTemp );
301 $mock->method( 'isNamed' )->willReturn( $user->isRegistered() && !$isTemp );
305 /** @return string[] Some dummy message parameters to test error message formatting. */
306 private function getFakeBlockMessageParams(): array {
308 '[[User:Blocker|Blocker]]',
309 'Block reason that can contain {{templates}}',
316 * @param bool $limited
317 * @return RateLimiter
319 private function newRateLimiter( $limited = false ): RateLimiter
{
320 /** @var RateLimiter&MockObject $rateLimiter */
321 $rateLimiter = $this->createNoOpMock(
323 [ 'limit', 'isLimitable' ]
326 $rateLimiter->method( 'limit' )->willReturn( $limited );
327 $rateLimiter->method( 'isLimitable' )->willReturn( true );
333 * @param string[] $permissions
334 * @return PermissionManager
336 private function newPermissionsManager( array $permissions ): PermissionManager
{
337 /** @var PermissionManager&MockObject $permissionManager */
338 $permissionManager = $this->createNoOpMock(
339 PermissionManager
::class,
345 'getPermissionStatus',
346 'getPermissionErrors',
348 'getApplicableBlock',
349 'newFatalPermissionDeniedStatus',
353 $permissionManager->method( 'userHasRight' )->willReturnCallback(
354 static function ( $user, $permission ) use ( $permissions ) {
355 return in_array( $permission, $permissions );
359 $permissionManager->method( 'userHasAnyRight' )->willReturnCallback(
360 static function ( $user, ...$actions ) use ( $permissions ) {
361 return array_diff( $actions, $permissions ) != $actions;
365 $permissionManager->method( 'userHasAllRights' )->willReturnCallback(
366 static function ( $user, ...$actions ) use ( $permissions ) {
367 return !array_diff( $actions, $permissions );
371 $permissionManager->method( 'userCan' )->willReturnCallback(
372 static function ( $permission, $user ) use ( $permissionManager ) {
373 return $permissionManager->userHasRight( $user, $permission );
377 $fakeBlockMessageParams = $this->getFakeBlockMessageParams();
378 // If the user has a block, the block applies to all actions except for 'read'
379 $permissionManager->method( 'getPermissionStatus' )->willReturnCallback(
380 static function ( $permission, $user, $target ) use ( $permissionManager, $fakeBlockMessageParams ) {
381 $status = PermissionStatus
::newEmpty();
382 if ( !$permissionManager->userCan( $permission, $user, $target ) ) {
383 $status->fatal( 'permissionserrors' );
385 if ( $user->getBlock() && $permission !== 'read' ) {
386 $status->fatal( 'blockedtext-partial', ...$fakeBlockMessageParams );
392 $permissionManager->method( 'getPermissionErrors' )->willReturnCallback(
393 static function ( $permission, $user, $target ) use ( $permissionManager, $fakeBlockMessageParams ) {
394 return $permissionManager
395 ->getPermissionStatus( $permission, $user, $target )
396 ->toLegacyErrorArray();
400 $permissionManager->method( 'newFatalPermissionDeniedStatus' )->willReturnCallback(
401 static function ( $permission, $context ) use ( $permissionManager ) {
402 return StatusValue
::newFatal( 'permissionserrors' );
406 // If the page's title is "Forbidden", will return a SystemBlock. Likewise,
407 // if the action is 'blocked', this will return a SystemBlock.
408 $permissionManager->method( 'getApplicableBlock' )->willReturnCallback(
409 static function ( $action, User
$user, $rigor, $page ) {
410 if ( $page && $page->getDBkey() === 'Forbidden' ) {
411 return new SystemBlock();
414 if ( $action === 'blocked' ) {
415 return new SystemBlock();
422 $permissionManager->method( 'isBlockedFrom' )->willReturnCallback(
423 static function ( User
$user, $page ) {
424 return $page->getDBkey() === 'Forbidden';
428 return $permissionManager;
431 private function newUser( ?Block
$block = null, bool $isTemp = false ): User
{
432 /** @var User&MockObject $actor */
433 $actor = $this->createNoOpMock( User
::class, [ 'getBlock', 'isNewbie', 'toRateLimitSubject' ] );
434 $actor->method( 'getBlock' )->willReturn( $block );
435 $actor->method( 'isNewbie' )->willReturn( false );
436 $actor->method( 'isTemp' )->willReturn( $isTemp );
437 $actor->method( 'isNamed' )->willReturn( !$isTemp );
439 $subject = new RateLimitSubject( $actor, '::1', [] );
440 $actor->method( 'toRateLimitSubject' )->willReturn( $subject );
444 private function newBlockErrorFormatter(): BlockErrorFormatter
{
445 $blockErrorFormatter = $this->createNoOpMock( BlockErrorFormatter
::class, [ 'getMessages' ] );
446 $blockErrorFormatter->method( 'getMessages' )->willReturn( [ new Message( 'blocked' ) ] );
447 return $blockErrorFormatter;
450 private function newContext(): IContextSource
{
451 $language = $this->createNoOpMock( Language
::class, [ 'getCode' ] );
452 $language->method( 'getCode' )->willReturn( 'en' );
454 $context = $this->createNoOpMock( IContextSource
::class, [ 'getLanguage' ] );
455 $context->method( 'getLanguage' )->willReturn( $language );
459 private function newRequest(): WebRequest
{
460 $request = new FauxRequest();
461 $request->setIP( '1.2.3.4' );
465 private function newUserAuthority( array $options = [] ): UserAuthority
{
466 $permissionManager = $options['permissionManager']
467 ??
$this->newPermissionsManager( $options['permissions'] ??
[] );
469 $rateLimiter = $options['rateLimiter']
470 ??
$this->newRateLimiter( $options['limited'] ??
false );
472 $blockErrorFormatter = $options['blockErrorFormatter']
473 ??
$this->newBlockErrorFormatter();
475 return new UserAuthority(
476 $options['actor'] ??
$this->newUser(),
477 $options['request'] ??
$this->newRequest(),
478 $options['context'] ??
$this->newContext(),