Merge "docs: Fix typo"
[mediawiki.git] / tests / phpunit / includes / Permissions / PermissionManagerTest.php
blob6e297c882276cd653b72229de7e8a63ef3ddbdc5
1 <?php
3 namespace MediaWiki\Tests\Integration\Permissions;
5 use Action;
6 use MediaWiki\Api\ApiMessage;
7 use MediaWiki\Block\BlockActionInfo;
8 use MediaWiki\Block\CompositeBlock;
9 use MediaWiki\Block\DatabaseBlock;
10 use MediaWiki\Block\Restriction\ActionRestriction;
11 use MediaWiki\Block\Restriction\NamespaceRestriction;
12 use MediaWiki\Block\Restriction\PageRestriction;
13 use MediaWiki\Block\SystemBlock;
14 use MediaWiki\Cache\CacheKeyHelper;
15 use MediaWiki\Context\RequestContext;
16 use MediaWiki\MainConfigNames;
17 use MediaWiki\Message\Message;
18 use MediaWiki\Permissions\PermissionManager;
19 use MediaWiki\Permissions\PermissionStatus;
20 use MediaWiki\Request\FauxRequest;
21 use MediaWiki\Session\SessionId;
22 use MediaWiki\Tests\Session\TestUtils;
23 use MediaWiki\Tests\Unit\MockBlockTrait;
24 use MediaWiki\Tests\User\TempUser\TempUserTestTrait;
25 use MediaWiki\Title\Title;
26 use MediaWiki\User\User;
27 use MediaWiki\User\UserIdentityValue;
28 use MediaWikiLangTestCase;
29 use stdClass;
30 use TestAllServiceOptionsUsed;
31 use Wikimedia\ScopedCallback;
32 use Wikimedia\TestingAccessWrapper;
34 /**
35 * For the pure unit tests, see \MediaWiki\Tests\Unit\Permissions\PermissionManagerTest.
37 * @group Database
38 * @covers \MediaWiki\Permissions\PermissionManager
40 class PermissionManagerTest extends MediaWikiLangTestCase {
41 use TestAllServiceOptionsUsed;
42 use MockBlockTrait;
43 use TempUserTestTrait;
45 protected string $userName;
46 protected Title $title;
47 protected User $user;
48 protected User $anonUser;
49 protected User $userUser;
50 protected User $altUser;
52 private const USER_TALK_PAGE = '<user talk page>';
54 protected function setUp(): void {
55 parent::setUp();
57 $localZone = 'UTC';
58 $localOffset = date( 'Z' ) / 60;
60 $this->overrideConfigValues( [
61 MainConfigNames::Localtimezone => $localZone,
62 MainConfigNames::LocalTZoffset => $localOffset,
63 MainConfigNames::ImplicitRights => [
64 'limitabletest'
66 MainConfigNames::RevokePermissions => [
67 'formertesters' => [
68 'runtest' => true
71 MainConfigNames::AvailableRights => [
72 'test',
73 'runtest',
74 'writetest',
75 'nukeworld',
76 'modifytest',
77 'editmyoptions',
78 'editinterface',
80 // Interface admin
81 'editsitejs',
82 'edituserjs',
84 // Admin
85 'delete',
86 'undelete',
87 'deletedhistory',
88 'deletedtext',
90 ] );
92 $this->setGroupPermissions( [
93 'unittesters' => [
94 'test' => true,
95 'runtest' => true,
96 'writetest' => false,
97 'nukeworld' => false,
99 'testwriters' => [
100 'test' => true,
101 'writetest' => true,
102 'modifytest' => true,
104 '*' => [
105 'editmyoptions' => true,
107 'deleted-viewer' => [
108 'deletedhistory' => true,
109 'deletedtext' => true,
110 'viewsuppressed' => true,
112 'interface-admin' => [
113 'editinterface' => true,
114 'editsitejs' => true,
115 'edituserjs' => true,
117 'sysop' => [
118 'editinterface' => true,
119 'delete' => true,
120 'undelete' => true,
121 'deletedhistory' => true,
122 'deletedtext' => true,
124 ] );
126 // Without this testUserBlock will use a non-English context on non-English MediaWiki
127 // installations (because of how Title::checkUserBlock is implemented) and fail.
128 RequestContext::resetMain();
130 $this->userName = 'Useruser';
131 $altUserName = 'Altuseruser';
132 date_default_timezone_set( $localZone );
135 * TODO: We should provision title object(s) via providers not in here
136 * in order for us to avoid setting mInterwiki via reflection property.
138 $this->title = Title::makeTitle( NS_MAIN, "Main Page" );
139 if ( !isset( $this->userUser ) || !( $this->userUser instanceof User ) ) {
140 $this->userUser = User::newFromName( $this->userName );
142 if ( !$this->userUser->getId() ) {
143 $this->userUser = User::createNew( $this->userName, [
144 "email" => "test@example.com",
145 "real_name" => "Test User" ] );
146 $this->userUser->load();
149 $this->altUser = User::newFromName( $altUserName );
150 if ( !$this->altUser->getId() ) {
151 $this->altUser = User::createNew( $altUserName, [
152 "email" => "alttest@example.com",
153 "real_name" => "Test User Alt" ] );
154 $this->altUser->load();
157 $this->anonUser = User::newFromId( 0 );
159 $this->user = $this->userUser;
163 protected function setTitle( $ns, $title = "Main_Page" ) {
164 $this->title = Title::makeTitle( $ns, $title );
167 protected function setUser( $userName = null ) {
168 if ( $userName === 'anon' ) {
169 $this->user = $this->anonUser;
170 } elseif ( $userName === null || $userName === $this->userName ) {
171 $this->user = $this->userUser;
172 } else {
173 $this->user = $this->altUser;
178 * @dataProvider provideSpecialsAndNSPermissions
180 public function testSpecialsAndNSPermissions(
181 $namespace,
182 $userPerms,
183 $namespaceProtection,
184 $expectedPermErrors,
185 $expectedUserCan
187 $this->setUser( $this->userName );
188 $this->setTitle( $namespace );
190 $this->mergeMwGlobalArrayValue( 'wgNamespaceProtection', $namespaceProtection );
191 $this->overrideConfigValue(
192 MainConfigNames::NamespaceProtection,
193 $namespaceProtection + [ NS_MEDIAWIKI => 'editinterface' ]
196 $permissionManager = $this->getServiceContainer()->getPermissionManager();
198 $this->overrideUserPermissions( $this->user, $userPerms );
200 $this->assertEquals(
201 $expectedPermErrors,
202 $permissionManager->getPermissionErrors( 'bogus', $this->user, $this->title )
204 $this->assertSame(
205 $expectedUserCan,
206 $permissionManager->userCan( 'bogus', $this->user, $this->title )
210 public static function provideSpecialsAndNSPermissions() {
211 yield [
212 'namespace' => NS_SPECIAL,
213 'user permissions' => [],
214 'namespace protection' => [],
215 'expected permission errors' => [ [ 'badaccess-group0' ], [ 'ns-specialprotected' ] ],
216 'user can' => false,
218 yield [
219 'namespace' => NS_MAIN,
220 'user permissions' => [ 'bogus' ],
221 'namespace protection' => [],
222 'expected permission errors' => [],
223 'user can' => true,
225 yield [
226 'namespace' => NS_MAIN,
227 'user permissions' => [],
228 'namespace protection' => [],
229 'expected permission errors' => [ [ 'badaccess-group0' ] ],
230 'user can' => false,
232 yield [
233 'namespace' => NS_USER,
234 'user permissions' => [],
235 'namespace protection' => [ NS_USER => [ 'bogus' ] ],
236 'expected permission errors' => [ [ 'badaccess-group0' ], [ 'namespaceprotected', 'User', 'bogus' ] ],
237 'user can' => false,
239 yield [
240 'namespace' => NS_MEDIAWIKI,
241 'user permissions' => [ 'bogus' ],
242 'namespace protection' => [],
243 'expected permission errors' => [ [ 'protectedinterface', 'bogus' ] ],
244 'user can' => false,
246 yield [
247 'namespace' => NS_MAIN,
248 'user permissions' => [ 'bogus' ],
249 'namespace protection' => [],
250 'expected permission errors' => [],
251 'user can' => true,
255 public function testCascadingSourcesRestrictions() {
256 $this->setTitle( NS_MAIN, "Test page" );
257 $this->overrideUserPermissions( $this->user, [ "edit", "bogus", 'createpage' ] );
259 $rs = $this->getServiceContainer()->getRestrictionStore();
260 $wrapper = TestingAccessWrapper::newFromObject( $rs );
261 $wrapper->cache = [ CacheKeyHelper::getKeyForPage( $this->title ) => [
262 'cascade_sources' => [
264 Title::makeTitle( NS_MAIN, "Bogus" ),
265 Title::makeTitle( NS_MAIN, "UnBogus" )
266 ], [
267 "bogus" => [ 'bogus', "sysop", "protect", "" ],
270 ] ];
272 $permissionManager = $this->getServiceContainer()->getPermissionManager();
274 $this->assertFalse( $permissionManager->userCan( 'bogus', $this->user, $this->title ) );
275 $this->assertEquals( [
276 [ "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ] ],
277 $permissionManager->getPermissionErrors(
278 'bogus', $this->user, $this->title ) );
280 $this->assertTrue( $permissionManager->userCan( 'edit', $this->user, $this->title ) );
281 $this->assertEquals(
283 $permissionManager->getPermissionErrors( 'edit', $this->user, $this->title )
288 * @dataProvider provideActionPermissions
290 public function testActionPermissions(
291 $namespace,
292 $titleOverrides,
293 $action,
294 $userPerms,
295 $expectedPermErrors,
296 $expectedUserCan
298 $this->setTitle( $namespace, "Test page" );
300 $this->overrideUserPermissions( $this->user, $userPerms );
302 $permissionManager = $this->getServiceContainer()->getPermissionManager();
304 $rs = $this->getServiceContainer()->getRestrictionStore();
305 $wrapper = TestingAccessWrapper::newFromObject( $rs );
306 $wrapper->cache = [ CacheKeyHelper::getKeyForPage( $this->title ) => [
307 'create_protection' => [
308 'permission' => $titleOverrides['protectedPermission'] ?? '',
309 'user' => $this->user->getId(),
310 'expiry' => 'infinity',
311 'reason' => 'test',
313 'has_cascading' => false,
314 // XXX This is bogus, restrictions won't be empty if there's create protection
315 'restrictions' => [],
316 ] ];
318 if ( isset( $titleOverrides['interwiki'] ) ) {
319 $reflectedTitle = TestingAccessWrapper::newFromObject( $this->title );
320 $reflectedTitle->mInterwiki = $titleOverrides['interwiki'];
323 $this->assertEquals(
324 $expectedPermErrors,
325 $permissionManager->getPermissionErrors( $action, $this->user, $this->title )
327 $this->assertSame(
328 $expectedUserCan,
329 $permissionManager->userCan( $action, $this->user, $this->title )
333 public static function provideActionPermissions() {
334 // title overrides can include "protectedPermission" to override
335 // $title->mTitleProtection['permission'], and "interwiki" to override
336 // $title->mInterwiki, for the few cases those are needed
337 yield [
338 'namespace' => NS_MAIN,
339 'title overrides' => [],
340 'action' => 'create',
341 'user permissions' => [ 'createpage' ],
342 'expected permission errors' => [ [ 'titleprotected', 'Useruser', 'test' ] ],
343 'user can' => false,
345 yield [
346 'namespace' => NS_MAIN,
347 'title overrides' => [ 'protectedPermission' => 'editprotected' ],
348 'action' => 'create',
349 'user permissions' => [ 'createpage', 'protect' ],
350 'expected permission errors' => [ [ 'titleprotected', 'Useruser', 'test' ] ],
351 'user can' => false,
353 yield [
354 'namespace' => NS_MAIN,
355 'title overrides' => [ 'protectedPermission' => 'editprotected' ],
356 'action' => 'create',
357 'user permissions' => [ 'createpage', 'editprotected' ],
358 'expected permission errors' => [],
359 'user can' => true,
361 yield [
362 'namespace' => NS_MEDIA,
363 'title overrides' => [],
364 'action' => 'move',
365 'user permissions' => [ 'move' ],
366 'expected permission errors' => [ [ 'immobile-source-namespace', 'Media' ] ],
367 'user can' => false,
369 yield [
370 'namespace' => NS_HELP,
371 'title overrides' => [],
372 'action' => 'move',
373 'user permissions' => [ 'move' ],
374 'expected permission errors' => [],
375 'user can' => true,
377 yield [
378 'namespace' => NS_HELP,
379 'title overrides' => [ 'interwiki' => 'no' ],
380 'action' => 'move',
381 'user permissions' => [ 'move' ],
382 'expected permission errors' => [ [ 'immobile-source-page' ] ],
383 'user can' => false,
385 yield [
386 'namespace' => NS_MEDIA,
387 'title overrides' => [],
388 'action' => 'move-target',
389 'user permissions' => [ 'move' ],
390 'expected permission errors' => [ [ 'immobile-target-namespace', 'Media' ] ],
391 'user can' => false,
393 yield [
394 'namespace' => NS_HELP,
395 'title overrides' => [],
396 'action' => 'move-target',
397 'user permissions' => [ 'move' ],
398 'expected permission errors' => [],
399 'user can' => true,
401 yield [
402 'namespace' => NS_HELP,
403 'title overrides' => [ 'interwiki' => 'no' ],
404 'action' => 'move-target',
405 'user permissions' => [ 'move' ],
406 'expected permission errors' => [ [ 'immobile-target-page' ] ],
407 'user can' => false,
409 yield [
410 'namespace' => NS_MAIN,
411 'title overrides' => [],
412 'action' => 'edit',
413 'user permissions' => [ 'createpage', 'edit' ],
414 'expected permission errors' => [ [ 'titleprotected', 'Useruser', 'test' ] ],
415 'user can' => false,
417 yield [
418 'namespace' => NS_MAIN,
419 'title overrides' => [],
420 'action' => 'edit',
421 'user permissions' => [ 'edit' ],
422 'expected permission errors' => [ [ 'nocreate-loggedin' ] ],
423 'user can' => false,
427 public function testEditActionPermissionWithExistingPage() {
428 $title = $this->getExistingTestPage( 'test page' )->getTitle();
430 $permissionManager = $this->getServiceContainer()->getPermissionManager();
432 $this->overrideUserPermissions( $this->user, [ 'edit' ] );
434 $this->assertSame( [], $permissionManager->getPermissionErrors( 'edit', $this->user, $title ) );
435 $this->assertTrue( $permissionManager->userCan( 'edit', $this->user, $title ) );
438 public function testAutocreatePermissionsHack() {
439 $this->enableAutoCreateTempUser();
440 $this->overrideConfigValue( MainConfigNames::GroupPermissions, [
441 '*' => [ 'edit' => false ],
442 'temp' => [ 'edit' => true, 'createpage' => true ],
443 ] );
444 $services = $this->getServiceContainer();
445 $permissionManager = $services->getPermissionManager();
446 $user = $services->getUserFactory()->newAnonymous();
447 $title = $this->getNonexistingTestPage()->getTitle();
448 $this->assertNotEmpty(
449 $permissionManager->getPermissionErrors(
450 'edit',
451 $user,
452 $title
455 $this->assertSame(
457 $permissionManager->getPermissionErrors(
458 'edit',
459 $user,
460 $title,
461 PermissionManager::RIGOR_QUICK
467 * @dataProvider provideTestCheckUserBlockActions
469 public function testCheckUserBlockActions( $block, $restriction, $expected ) {
470 $this->overrideConfigValues( [
471 MainConfigNames::EmailConfirmToEdit => false,
472 MainConfigNames::EnablePartialActionBlocks => true,
473 ] );
475 if ( $restriction ) {
476 $pageRestriction = new PageRestriction( 0, $this->title->getArticleID() );
477 $pageRestriction->setTitle( $this->title );
478 $block->setRestrictions( [ $pageRestriction ] );
481 $user = $this->createUserWithBlock( $block );
483 $this->overrideUserPermissions( $user, [
484 'createpage',
485 'edit',
486 'move',
487 'rollback',
488 'patrol',
489 'upload',
490 'purge'
491 ] );
493 $permissionManager = $this->getServiceContainer()->getPermissionManager();
495 // Check that user is blocked or unblocked from specific actions using getPermissionErrors
496 foreach ( $expected as $action => $blocked ) {
497 $expectedErrorCount = $blocked ? 1 : 0;
498 $this->assertCount(
499 $expectedErrorCount,
500 $permissionManager->getPermissionErrors(
501 $action,
502 $user,
503 $this->title
505 "Number of permission errors for action \"$action\""
509 // Check that user is blocked or unblocked from specific actions using getApplicableBlock
510 foreach ( $expected as $action => $blocked ) {
511 $this->assertSame(
512 $blocked,
513 $permissionManager->getApplicableBlock(
514 $action,
515 $user,
516 PermissionManager::RIGOR_FULL,
517 $this->title,
518 null
519 ) !== null,
520 "Block returned by getApplicableBlock() for action \"$action\""
524 // quickUserCan should ignore user blocks
525 $this->assertTrue(
526 $permissionManager->quickUserCan( 'move-target', $this->user, $this->title )
531 * Create a user that is blocked in global state
533 * @param array $options $block
534 * @return User
536 private function createUserWithBlock( $options = [] ) {
537 $newUser = new User();
538 $newUser->setId( 12345 );
539 $newUser->setName( 'BlockedUser' );
541 $this->installMockBlockManager( $options, $newUser );
542 return $newUser;
546 * Regression test for T348451
548 public function testGetApplicableBlockForSpecialPage() {
549 $block = new DatabaseBlock( [
550 'address' => '127.0.8.1',
551 'by' => new UserIdentityValue( 100, 'TestUser' ),
552 'auto' => true,
553 ] );
555 $user = $this->createUserWithBlock( $block );
556 $title = Title::makeTitle( NS_SPECIAL, 'Blankpage' );
558 $this->overrideUserPermissions( $user, [
559 'createpage',
560 'edit',
561 ] );
563 $permissionManager = $this->getServiceContainer()->getPermissionManager();
565 // The block is applicable even if the target page is a special page
566 // for which we cannot instantiate an Action object.
567 $this->assertSame(
568 $block,
569 $permissionManager->getApplicableBlock(
570 'edit',
571 $user,
572 PermissionManager::RIGOR_FULL,
573 $title,
574 null
580 * Regression test for T350202
582 public function testGetApplicableBlockForImplicitRight() {
583 $block = new DatabaseBlock( [
584 'address' => '127.0.8.1',
585 'by' => new UserIdentityValue( 100, 'TestUser' ),
586 'auto' => true,
587 ] );
589 $user = $this->createUserWithBlock( $block );
590 $title = Title::makeTitle( NS_MAIN, 'Test' );
592 $permissionManager = $this->getServiceContainer()->getPermissionManager();
594 // The block is not applicable because the purge permission is implicit.
595 $this->assertNull(
596 $permissionManager->getApplicableBlock(
597 'purge',
598 $user,
599 PermissionManager::RIGOR_FULL,
600 $title,
601 null
606 public static function provideTestCheckUserBlockActions() {
607 return [
608 'Sitewide autoblock' => [
609 new DatabaseBlock( [
610 'address' => '127.0.8.1',
611 'by' => new UserIdentityValue( 100, 'TestUser' ),
612 'auto' => true,
613 ] ),
614 false,
616 'edit' => true,
617 'move-target' => true,
618 'rollback' => true,
619 'patrol' => true,
620 'upload' => true,
621 'purge' => false,
624 'Sitewide block' => [
625 new DatabaseBlock( [
626 'address' => '127.0.8.1',
627 'by' => new UserIdentityValue( 100, 'TestUser' ),
628 ] ),
629 false,
631 'edit' => true,
632 'move-target' => true,
633 'rollback' => true,
634 'patrol' => true,
635 'upload' => true,
636 'purge' => false,
639 'Partial block without restriction against this page' => [
640 new DatabaseBlock( [
641 'address' => '127.0.8.1',
642 'by' => new UserIdentityValue( 100, 'TestUser' ),
643 'sitewide' => false,
644 ] ),
645 false,
647 'edit' => false,
648 'move-target' => false,
649 'rollback' => false,
650 'patrol' => false,
651 'upload' => false,
652 'purge' => false,
655 'Partial block with restriction against this page' => [
656 new DatabaseBlock( [
657 'address' => '127.0.8.1',
658 'by' => new UserIdentityValue( 100, 'TestUser' ),
659 'sitewide' => false,
660 ] ),
661 true,
663 'edit' => true,
664 'move-target' => true,
665 'rollback' => true,
666 'patrol' => true,
667 'upload' => false,
668 'purge' => false,
671 'Partial block with action restriction against uploading' => [
672 ( new DatabaseBlock( [
673 'address' => '127.0.8.1',
674 'by' => UserIdentityValue::newRegistered( 100, 'Test' ),
675 'sitewide' => false,
676 ] ) )->setRestrictions( [
677 new ActionRestriction( 0, BlockActionInfo::ACTION_UPLOAD )
678 ] ),
679 false,
681 'edit' => false,
682 'move-target' => false,
683 'rollback' => false,
684 'patrol' => false,
685 'upload' => true,
686 'purge' => false,
689 'System block' => [
690 new SystemBlock( [
691 'address' => '127.0.8.1',
692 'by' => 100,
693 'systemBlock' => 'test',
694 ] ),
695 false,
697 'edit' => true,
698 'move-target' => true,
699 'rollback' => true,
700 'patrol' => true,
701 'upload' => true,
702 'purge' => false,
705 'No block' => [
706 null,
707 false,
709 'edit' => false,
710 'move-target' => false,
711 'rollback' => false,
712 'patrol' => false,
713 'upload' => false,
714 'purge' => false,
721 * A test of the filter() calls in getApplicableBlock()
723 public function testGetApplicableBlockCompositeFilter() {
724 $this->overrideConfigValues( [
725 MainConfigNames::EnablePartialActionBlocks => true,
726 ] );
727 $blockOptions = [
728 'address' => '127.0.8.1',
729 'by' => UserIdentityValue::newRegistered( 100, 'Test' ),
730 'sitewide' => false,
733 $uploadBlock = new DatabaseBlock( $blockOptions );
734 $uploadBlock->setRestrictions( [
735 new ActionRestriction( 0, BlockActionInfo::ACTION_UPLOAD )
736 ] );
738 $emailBlock = new DatabaseBlock(
740 'blockEmail' => true,
741 'sitewide' => true
742 ] + $blockOptions
745 $page = $this->getExistingTestPage();
746 $page2 = $this->getExistingTestPage( __FUNCTION__ . ' page2' );
747 $pageBlock = new DatabaseBlock( $blockOptions );
748 $pageBlock->setRestrictions( [
749 new PageRestriction( 0, $page->getId() )
750 ] );
752 $compositeBlock = new CompositeBlock( [
753 'originalBlocks' => [
754 $uploadBlock,
755 $emailBlock,
756 $pageBlock
758 ] );
759 $user = $this->createUserWithBlock( $compositeBlock );
760 $permissionManager = $this->getServiceContainer()->getPermissionManager();
762 // The email block, being a sitewide block with an additional
763 // blockEmail option, also blocks upload.
764 // assertEquals() gives nicer failure messages than assertSame().
765 $this->assertEquals(
766 [ $uploadBlock, $emailBlock ],
767 $permissionManager->getApplicableBlock(
768 'upload', $user, PermissionManager::RIGOR_FULL, null, null
769 )->toArray()
772 // Emailing is only blocked by the email block
773 $this->assertEquals(
774 [ $emailBlock ],
775 $permissionManager->getApplicableBlock(
776 'sendemail', $user, PermissionManager::RIGOR_FULL, null, null
777 )->toArray()
780 // As for upload, the email block applies to sitewide editing
781 $this->assertEquals(
782 [ $emailBlock, $pageBlock ],
783 $permissionManager->getApplicableBlock(
784 'edit', $user, PermissionManager::RIGOR_FULL, $page->getTitle(), null
785 )->toArray()
788 // Test filtering by page -- we use $page2 so $pageBlock does not apply
789 $this->assertEquals(
790 [ $emailBlock ],
791 $permissionManager->getApplicableBlock(
792 'edit', $user, PermissionManager::RIGOR_FULL, $page2->getTitle(), null
793 )->toArray()
798 * @dataProvider provideTestCheckUserBlockMessage
800 public function testCheckUserBlockMessage( $blockType, $blockParams, $restriction, $expected ) {
801 $this->overrideConfigValue(
802 MainConfigNames::EmailConfirmToEdit, false
804 $block = new $blockType( array_merge( [
805 'address' => '127.0.8.1',
806 'by' => $this->user,
807 'reason' => 'Test reason',
808 'timestamp' => '20000101000000',
809 'expiry' => 0,
810 ], $blockParams ) );
812 if ( $restriction ) {
813 $pageRestriction = new PageRestriction( 0, $this->title->getArticleID() );
814 $pageRestriction->setTitle( $this->title );
815 $block->setRestrictions( [ $pageRestriction ] );
818 $user = $this->createUserWithBlock( $block );
819 $this->overrideUserPermissions( $user, [ 'edit', 'createpage' ] );
821 $permissionManager = $this->getServiceContainer()->getPermissionManager();
822 $errors = $permissionManager->getPermissionErrors(
823 'edit',
824 $user,
825 $this->title
828 $this->assertEquals(
829 $expected['message'],
830 $errors[0][0]
834 public static function provideTestCheckUserBlockMessage() {
835 return [
836 'Sitewide autoblock' => [
837 DatabaseBlock::class,
838 [ 'auto' => true ],
839 false,
841 'message' => 'autoblockedtext',
844 'Sitewide block' => [
845 DatabaseBlock::class,
847 false,
849 'message' => 'blockedtext',
852 'Partial block with restriction against this page' => [
853 DatabaseBlock::class,
854 [ 'sitewide' => false ],
855 true,
857 'message' => 'blockedtext-partial',
860 'System block' => [
861 SystemBlock::class,
862 [ 'systemBlock' => 'test' ],
863 false,
865 'message' => 'systemblockedtext',
872 * @dataProvider provideTestCheckUserBlockEmailConfirmToEdit
874 public function testCheckUserBlockEmailConfirmToEdit( $emailConfirmToEdit, $assertion ) {
875 $this->overrideConfigValues( [
876 MainConfigNames::EmailConfirmToEdit => $emailConfirmToEdit,
877 MainConfigNames::EmailAuthentication => true,
878 ] );
880 $this->overrideUserPermissions( $this->user, [
881 'edit',
882 'move',
883 ] );
885 $permissionManager = $this->getServiceContainer()->getPermissionManager();
887 $this->$assertion( [ 'confirmedittext' ],
888 $permissionManager->getPermissionErrors( 'edit', $this->user, $this->title ) );
890 // $wgEmailConfirmToEdit only applies to 'edit' action
891 $this->assertEquals( [],
892 $permissionManager->getPermissionErrors( 'move-target', $this->user, $this->title ) );
895 public static function provideTestCheckUserBlockEmailConfirmToEdit() {
896 return [
897 'User must confirm email to edit' => [
898 true,
899 'assertContains',
901 'User may edit without confirming email' => [
902 false,
903 'assertNotContains',
909 * Determine that the passed-in permission does not get mixed up with
910 * an action of the same name.
912 public function testCheckUserBlockActionPermission() {
913 $tester = $this->createMock( Action::class );
914 $tester->method( 'getName' )
915 ->willReturn( 'tester' );
916 $tester->method( 'getRestriction' )
917 ->willReturn( 'test' );
918 $tester->method( 'requiresUnblock' )
919 ->willReturn( false );
920 $tester->method( 'requiresWrite' )
921 ->willReturn( false );
922 $tester->method( 'needsReadRights' )
923 ->willReturn( false );
925 $this->overrideConfigValues( [
926 MainConfigNames::Actions => [
927 'tester' => $tester,
929 MainConfigNames::GroupPermissions => [
930 '*' => [
931 'tester' => true,
934 ] );
936 $user = $this->createUserWithBlock( new DatabaseBlock( [
937 'address' => '127.0.8.1',
938 'by' => $this->user,
939 ] ) );
940 $this->assertCount( 1, $this->getServiceContainer()->getPermissionManager()
941 ->getPermissionErrors( 'tester', $user, $this->title )
945 public function testBlockInstanceCache() {
946 // First, check the user isn't blocked
947 $user = $this->getMutableTestUser()->getUser();
948 $ut = Title::makeTitle( NS_USER_TALK, $user->getName() );
949 $this->assertNull( $user->getBlock( false ) );
950 $this->assertFalse( $this->getServiceContainer()->getPermissionManager()
951 ->isBlockedFrom( $user, $ut ) );
953 // Block the user
954 $blocker = $this->getTestSysop()->getUser();
955 $block = new DatabaseBlock( [
956 'hideName' => true,
957 'allowUsertalk' => false,
958 'reason' => 'Because',
959 ] );
960 $block->setTarget( $user );
961 $block->setBlocker( $blocker );
962 $blockStore = $this->getServiceContainer()->getDatabaseBlockStore();
963 $res = $blockStore->insertBlock( $block );
964 $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
966 // Clear cache and confirm it loaded the block properly
967 $user->clearInstanceCache();
968 $this->assertInstanceOf( DatabaseBlock::class, $user->getBlock( false ) );
969 $this->assertTrue( $this->getServiceContainer()->getPermissionManager()
970 ->isBlockedFrom( $user, $ut ) );
972 // Unblock
973 $blockStore->deleteBlock( $block );
975 // Clear cache and confirm it loaded the not-blocked properly
976 $user->clearInstanceCache();
977 $this->assertNull( $user->getBlock( false ) );
978 $this->assertFalse( $this->getServiceContainer()->getPermissionManager()
979 ->isBlockedFrom( $user, $ut ) );
983 * @dataProvider provideIsBlockedFrom
984 * @param string|null $title Title to test.
985 * @param bool $expect Expected result from User::isBlockedFrom()
986 * @param array $options Additional test options:
987 * - 'blockAllowsUTEdit': (bool, default true) Value for $wgBlockAllowsUTEdit
988 * - 'allowUsertalk': (bool, default false) Passed to DatabaseBlock::__construct()
989 * - 'pageRestrictions': (array|null) If non-empty, page restriction titles for the block.
991 public function testIsBlockedFrom( $title, $expect, array $options = [] ) {
992 $this->overrideConfigValue(
993 MainConfigNames::BlockAllowsUTEdit,
994 $options['blockAllowsUTEdit'] ?? true
997 $user = $this->getTestUser()->getUser();
999 if ( $title === self::USER_TALK_PAGE ) {
1000 $title = $user->getTalkPage();
1001 } else {
1002 $title = Title::newFromText( $title );
1005 $restrictions = [];
1006 foreach ( $options['pageRestrictions'] ?? [] as $pagestr ) {
1007 $page = $this->getExistingTestPage(
1008 $pagestr === self::USER_TALK_PAGE ? $user->getTalkPage() : $pagestr
1010 $restrictions[] = new PageRestriction( 0, $page->getId() );
1012 foreach ( $options['namespaceRestrictions'] ?? [] as $ns ) {
1013 $restrictions[] = new NamespaceRestriction( 0, $ns );
1016 $block = new DatabaseBlock( [
1017 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
1018 'allowUsertalk' => $options['allowUsertalk'] ?? false,
1019 'sitewide' => !$restrictions,
1020 ] );
1021 $block->setTarget( $user );
1022 $block->setBlocker( $this->getTestSysop()->getUser() );
1023 if ( $restrictions ) {
1024 $block->setRestrictions( $restrictions );
1026 $blockStore = $this->getServiceContainer()->getDatabaseBlockStore();
1027 $blockStore->insertBlock( $block );
1029 $this->assertSame( $expect, $this->getServiceContainer()->getPermissionManager()
1030 ->isBlockedFrom( $user, $title ) );
1033 public static function provideIsBlockedFrom() {
1034 return [
1035 'Sitewide block, basic operation' => [ 'Test page', true ],
1036 'Sitewide block, not allowing user talk' => [
1037 self::USER_TALK_PAGE, true, [
1038 'allowUsertalk' => false,
1041 'Sitewide block, allowing user talk' => [
1042 self::USER_TALK_PAGE, false, [
1043 'allowUsertalk' => true,
1046 'Sitewide block, allowing user talk but $wgBlockAllowsUTEdit is false' => [
1047 self::USER_TALK_PAGE, true, [
1048 'allowUsertalk' => true,
1049 'blockAllowsUTEdit' => false,
1052 'Partial block, blocking the page' => [
1053 'Test page', true, [
1054 'pageRestrictions' => [ 'Test page' ],
1057 'Partial block, not blocking the page' => [
1058 'Test page 2', false, [
1059 'pageRestrictions' => [ 'Test page' ],
1062 'Partial block, not allowing user talk but user talk page is not blocked' => [
1063 self::USER_TALK_PAGE, false, [
1064 'allowUsertalk' => false,
1065 'pageRestrictions' => [ 'Test page' ],
1068 'Partial block, allowing user talk but user talk page is blocked' => [
1069 self::USER_TALK_PAGE, true, [
1070 'allowUsertalk' => true,
1071 'pageRestrictions' => [ self::USER_TALK_PAGE ],
1074 'Partial block, user talk page is not blocked but $wgBlockAllowsUTEdit is false' => [
1075 self::USER_TALK_PAGE, false, [
1076 'allowUsertalk' => false,
1077 'pageRestrictions' => [ 'Test page' ],
1078 'blockAllowsUTEdit' => false,
1081 'Partial block, user talk page is blocked and $wgBlockAllowsUTEdit is false' => [
1082 self::USER_TALK_PAGE, true, [
1083 'allowUsertalk' => true,
1084 'pageRestrictions' => [ self::USER_TALK_PAGE ],
1085 'blockAllowsUTEdit' => false,
1088 'Partial user talk namespace block, not allowing user talk' => [
1089 self::USER_TALK_PAGE, true, [
1090 'allowUsertalk' => false,
1091 'namespaceRestrictions' => [ NS_USER_TALK ],
1094 'Partial user talk namespace block, allowing user talk' => [
1095 self::USER_TALK_PAGE, false, [
1096 'allowUsertalk' => true,
1097 'namespaceRestrictions' => [ NS_USER_TALK ],
1100 'Partial user talk namespace block, where $wgBlockAllowsUTEdit is false' => [
1101 self::USER_TALK_PAGE, true, [
1102 'allowUsertalk' => true,
1103 'namespaceRestrictions' => [ NS_USER_TALK ],
1104 'blockAllowsUTEdit' => false,
1110 public function testGetUserPermissions() {
1111 $user = $this->getTestUser( [ 'unittesters' ] )->getUser();
1112 $rights = $this->getServiceContainer()->getPermissionManager()
1113 ->getUserPermissions( $user );
1114 $this->assertContains( 'runtest', $rights );
1115 $this->assertNotContains( 'writetest', $rights );
1116 $this->assertNotContains( 'modifytest', $rights );
1117 $this->assertNotContains( 'nukeworld', $rights );
1120 public function testGetUserPermissionsHooks() {
1121 $user = $this->getTestUser( [ 'unittesters', 'testwriters' ] )->getUser();
1122 $userWrapper = TestingAccessWrapper::newFromObject( $user );
1124 $permissionManager = $this->getServiceContainer()->getPermissionManager();
1125 $rights = $permissionManager->getUserPermissions( $user );
1126 $this->assertContains( 'test', $rights );
1127 $this->assertContains( 'runtest', $rights );
1128 $this->assertContains( 'writetest', $rights );
1129 $this->assertNotContains( 'nukeworld', $rights );
1131 // Add a hook manipluating the rights
1132 $this->setTemporaryHook( 'UserGetRights', static function ( $user, &$rights ) {
1133 $rights[] = 'nukeworld';
1134 $rights = array_diff( $rights, [ 'writetest' ] );
1135 } );
1137 $permissionManager->invalidateUsersRightsCache( $user );
1138 $rights = $permissionManager->getUserPermissions( $user );
1139 $this->assertContains( 'test', $rights );
1140 $this->assertContains( 'runtest', $rights );
1141 $this->assertNotContains( 'writetest', $rights );
1142 $this->assertContains( 'nukeworld', $rights );
1144 // Add a Session that limits rights. We're mocking a stdClass because the Session
1145 // class is final, and thus not mockable.
1146 $mock = $this->getMockBuilder( stdClass::class )
1147 ->addMethods( [ 'getAllowedUserRights', 'deregisterSession', 'getSessionId' ] )
1148 ->getMock();
1149 $mock->method( 'getAllowedUserRights' )->willReturn( [ 'test', 'writetest' ] );
1150 $mock->method( 'getSessionId' )->willReturn(
1151 new SessionId( str_repeat( 'X', 32 ) )
1153 $session = TestUtils::getDummySession( $mock );
1154 $mockRequest = $this->getMockBuilder( FauxRequest::class )
1155 ->onlyMethods( [ 'getSession' ] )
1156 ->getMock();
1157 $mockRequest->method( 'getSession' )->willReturn( $session );
1158 $userWrapper->mRequest = $mockRequest;
1160 $this->resetServices();
1161 $rights = $this->getServiceContainer()
1162 ->getPermissionManager()
1163 ->getUserPermissions( $user );
1164 $this->assertContains( 'test', $rights );
1165 $this->assertNotContains( 'runtest', $rights );
1166 $this->assertNotContains( 'writetest', $rights );
1167 $this->assertNotContains( 'nukeworld', $rights );
1170 public function testUserHasRight() {
1171 $permissionManager = $this->getServiceContainer()->getPermissionManager();
1173 $result = $permissionManager->userHasRight(
1174 $this->getTestUser( 'unittesters' )->getUser(),
1175 'test'
1177 $this->assertTrue( $result, 'right was granted to group, so should be allowed' );
1179 $result = $permissionManager->userHasRight(
1180 $this->getTestUser( 'unittesters' )->getUser(),
1181 'limitabletest'
1183 $this->assertTrue( $result, 'not granted, but listed as implicit' );
1185 $result = $permissionManager->userHasRight(
1186 $this->getTestUser( 'unittesters' )->getUser(),
1187 'mailpassword'
1189 $this->assertTrue( $result, 'not granted, but has a limit, so should be allowed' );
1191 $result = $permissionManager->userHasRight(
1192 $this->getTestUser( 'unittesters' )->getUser(),
1193 'rollback'
1195 $this->assertFalse( $result, 'not granted, has a limit but is listed as available, so should not be allowed' );
1197 $result = $permissionManager->userHasRight(
1198 $this->getTestUser( 'formertesters' )->getUser(),
1199 'runtest'
1201 $this->assertFalse( $result, 'not granted, should not be allowed' );
1203 $result = $permissionManager->userHasRight(
1204 $this->getTestUser( 'formertesters' )->getUser(),
1207 $this->assertTrue( $result, 'empty action should always be granted' );
1210 public function testIsEveryoneAllowed() {
1211 $permissionManager = $this->getServiceContainer()->getPermissionManager();
1213 $result = $permissionManager->isEveryoneAllowed( 'editmyoptions' );
1214 $this->assertTrue( $result );
1216 $result = $permissionManager->isEveryoneAllowed( 'test' );
1217 $this->assertFalse( $result );
1220 public function testAddTemporaryUserRights() {
1221 $permissionManager = $this->getServiceContainer()->getPermissionManager();
1222 $this->overrideUserPermissions( $this->user, [ 'read', 'edit' ] );
1224 $this->assertEquals( [ 'read', 'edit' ], $permissionManager->getUserPermissions( $this->user ) );
1225 $this->assertFalse( $permissionManager->userHasRight( $this->user, 'move' ) );
1227 $scope = $permissionManager->addTemporaryUserRights( $this->user, [ 'move', 'delete' ] );
1228 $this->assertEquals( [ 'read', 'edit', 'move', 'delete' ],
1229 $permissionManager->getUserPermissions( $this->user ) );
1230 $this->assertTrue( $permissionManager->userHasRight( $this->user, 'move' ) );
1232 $scope2 = $permissionManager->addTemporaryUserRights( $this->user, [ 'delete', 'upload' ] );
1233 $this->assertEquals( [ 'read', 'edit', 'move', 'delete', 'upload' ],
1234 $permissionManager->getUserPermissions( $this->user ) );
1236 ScopedCallback::consume( $scope );
1237 $this->assertEquals( [ 'read', 'edit', 'delete', 'upload' ],
1238 $permissionManager->getUserPermissions( $this->user ) );
1239 ScopedCallback::consume( $scope2 );
1240 $this->assertEquals( [ 'read', 'edit' ],
1241 $permissionManager->getUserPermissions( $this->user ) );
1242 $this->assertFalse( $permissionManager->userHasRight( $this->user, 'move' ) );
1244 ( function () use ( $permissionManager ) {
1245 $scope = $permissionManager->addTemporaryUserRights( $this->user, 'move' );
1246 $this->assertTrue( $permissionManager->userHasRight( $this->user, 'move' ) );
1247 } )();
1248 $this->assertFalse( $permissionManager->userHasRight( $this->user, 'move' ) );
1251 public static function provideGetRestrictionLevels() {
1252 return [
1253 'No namespace restriction' => [ [ '', 'autoconfirmed', 'sysop' ], NS_TALK ],
1254 'Restricted to autoconfirmed' => [ [ '', 'sysop' ], NS_MAIN ],
1255 'Restricted to sysop' => [ [ '' ], NS_USER ],
1256 'Restricted to someone in two groups' => [ [ '', 'sysop' ], 101 ],
1257 'No special permissions' => [
1258 [ '' ],
1259 NS_TALK,
1262 'autoconfirmed' => [
1263 [ '', 'autoconfirmed' ],
1264 NS_TALK,
1265 [ 'autoconfirmed' ]
1267 'autoconfirmed revoked' => [
1268 [ '' ],
1269 NS_TALK,
1270 [ 'autoconfirmed', 'noeditsemiprotected' ]
1272 'sysop' => [
1273 [ '', 'autoconfirmed', 'sysop' ],
1274 NS_TALK,
1275 [ 'sysop' ]
1277 'sysop with autoconfirmed revoked (a bit silly)' => [
1278 [ '', 'sysop' ],
1279 NS_TALK,
1280 [ 'sysop', 'noeditsemiprotected' ]
1286 * @dataProvider provideGetRestrictionLevels
1288 public function testGetRestrictionLevels( array $expected, $ns, ?array $userGroups = null ) {
1289 $this->overrideConfigValues( [
1290 MainConfigNames::GroupPermissions => [
1291 '*' => [ 'edit' => true ],
1292 'autoconfirmed' => [ 'editsemiprotected' => true ],
1293 'sysop' => [
1294 'editsemiprotected' => true,
1295 'editprotected' => true,
1297 'privileged' => [ 'privileged' => true ],
1299 MainConfigNames::RevokePermissions => [
1300 'noeditsemiprotected' => [ 'editsemiprotected' => true ],
1302 MainConfigNames::NamespaceProtection => [
1303 NS_MAIN => 'autoconfirmed',
1304 NS_USER => 'sysop',
1305 101 => [ 'editsemiprotected', 'privileged' ],
1307 MainConfigNames::RestrictionLevels => [ '', 'autoconfirmed', 'sysop' ],
1308 MainConfigNames::Autopromote => []
1309 ] );
1310 $user = $userGroups === null ? null : $this->getTestUser( $userGroups )->getUser();
1311 $this->assertSame( $expected, $this->getServiceContainer()
1312 ->getPermissionManager()
1313 ->getNamespaceRestrictionLevels( $ns, $user ) );
1316 public function testGetAllPermissions() {
1317 $this->overrideConfigValue( MainConfigNames::AvailableRights, [ 'test_right' ] );
1318 $this->assertContains(
1319 'test_right',
1320 $this->getServiceContainer()
1321 ->getPermissionManager()
1322 ->getAllPermissions()
1326 public function testAnonPermissionsNotClash() {
1327 $user1 = User::newFromName( 'User1' );
1328 $user2 = User::newFromName( 'User2' );
1329 $pm = $this->getServiceContainer()->getPermissionManager();
1330 $pm->overrideUserRightsForTesting( $user2, [] );
1331 $this->assertNotSame( $pm->getUserPermissions( $user1 ), $pm->getUserPermissions( $user2 ) );
1334 public function testAnonPermissionsNotClashOneRegistered() {
1335 $user1 = User::newFromName( 'User1' );
1336 $user2 = $this->getTestSysop()->getUser();
1337 $pm = $this->getServiceContainer()->getPermissionManager();
1338 $this->assertNotSame( $pm->getUserPermissions( $user1 ), $pm->getUserPermissions( $user2 ) );
1342 * Test delete-redirect checks for Special:MovePage
1344 public function testDeleteRedirect() {
1345 $this->editPage( 'ExistentRedirect3', '#REDIRECT [[Existent]]' );
1346 $page = Title::makeTitle( NS_MAIN, 'ExistentRedirect3' );
1347 $pm = $this->getServiceContainer()->getPermissionManager();
1349 $user = $this->getTestUser()->getUser();
1351 $this->assertFalse( $pm->quickUserCan( 'delete-redirect', $user, $page ) );
1353 $pm->overrideUserRightsForTesting( $user, 'delete-redirect' );
1355 $this->assertTrue( $pm->quickUserCan( 'delete-redirect', $user, $page ) );
1356 $this->assertArrayEquals( [], $pm->getPermissionErrors( 'delete-redirect', $user, $page ) );
1360 * Ensure normal admins can view deleted javascript, but not restore it
1361 * See T202989
1363 public function testSysopInterfaceAdminRights() {
1364 $interfaceAdmin = $this->getTestUser( [ 'interface-admin', 'sysop' ] )->getUser();
1365 $admin = $this->getTestSysop()->getUser();
1367 $permManager = $this->getServiceContainer()->getPermissionManager();
1368 $userJs = Title::makeTitle( NS_USER, 'Example/common.js' );
1370 $this->assertTrue( $permManager->userCan( 'delete', $admin, $userJs ) );
1371 $this->assertTrue( $permManager->userCan( 'delete', $interfaceAdmin, $userJs ) );
1372 $this->assertTrue( $permManager->userCan( 'deletedhistory', $admin, $userJs ) );
1373 $this->assertTrue( $permManager->userCan( 'deletedhistory', $interfaceAdmin, $userJs ) );
1374 $this->assertTrue( $permManager->userCan( 'deletedtext', $admin, $userJs ) );
1375 $this->assertTrue( $permManager->userCan( 'deletedtext', $interfaceAdmin, $userJs ) );
1376 $this->assertFalse( $permManager->userCan( 'undelete', $admin, $userJs ) );
1377 $this->assertTrue( $permManager->userCan( 'undelete', $interfaceAdmin, $userJs ) );
1381 * Ensure normal users can watch interface-protected pages
1382 * See T373758
1384 public function testWatchlistingInterface() {
1385 $permManager = $this->getServiceContainer()->getPermissionManager();
1386 $user = $this->user;
1388 $userJs = Title::makeTitle( NS_USER, 'Example/common.js' );
1389 $siteJs = Title::makeTitle( NS_MEDIAWIKI, 'Common.js' );
1390 $interfacePage = Title::makeTitle( NS_MEDIAWIKI, 'Sidebar' );
1392 $this->assertTrue( $permManager->userCan( 'editmywatchlist', $user, $userJs ) );
1393 $this->assertTrue( $permManager->userCan( 'editmywatchlist', $user, $siteJs ) );
1394 $this->assertTrue( $permManager->userCan( 'editmywatchlist', $user, $interfacePage ) );
1398 * Ensure specific users can view deleted contents regardless of Namespace
1399 * Protection, but not restore it
1400 * See T362536
1402 * @dataProvider provideDeletedViewerRights
1404 public function testDeletedViewerRights(
1405 $userGroup,
1406 $userPerms,
1407 $expectedUserCan
1409 $currentUser = $this->getTestUser( $userGroup )->getUser();
1410 $permManager = $this->getServiceContainer()->getPermissionManager();
1411 $targetPage = Title::makeTitle( NS_MEDIAWIKI, 'Example' );
1412 foreach ( $userPerms as $userPerm ) {
1413 $this->assertSame(
1414 $expectedUserCan,
1415 $permManager->userCan( $userPerm, $currentUser, $targetPage )
1420 public static function provideDeletedViewerRights() {
1421 yield [
1422 'usergroup' => '*',
1423 'user permissions' => [
1424 'delete',
1425 'deletedhistory',
1426 'deletedtext',
1427 'suppressrevision',
1428 'undelete',
1429 'viewsuppressed'
1431 'user can' => false
1433 yield [
1434 'usergroup' => 'deleted-viewer',
1435 'user permissions' => [
1436 'delete',
1437 'suppressrevision',
1438 'undelete'
1440 'user can' => false
1442 yield [
1443 'usergroup' => 'deleted-viewer',
1444 'user permissions' => [
1445 'deletedhistory',
1446 'deletedtext',
1447 'viewsuppressed'
1449 'user can' => true
1454 * Regression test for T306358 -- proper page assertion when checking
1455 * blocked status on a special page
1457 public function testBlockedFromNonProperPage() {
1458 $page = Title::makeTitle( NS_SPECIAL, 'Blankpage' );
1459 $pm = $this->getServiceContainer()->getPermissionManager();
1460 $user = $this->getMockBuilder( User::class )
1461 ->onlyMethods( [ 'getBlock' ] )
1462 ->getMock();
1463 $user->method( 'getBlock' )
1464 ->willReturn( new DatabaseBlock( [
1465 'address' => '127.0.8.1',
1466 'by' => $this->user,
1467 ] ) );
1468 $errors = $pm->getPermissionErrors( 'test', $user, $page );
1469 $this->assertNotEmpty( $errors );
1473 * Test interaction with $wgWhitelistRead.
1475 * @dataProvider provideWhitelistRead
1477 public function testWhitelistRead( array $whitelist, string $title, bool $shouldAllow ) {
1478 $this->overrideConfigValues( [
1479 MainConfigNames::LanguageCode => 'es',
1480 MainConfigNames::WhitelistRead => $whitelist,
1481 ] );
1482 $this->setGroupPermissions( '*', 'read', false );
1484 $title = Title::newFromText( $title );
1485 $pm = $this->getServiceContainer()->getPermissionManager();
1486 $errors = $pm->getPermissionErrors( 'read', new User, $title );
1487 if ( $shouldAllow ) {
1488 $this->assertSame( [], $errors );
1489 } else {
1490 $this->assertNotEmpty( $errors );
1494 public static function provideWhitelistRead() {
1495 yield 'no match' => [ [ 'Bar', 'Baz' ], 'Foo', false ];
1496 yield 'match' => [ [ 'Bar', 'Foo', 'Baz' ], 'Foo', true ];
1497 yield 'text form' => [ [ 'Foo bar' ], 'Foo_bar', true ];
1498 yield 'dbkey form' => [ [ 'Foo_bar' ], 'Foo bar', true ];
1499 yield 'local namespace' => [ [ 'Usuario:Foo' ], 'User:Foo', true ];
1500 yield 'legacy mainspace' => [ [ ':Foo' ], 'Foo', true ];
1501 yield 'local special' => [ [ 'Especial:Todas' ], 'Special:Allpages', true ];
1505 * @covers \MediaWiki\Permissions\PermissionManager::getPermissionErrors
1507 public function testGetPermissionErrors_ignoreErrors() {
1508 $hookCallback = static function ( $title, $user, $action, &$result ) {
1509 $result = [
1510 [ 'ignore', 'param' ],
1511 [ 'noignore', 'param' ],
1512 'ignore',
1513 'noignore',
1514 new Message( 'ignore' ),
1516 return false;
1518 $this->setTemporaryHook( 'getUserPermissionsErrors', $hookCallback );
1520 $pm = $this->getServiceContainer()->getPermissionManager();
1521 $errors = $pm->getPermissionErrors(
1522 'read',
1523 $this->user,
1524 $this->title,
1525 $pm::RIGOR_QUICK,
1526 [ 'ignore' ]
1529 $this->assertSame( [
1530 [ 'noignore', 'param' ],
1531 [ 'noignore' ],
1532 ], $errors );
1536 * @covers \MediaWiki\Permissions\PermissionManager::checkQuickPermissions
1538 public function testGetPermissionErrors_objectFromHookResult() {
1539 $msg = ApiMessage::create( 'mymessage', 'mymessagecode', [ 'mydata' => true ] );
1540 $this->setTemporaryHook(
1541 'TitleQuickPermissions',
1542 static function ( $hookTitle, $hookUser, $hookAction, &$errors, $doExpensiveQueries, $short ) use ( $msg ) {
1543 $errors[] = [ $msg ];
1544 return false;
1548 $pm = $this->getServiceContainer()->getPermissionManager();
1550 $errorsStatus = $pm->getPermissionStatus( 'create', $this->user, $this->title );
1551 $errorsArray = $pm->getPermissionErrors( 'create', $this->user, $this->title );
1553 $this->assertSame(
1554 [ $msg ],
1555 $errorsStatus->getMessages(),
1556 'getPermissionStatus() preserves ApiMessage objects'
1561 * @dataProvider provideTestCheckQuickPermissions
1563 public function testCheckQuickPermissions(
1564 int $namespace,
1565 string $pageTitle,
1566 string $userType,
1567 string $action,
1568 array $rights,
1569 string $expectedError
1571 // Convert string single error to the array of errors PermissionManager uses
1572 $expectedErrors = ( $expectedError === '' ? [] : [ [ $expectedError ] ] );
1574 $userIsAnon = $userType === 'anon';
1575 $userIsTemp = $userType === 'temp';
1576 $userIsNamed = $userType === 'user';
1577 $user = $this->createMock( User::class );
1578 $user->method( 'getId' )->willReturn( $userIsAnon ? 0 : 123 );
1579 $user->method( 'getName' )->willReturn( $userIsAnon ? '1.1.1.1' : 'NameOfActingUser' );
1580 $user->method( 'isAnon' )->willReturn( $userIsAnon );
1581 $user->method( 'isNamed' )->willReturn( $userIsNamed );
1582 $user->method( 'isTemp' )->willReturn( $userIsTemp );
1584 // Cannot use setGroupPermissions as this has to clear all the other user groups
1585 // in case `GroupPermissionsLookup::groupHasPermission` is called
1586 $this->overrideConfigValues( [
1587 MainConfigNames::GroupPermissions => [
1588 'autoconfirmed' => [
1589 'move' => true
1592 ] );
1594 $permissionManager = TestingAccessWrapper::newFromObject( $this->getServiceContainer()->getPermissionManager() );
1595 $permissionManager->overrideUserRightsForTesting( $user, $rights );
1597 $title = $this->createMock( Title::class );
1598 $title->method( 'getNamespace' )->willReturn( $namespace );
1599 $title->method( 'getText' )->willReturn( $pageTitle );
1601 // Ensure that `missingPermissionError` doesn't call User::newFatalPermissionDeniedStatus
1602 // which uses the global state
1603 $short = true;
1605 $result = PermissionStatus::newEmpty();
1606 $permissionManager->checkQuickPermissions(
1607 $action,
1608 $user,
1609 $result,
1610 PermissionManager::RIGOR_QUICK, // unused
1611 $short,
1612 $title
1614 $this->assertEquals( $expectedErrors, $result->toLegacyErrorArray() );
1617 public static function provideTestCheckQuickPermissions() {
1618 // $namespace, $pageTitle, $userIsAnon, $action, $rights, $expectedError
1620 // Four different possible errors when trying to create
1621 yield 'Anon createtalk fail' => [
1622 NS_TALK, 'Example', 'anon', 'create', [], 'nocreatetext'
1624 yield 'Anon createpage fail' => [
1625 NS_MAIN, 'Example', 'anon', 'create', [], 'nocreatetext'
1627 yield 'User createtalk fail' => [
1628 NS_TALK, 'Example', 'user', 'create', [], 'nocreate-loggedin'
1630 yield 'User createpage fail' => [
1631 NS_MAIN, 'Example', 'user', 'create', [], 'nocreate-loggedin'
1633 yield 'Temp user createpage fail' => [
1634 NS_MAIN, 'Example', 'temp', 'create', [], 'nocreatetext'
1637 yield 'Createpage pass' => [
1638 NS_MAIN, 'Example', 'anon', 'create', [ 'createpage' ], ''
1641 // Three different namespace specific move failures, even if user has `move` rights
1642 yield 'Move root user page fail' => [
1643 NS_USER, 'Example', 'anon', 'move', [ 'move' ], 'cant-move-user-page'
1645 yield 'Move file fail' => [
1646 NS_FILE, 'Example', 'anon', 'move', [ 'move' ], 'movenotallowedfile'
1648 yield 'Move category fail' => [
1649 NS_CATEGORY, 'Example', 'anon', 'move', [ 'move' ], 'cant-move-category-page'
1652 // No move rights at all. Different failures depending on who is allowed to move.
1653 // Test method sets group permissions to [ 'autoconfirmed' => [ 'move' => true ] ]
1654 yield 'Anon move fail, autoconfirmed can move' => [
1655 NS_TALK, 'Example', 'anon', 'move', [], 'movenologintext'
1657 yield 'User move fail, autoconfirmed can move' => [
1658 NS_TALK, 'Example', 'user', 'move', [], 'movenotallowed'
1660 yield 'Temp user move fail, autoconfirmed can move' => [
1661 NS_TALK, 'Example', 'temp', 'move', [], 'movenologintext'
1663 yield 'Move pass' => [ NS_MAIN, 'Example', 'anon', 'move', [ 'move' ], '' ];
1665 // Three different possible failures for move target
1666 yield 'Move-target no rights' => [
1667 NS_MAIN, 'Example', 'user', 'move-target', [], 'movenotallowed'
1669 yield 'Move-target to user root' => [
1670 NS_USER, 'Example', 'user', 'move-target', [ 'move' ], 'cant-move-to-user-page'
1672 yield 'Move-target to category' => [
1673 NS_CATEGORY, 'Example', 'user', 'move-target', [ 'move' ], 'cant-move-to-category-page'
1675 yield 'Move-target pass' => [
1676 NS_MAIN, 'Example', 'user', 'move-target', [ 'move' ], ''
1679 // Other actions without special handling
1680 yield 'Missing rights for edit' => [
1681 NS_MAIN, 'Example', 'user', 'edit', [], 'badaccess-group0'
1683 yield 'Having rights for edit' => [
1684 NS_MAIN, 'Example', 'user', 'edit', [ 'edit', ], ''