3 namespace MediaWiki\Tests\Integration\Permissions
;
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
;
30 use TestAllServiceOptionsUsed
;
31 use Wikimedia\ScopedCallback
;
32 use Wikimedia\TestingAccessWrapper
;
35 * For the pure unit tests, see \MediaWiki\Tests\Unit\Permissions\PermissionManagerTest.
38 * @covers \MediaWiki\Permissions\PermissionManager
40 class PermissionManagerTest
extends MediaWikiLangTestCase
{
41 use TestAllServiceOptionsUsed
;
43 use TempUserTestTrait
;
45 protected string $userName;
46 protected Title
$title;
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
{
58 $localOffset = date( 'Z' ) / 60;
60 $this->overrideConfigValues( [
61 MainConfigNames
::Localtimezone
=> $localZone,
62 MainConfigNames
::LocalTZoffset
=> $localOffset,
63 MainConfigNames
::ImplicitRights
=> [
66 MainConfigNames
::RevokePermissions
=> [
71 MainConfigNames
::AvailableRights
=> [
92 $this->setGroupPermissions( [
102 'modifytest' => true,
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,
118 'editinterface' => true,
121 'deletedhistory' => true,
122 'deletedtext' => true,
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
;
173 $this->user
= $this->altUser
;
178 * @dataProvider provideSpecialsAndNSPermissions
180 public function testSpecialsAndNSPermissions(
183 $namespaceProtection,
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 );
202 $permissionManager->getPermissionErrors( 'bogus', $this->user
, $this->title
)
206 $permissionManager->userCan( 'bogus', $this->user
, $this->title
)
210 public static function provideSpecialsAndNSPermissions() {
212 'namespace' => NS_SPECIAL
,
213 'user permissions' => [],
214 'namespace protection' => [],
215 'expected permission errors' => [ [ 'badaccess-group0' ], [ 'ns-specialprotected' ] ],
219 'namespace' => NS_MAIN
,
220 'user permissions' => [ 'bogus' ],
221 'namespace protection' => [],
222 'expected permission errors' => [],
226 'namespace' => NS_MAIN
,
227 'user permissions' => [],
228 'namespace protection' => [],
229 'expected permission errors' => [ [ 'badaccess-group0' ] ],
233 'namespace' => NS_USER
,
234 'user permissions' => [],
235 'namespace protection' => [ NS_USER
=> [ 'bogus' ] ],
236 'expected permission errors' => [ [ 'badaccess-group0' ], [ 'namespaceprotected', 'User', 'bogus' ] ],
240 'namespace' => NS_MEDIAWIKI
,
241 'user permissions' => [ 'bogus' ],
242 'namespace protection' => [],
243 'expected permission errors' => [ [ 'protectedinterface', 'bogus' ] ],
247 'namespace' => NS_MAIN
,
248 'user permissions' => [ 'bogus' ],
249 'namespace protection' => [],
250 'expected permission errors' => [],
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" )
267 "bogus" => [ 'bogus', "sysop", "protect", "" ],
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
) );
283 $permissionManager->getPermissionErrors( 'edit', $this->user
, $this->title
)
288 * @dataProvider provideActionPermissions
290 public function testActionPermissions(
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',
313 'has_cascading' => false,
314 // XXX This is bogus, restrictions won't be empty if there's create protection
315 'restrictions' => [],
318 if ( isset( $titleOverrides['interwiki'] ) ) {
319 $reflectedTitle = TestingAccessWrapper
::newFromObject( $this->title
);
320 $reflectedTitle->mInterwiki
= $titleOverrides['interwiki'];
325 $permissionManager->getPermissionErrors( $action, $this->user
, $this->title
)
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
338 'namespace' => NS_MAIN
,
339 'title overrides' => [],
340 'action' => 'create',
341 'user permissions' => [ 'createpage' ],
342 'expected permission errors' => [ [ 'titleprotected', 'Useruser', 'test' ] ],
346 'namespace' => NS_MAIN
,
347 'title overrides' => [ 'protectedPermission' => 'editprotected' ],
348 'action' => 'create',
349 'user permissions' => [ 'createpage', 'protect' ],
350 'expected permission errors' => [ [ 'titleprotected', 'Useruser', 'test' ] ],
354 'namespace' => NS_MAIN
,
355 'title overrides' => [ 'protectedPermission' => 'editprotected' ],
356 'action' => 'create',
357 'user permissions' => [ 'createpage', 'editprotected' ],
358 'expected permission errors' => [],
362 'namespace' => NS_MEDIA
,
363 'title overrides' => [],
365 'user permissions' => [ 'move' ],
366 'expected permission errors' => [ [ 'immobile-source-namespace', 'Media' ] ],
370 'namespace' => NS_HELP
,
371 'title overrides' => [],
373 'user permissions' => [ 'move' ],
374 'expected permission errors' => [],
378 'namespace' => NS_HELP
,
379 'title overrides' => [ 'interwiki' => 'no' ],
381 'user permissions' => [ 'move' ],
382 'expected permission errors' => [ [ 'immobile-source-page' ] ],
386 'namespace' => NS_MEDIA
,
387 'title overrides' => [],
388 'action' => 'move-target',
389 'user permissions' => [ 'move' ],
390 'expected permission errors' => [ [ 'immobile-target-namespace', 'Media' ] ],
394 'namespace' => NS_HELP
,
395 'title overrides' => [],
396 'action' => 'move-target',
397 'user permissions' => [ 'move' ],
398 'expected permission errors' => [],
402 'namespace' => NS_HELP
,
403 'title overrides' => [ 'interwiki' => 'no' ],
404 'action' => 'move-target',
405 'user permissions' => [ 'move' ],
406 'expected permission errors' => [ [ 'immobile-target-page' ] ],
410 'namespace' => NS_MAIN
,
411 'title overrides' => [],
413 'user permissions' => [ 'createpage', 'edit' ],
414 'expected permission errors' => [ [ 'titleprotected', 'Useruser', 'test' ] ],
418 'namespace' => NS_MAIN
,
419 'title overrides' => [],
421 'user permissions' => [ 'edit' ],
422 'expected permission errors' => [ [ 'nocreate-loggedin' ] ],
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 ],
444 $services = $this->getServiceContainer();
445 $permissionManager = $services->getPermissionManager();
446 $user = $services->getUserFactory()->newAnonymous();
447 $title = $this->getNonexistingTestPage()->getTitle();
448 $this->assertNotEmpty(
449 $permissionManager->getPermissionErrors(
457 $permissionManager->getPermissionErrors(
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,
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, [
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;
500 $permissionManager->getPermissionErrors(
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 ) {
513 $permissionManager->getApplicableBlock(
516 PermissionManager
::RIGOR_FULL
,
520 "Block returned by getApplicableBlock() for action \"$action\""
524 // quickUserCan should ignore user blocks
526 $permissionManager->quickUserCan( 'move-target', $this->user
, $this->title
)
531 * Create a user that is blocked in global state
533 * @param array $options $block
536 private function createUserWithBlock( $options = [] ) {
537 $newUser = new User();
538 $newUser->setId( 12345 );
539 $newUser->setName( 'BlockedUser' );
541 $this->installMockBlockManager( $options, $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' ),
555 $user = $this->createUserWithBlock( $block );
556 $title = Title
::makeTitle( NS_SPECIAL
, 'Blankpage' );
558 $this->overrideUserPermissions( $user, [
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.
569 $permissionManager->getApplicableBlock(
572 PermissionManager
::RIGOR_FULL
,
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' ),
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.
596 $permissionManager->getApplicableBlock(
599 PermissionManager
::RIGOR_FULL
,
606 public static function provideTestCheckUserBlockActions() {
608 'Sitewide autoblock' => [
610 'address' => '127.0.8.1',
611 'by' => new UserIdentityValue( 100, 'TestUser' ),
617 'move-target' => true,
624 'Sitewide block' => [
626 'address' => '127.0.8.1',
627 'by' => new UserIdentityValue( 100, 'TestUser' ),
632 'move-target' => true,
639 'Partial block without restriction against this page' => [
641 'address' => '127.0.8.1',
642 'by' => new UserIdentityValue( 100, 'TestUser' ),
648 'move-target' => false,
655 'Partial block with restriction against this page' => [
657 'address' => '127.0.8.1',
658 'by' => new UserIdentityValue( 100, 'TestUser' ),
664 'move-target' => true,
671 'Partial block with action restriction against uploading' => [
672 ( new DatabaseBlock( [
673 'address' => '127.0.8.1',
674 'by' => UserIdentityValue
::newRegistered( 100, 'Test' ),
676 ] ) )->setRestrictions( [
677 new ActionRestriction( 0, BlockActionInfo
::ACTION_UPLOAD
)
682 'move-target' => false,
691 'address' => '127.0.8.1',
693 'systemBlock' => 'test',
698 'move-target' => true,
710 'move-target' => false,
721 * A test of the filter() calls in getApplicableBlock()
723 public function testGetApplicableBlockCompositeFilter() {
724 $this->overrideConfigValues( [
725 MainConfigNames
::EnablePartialActionBlocks
=> true,
728 'address' => '127.0.8.1',
729 'by' => UserIdentityValue
::newRegistered( 100, 'Test' ),
733 $uploadBlock = new DatabaseBlock( $blockOptions );
734 $uploadBlock->setRestrictions( [
735 new ActionRestriction( 0, BlockActionInfo
::ACTION_UPLOAD
)
738 $emailBlock = new DatabaseBlock(
740 'blockEmail' => true,
745 $page = $this->getExistingTestPage();
746 $page2 = $this->getExistingTestPage( __FUNCTION__
. ' page2' );
747 $pageBlock = new DatabaseBlock( $blockOptions );
748 $pageBlock->setRestrictions( [
749 new PageRestriction( 0, $page->getId() )
752 $compositeBlock = new CompositeBlock( [
753 'originalBlocks' => [
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().
766 [ $uploadBlock, $emailBlock ],
767 $permissionManager->getApplicableBlock(
768 'upload', $user, PermissionManager
::RIGOR_FULL
, null, null
772 // Emailing is only blocked by the email block
775 $permissionManager->getApplicableBlock(
776 'sendemail', $user, PermissionManager
::RIGOR_FULL
, null, null
780 // As for upload, the email block applies to sitewide editing
782 [ $emailBlock, $pageBlock ],
783 $permissionManager->getApplicableBlock(
784 'edit', $user, PermissionManager
::RIGOR_FULL
, $page->getTitle(), null
788 // Test filtering by page -- we use $page2 so $pageBlock does not apply
791 $permissionManager->getApplicableBlock(
792 'edit', $user, PermissionManager
::RIGOR_FULL
, $page2->getTitle(), null
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',
807 'reason' => 'Test reason',
808 'timestamp' => '20000101000000',
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(
829 $expected['message'],
834 public static function provideTestCheckUserBlockMessage() {
836 'Sitewide autoblock' => [
837 DatabaseBlock
::class,
841 'message' => 'autoblockedtext',
844 'Sitewide block' => [
845 DatabaseBlock
::class,
849 'message' => 'blockedtext',
852 'Partial block with restriction against this page' => [
853 DatabaseBlock
::class,
854 [ 'sitewide' => false ],
857 'message' => 'blockedtext-partial',
862 [ 'systemBlock' => 'test' ],
865 'message' => 'systemblockedtext',
872 * @dataProvider provideTestCheckUserBlockEmailConfirmToEdit
874 public function testCheckUserBlockEmailConfirmToEdit( $emailConfirmToEdit, $assertion ) {
875 $this->overrideConfigValues( [
876 MainConfigNames
::EmailConfirmToEdit
=> $emailConfirmToEdit,
877 MainConfigNames
::EmailAuthentication
=> true,
880 $this->overrideUserPermissions( $this->user
, [
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() {
897 'User must confirm email to edit' => [
901 'User may edit without confirming email' => [
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
=> [
929 MainConfigNames
::GroupPermissions
=> [
936 $user = $this->createUserWithBlock( new DatabaseBlock( [
937 'address' => '127.0.8.1',
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 ) );
954 $blocker = $this->getTestSysop()->getUser();
955 $block = new DatabaseBlock( [
957 'allowUsertalk' => false,
958 'reason' => 'Because',
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 ) );
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();
1002 $title = Title
::newFromText( $title );
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,
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() {
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' ] );
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' ] )
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' ] )
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(),
1177 $this->assertTrue( $result, 'right was granted to group, so should be allowed' );
1179 $result = $permissionManager->userHasRight(
1180 $this->getTestUser( 'unittesters' )->getUser(),
1183 $this->assertTrue( $result, 'not granted, but listed as implicit' );
1185 $result = $permissionManager->userHasRight(
1186 $this->getTestUser( 'unittesters' )->getUser(),
1189 $this->assertTrue( $result, 'not granted, but has a limit, so should be allowed' );
1191 $result = $permissionManager->userHasRight(
1192 $this->getTestUser( 'unittesters' )->getUser(),
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(),
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' ) );
1248 $this->assertFalse( $permissionManager->userHasRight( $this->user
, 'move' ) );
1251 public static function provideGetRestrictionLevels() {
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' => [
1262 'autoconfirmed' => [
1263 [ '', 'autoconfirmed' ],
1267 'autoconfirmed revoked' => [
1270 [ 'autoconfirmed', 'noeditsemiprotected' ]
1273 [ '', 'autoconfirmed', 'sysop' ],
1277 'sysop with autoconfirmed revoked (a bit silly)' => [
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 ],
1294 'editsemiprotected' => true,
1295 'editprotected' => true,
1297 'privileged' => [ 'privileged' => true ],
1299 MainConfigNames
::RevokePermissions
=> [
1300 'noeditsemiprotected' => [ 'editsemiprotected' => true ],
1302 MainConfigNames
::NamespaceProtection
=> [
1303 NS_MAIN
=> 'autoconfirmed',
1305 101 => [ 'editsemiprotected', 'privileged' ],
1307 MainConfigNames
::RestrictionLevels
=> [ '', 'autoconfirmed', 'sysop' ],
1308 MainConfigNames
::Autopromote
=> []
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(
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
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
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
1402 * @dataProvider provideDeletedViewerRights
1404 public function testDeletedViewerRights(
1409 $currentUser = $this->getTestUser( $userGroup )->getUser();
1410 $permManager = $this->getServiceContainer()->getPermissionManager();
1411 $targetPage = Title
::makeTitle( NS_MEDIAWIKI
, 'Example' );
1412 foreach ( $userPerms as $userPerm ) {
1415 $permManager->userCan( $userPerm, $currentUser, $targetPage )
1420 public static function provideDeletedViewerRights() {
1423 'user permissions' => [
1434 'usergroup' => 'deleted-viewer',
1435 'user permissions' => [
1443 'usergroup' => 'deleted-viewer',
1444 'user permissions' => [
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' ] )
1463 $user->method( 'getBlock' )
1464 ->willReturn( new DatabaseBlock( [
1465 'address' => '127.0.8.1',
1466 'by' => $this->user
,
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,
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 );
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 ) {
1510 [ 'ignore', 'param' ],
1511 [ 'noignore', 'param' ],
1514 new Message( 'ignore' ),
1518 $this->setTemporaryHook( 'getUserPermissionsErrors', $hookCallback );
1520 $pm = $this->getServiceContainer()->getPermissionManager();
1521 $errors = $pm->getPermissionErrors(
1529 $this->assertSame( [
1530 [ 'noignore', 'param' ],
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 ];
1548 $pm = $this->getServiceContainer()->getPermissionManager();
1550 $errorsStatus = $pm->getPermissionStatus( 'create', $this->user
, $this->title
);
1551 $errorsArray = $pm->getPermissionErrors( 'create', $this->user
, $this->title
);
1555 $errorsStatus->getMessages(),
1556 'getPermissionStatus() preserves ApiMessage objects'
1561 * @dataProvider provideTestCheckQuickPermissions
1563 public function testCheckQuickPermissions(
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' => [
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
1605 $result = PermissionStatus
::newEmpty();
1606 $permissionManager->checkQuickPermissions(
1610 PermissionManager
::RIGOR_QUICK
, // unused
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', ], ''