Localisation updates from https://translatewiki.net.
[mediawiki.git] / tests / phpunit / mocks / permissions / MockAuthorityTrait.php
blob275423be8be11c84a2bbebddde1f031facfeb870
1 <?php
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;
25 use StatusValue;
27 /**
28 * Various useful Authority mocks.
29 * @stable to use (since 1.37)
31 trait MockAuthorityTrait {
33 /**
34 * Create mock ultimate Authority for anon user.
36 * @return Authority
38 private function mockAnonUltimateAuthority(): Authority {
39 return new UltimateAuthority( new UserIdentityValue( 0, '127.0.0.1' ) );
42 /**
43 * Create mock ultimate Authority for a temp user.
45 * @return Authority
47 private function mockTempUltimateAuthority(): Authority {
48 return new UltimateAuthority( new UserIdentityValue( 42, '~2024-1' ), true );
51 /**
52 * Create mock ultimate Authority for registered user.
54 * @return Authority
56 private function mockRegisteredUltimateAuthority(): Authority {
57 return new UltimateAuthority( new UserIdentityValue( 9999, 'Petr' ) );
60 /**
61 * Create mock Authority for anon user with no permissions.
63 * @return Authority
65 private function mockAnonNullAuthority(): Authority {
66 return new SimpleAuthority( new UserIdentityValue( 0, '127.0.0.1' ), [] );
69 /**
70 * Create mock Authority for a temp user with no permissions.
72 * @return Authority
74 private function mockTempNullAuthority(): Authority {
75 return new SimpleAuthority( new UserIdentityValue( 42, '~2024-1' ), [], true );
78 /**
79 * Create mock Authority for a registered user with no permissions.
81 * @return Authority
83 private function mockRegisteredNullAuthority(): Authority {
84 return new SimpleAuthority( new UserIdentityValue( 9999, 'Petr' ), [] );
87 /**
88 * Create a mock Authority for anon user with $permissions.
90 * @param array $permissions
91 * @return Authority
93 private function mockAnonAuthorityWithPermissions( array $permissions ): Authority {
94 return new SimpleAuthority( new UserIdentityValue( 0, '127.0.0.1' ), $permissions );
97 /**
98 * Create a mock Authority for a temp user with $permissions.
100 * @param array $permissions
101 * @return Authority
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
111 * @return Authority
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
123 * @return Authority
125 private function mockUserAuthorityWithPermissions(
126 UserIdentity $user,
127 array $permissions,
128 bool $isTemp = false
129 ): Authority {
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
141 * @return Authority
143 private function mockUserAuthorityWithBlock(
144 UserIdentity $user,
145 Block $block,
146 array $permissions = [],
147 bool $isTemp = false
148 ): Authority {
149 return $this->mockAuthority(
150 $user,
151 static function ( $permission ) use ( $permissions ) {
152 return in_array( $permission, $permissions );
154 $block,
155 $isTemp
160 * Create a mock Authority for an anon user with all but $permissions
161 * @param array $permissions
162 * @return Authority
164 private function mockAnonAuthorityWithoutPermissions( array $permissions ): Authority {
165 return $this->mockUserAuthorityWithoutPermissions(
166 new UserIdentityValue( 0, '127.0.0.1' ),
167 $permissions
172 * Create a mock Authority for a temp user with all but $permissions
173 * @param array $permissions
174 * @return Authority
176 private function mockTempAuthorityWithoutPermissions( array $permissions ): Authority {
177 return $this->mockUserAuthorityWithoutPermissions(
178 new UserIdentityValue( 42, '~2024-1' ),
179 $permissions,
180 true
185 * Create a mock Authority for a registered user with all but $permissions
186 * @param array $permissions
187 * @return Authority
189 private function mockRegisteredAuthorityWithoutPermissions( array $permissions ): Authority {
190 return $this->mockUserAuthorityWithoutPermissions(
191 new UserIdentityValue( 9999, 'Petr' ),
192 $permissions
197 * Create a mock Authority for a $user with all but $permissions
198 * @param UserIdentity $user
199 * @param array $permissions
200 * @param bool $isTemp
201 * @return Authority
203 private function mockUserAuthorityWithoutPermissions(
204 UserIdentity $user,
205 array $permissions,
206 bool $isTemp = false
207 ): Authority {
208 return $this->mockAuthority(
209 $user,
210 static function ( $permission ) use ( $permissions ) {
211 return !in_array( $permission, $permissions );
213 null,
214 $isTemp
219 * Create mock Authority for anon user where permissions are determined by $callback.
221 * @param callable $permissionCallback
222 * @return Authority
224 private function mockAnonAuthority( callable $permissionCallback ): Authority {
225 return $this->mockAuthority(
226 new UserIdentityValue( 0, '127.0.0.1' ),
227 $permissionCallback
232 * Create mock Authority for a temp user where permissions are determined by $callback.
234 * @param callable $permissionCallback
235 * @return Authority
237 private function mockTempAuthority( callable $permissionCallback ): Authority {
238 return $this->mockAuthority(
239 new UserIdentityValue( 42, '~2024-1' ),
240 $permissionCallback,
241 null,
242 true
247 * Create mock Authority for registered user where permissions are determined by $callback.
249 * @param callable $permissionCallback
250 * @return Authority
252 private function mockRegisteredAuthority( callable $permissionCallback ): Authority {
253 return $this->mockAuthority(
254 new UserIdentityValue( 9999, 'Petr' ),
255 $permissionCallback
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
267 * @return Authority
269 private function mockAuthority(
270 UserIdentity $user,
271 callable $permissionCallback,
272 ?Block $block = null,
273 bool $isTemp = false
274 ): Authority {
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 ) ) {
285 return true;
288 return false;
289 } );
290 $mock->method( 'isAllowedAll' )
291 ->willReturnCallback( static function ( ...$permissions ) use ( $permissionCallback ) {
292 foreach ( $permissions as $permission ) {
293 if ( !$permissionCallback( $permission ) ) {
294 return false;
297 return true;
298 } );
299 $mock->method( 'getBlock' )->willReturn( $block );
300 $mock->method( 'isTemp' )->willReturn( $isTemp );
301 $mock->method( 'isNamed' )->willReturn( $user->isRegistered() && !$isTemp );
302 return $mock;
305 /** @return string[] Some dummy message parameters to test error message formatting. */
306 private function getFakeBlockMessageParams(): array {
307 return [
308 '[[User:Blocker|Blocker]]',
309 'Block reason that can contain {{templates}}',
310 '192.168.0.1',
311 'Blocker',
316 * @param bool $limited
317 * @return RateLimiter
319 private function newRateLimiter( $limited = false ): RateLimiter {
320 /** @var RateLimiter&MockObject $rateLimiter */
321 $rateLimiter = $this->createNoOpMock(
322 RateLimiter::class,
323 [ 'limit', 'isLimitable' ]
326 $rateLimiter->method( 'limit' )->willReturn( $limited );
327 $rateLimiter->method( 'isLimitable' )->willReturn( true );
329 return $rateLimiter;
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,
341 'userHasRight',
342 'userHasAnyRight',
343 'userHasAllRights',
344 'userCan',
345 'getPermissionStatus',
346 'getPermissionErrors',
347 'isBlockedFrom',
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 );
388 return $status;
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();
418 return null;
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 );
441 return $actor;
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 );
456 return $context;
459 private function newRequest(): WebRequest {
460 $request = new FauxRequest();
461 $request->setIP( '1.2.3.4' );
462 return $request;
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(),
479 $permissionManager,
480 $rateLimiter,
481 $blockErrorFormatter