Move getRestrictionLevels from NamespaceInfo to PermissionManager.
authorPetr Pchelko <ppchelko@wikimedia.org>
Wed, 21 Aug 2019 19:49:59 +0000 (12:49 -0700)
committerPetr Pchelko <ppchelko@wikimedia.org>
Thu, 22 Aug 2019 21:32:38 +0000 (14:32 -0700)
Bug: T11977
Change-Id: I051be9148c98086fdf53a66a74bf7c28699016db

includes/EditPage.php
includes/MWNamespace.php
includes/Permissions/PermissionManager.php
includes/ProtectionForm.php
includes/editpage/TextboxBuilder.php
includes/skins/SkinTemplate.php
includes/title/NamespaceInfo.php
tests/phpunit/includes/Permissions/PermissionManagerTest.php
tests/phpunit/includes/title/NamespaceInfoTest.php

index 550a018..d0a5080 100644 (file)
@@ -4447,8 +4447,8 @@ ERROR;
        protected function addPageProtectionWarningHeaders() {
                $out = $this->context->getOutput();
                if ( $this->mTitle->isProtected( 'edit' ) &&
-                       MediaWikiServices::getInstance()->getNamespaceInfo()->getRestrictionLevels(
-                               $this->mTitle->getNamespace()
+                       MediaWikiServices::getInstance()->getPermissionManager()->getNamespaceRestrictionLevels(
+                               $this->getTitle()->getNamespace()
                        ) !== [ '' ]
                ) {
                        # Is the title semi-protected?
index 0121bd5..4a911b0 100644 (file)
@@ -318,8 +318,9 @@ class MWNamespace {
         * @return array
         */
        public static function getRestrictionLevels( $index, User $user = null ) {
-               return MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       getRestrictionLevels( $index, $user );
+               return MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->getNamespaceRestrictionLevels( $index, $user );
        }
 
        /**
index 2d4885e..dd21a93 100644 (file)
@@ -68,7 +68,9 @@ class PermissionManager {
                'BlockDisablesLogin',
                'GroupPermissions',
                'RevokePermissions',
-               'AvailableRights'
+               'AvailableRights',
+               'NamespaceProtection',
+               'RestrictionLevels'
        ];
 
        /** @var ServiceOptions */
@@ -1415,6 +1417,85 @@ class PermissionManager {
                return $this->allRights;
        }
 
+       /**
+        * Determine which restriction levels it makes sense to use in a namespace,
+        * optionally filtered by a user's rights.
+        *
+        * @param int $index Index to check
+        * @param UserIdentity|null $user User to check
+        * @return array
+        */
+       public function getNamespaceRestrictionLevels( $index, UserIdentity $user = null ) {
+               if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
+                       // All levels are valid if there's no namespace restriction.
+                       // But still filter by user, if necessary
+                       $levels = $this->options->get( 'RestrictionLevels' );
+                       if ( $user ) {
+                               $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
+                                       $right = $level;
+                                       if ( $right == 'sysop' ) {
+                                               $right = 'editprotected'; // BC
+                                       }
+                                       if ( $right == 'autoconfirmed' ) {
+                                               $right = 'editsemiprotected'; // BC
+                                       }
+                                       return $this->userHasRight( $user, $right );
+                               } ) );
+                       }
+                       return $levels;
+               }
+
+               // $wgNamespaceProtection can require one or more rights to edit the namespace, which
+               // may be satisfied by membership in multiple groups each giving a subset of those rights.
+               // A restriction level is redundant if, for any one of the namespace rights, all groups
+               // giving that right also give the restriction level's right. Or, conversely, a
+               // restriction level is not redundant if, for every namespace right, there's at least one
+               // group giving that right without the restriction level's right.
+               //
+               // First, for each right, get a list of groups with that right.
+               $namespaceRightGroups = [];
+               foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
+                       if ( $right == 'sysop' ) {
+                               $right = 'editprotected'; // BC
+                       }
+                       if ( $right == 'autoconfirmed' ) {
+                               $right = 'editsemiprotected'; // BC
+                       }
+                       if ( $right != '' ) {
+                               $namespaceRightGroups[$right] = $this->getGroupsWithPermission( $right );
+                       }
+               }
+
+               // Now, go through the protection levels one by one.
+               $usableLevels = [ '' ];
+               foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
+                       $right = $level;
+                       if ( $right == 'sysop' ) {
+                               $right = 'editprotected'; // BC
+                       }
+                       if ( $right == 'autoconfirmed' ) {
+                               $right = 'editsemiprotected'; // BC
+                       }
+
+                       if ( $right != '' &&
+                                !isset( $namespaceRightGroups[$right] ) &&
+                                ( !$user || $this->userHasRight( $user, $right ) )
+                       ) {
+                               // Do any of the namespace rights imply the restriction right? (see explanation above)
+                               foreach ( $namespaceRightGroups as $groups ) {
+                                       if ( !array_diff( $groups, $this->getGroupsWithPermission( $right ) ) ) {
+                                               // Yes, this one does.
+                                               continue 2;
+                                       }
+                               }
+                               // No, keep the restriction level
+                               $usableLevels[] = $level;
+                       }
+               }
+
+               return $usableLevels;
+       }
+
        /**
         * Add temporary user rights, only valid for the current scope.
         * This is meant for making it possible to programatically trigger certain actions that
index adca805..8b5d995 100644 (file)
@@ -90,7 +90,7 @@ class ProtectionForm {
         * Loads the current state of protection into the object.
         */
        function loadData() {
-               $levels = MediaWikiServices::getInstance()->getNamespaceInfo()->getRestrictionLevels(
+               $levels = MediaWikiServices::getInstance()->getPermissionManager()->getNamespaceRestrictionLevels(
                        $this->mTitle->getNamespace(), $this->mContext->getUser()
                );
                $this->mCascade = $this->mTitle->areRestrictionsCascading();
@@ -180,7 +180,7 @@ class ProtectionForm {
         */
        function execute() {
                if (
-                       MediaWikiServices::getInstance()->getNamespaceInfo()->getRestrictionLevels(
+                       MediaWikiServices::getInstance()->getPermissionManager()->getNamespaceRestrictionLevels(
                                $this->mTitle->getNamespace()
                        ) === [ '' ]
                ) {
@@ -586,10 +586,12 @@ class ProtectionForm {
        function buildSelector( $action, $selected ) {
                // If the form is disabled, display all relevant levels. Otherwise,
                // just show the ones this user can use.
-               $levels = MediaWikiServices::getInstance()->getNamespaceInfo()->getRestrictionLevels(
-                       $this->mTitle->getNamespace(),
-                       $this->disabled ? null : $this->mContext->getUser()
-               );
+               $levels = MediaWikiServices::getInstance()
+                               ->getPermissionManager()
+                               ->getNamespaceRestrictionLevels(
+                                       $this->mTitle->getNamespace(),
+                                       $this->disabled ? null : $this->mContext->getUser()
+                               );
 
                $id = 'mwProtect-level-' . $action;
 
index 103b3e5..8161251 100644 (file)
@@ -75,8 +75,8 @@ class TextboxBuilder {
        public function getTextboxProtectionCSSClasses( Title $title ) {
                $classes = []; // Textarea CSS
                if ( $title->isProtected( 'edit' ) &&
-                       MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       getRestrictionLevels( $title->getNamespace() ) !== [ '' ]
+                       MediaWikiServices::getInstance()->getPermissionManager()
+                               ->getNamespaceRestrictionLevels( $title->getNamespace() ) !== [ '' ]
                ) {
                        # Is the title semi-protected?
                        if ( $title->isSemiProtected() ) {
index af7ec29..a31f2a3 100644 (file)
@@ -1068,8 +1068,8 @@ class SkinTemplate extends Skin {
                                }
 
                                if ( $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() &&
-                                       MediaWikiServices::getInstance()->getNamespaceInfo()->
-                                               getRestrictionLevels( $title->getNamespace(), $user ) !== [ '' ]
+                                       MediaWikiServices::getInstance()->getPermissionManager()
+                                               ->getNamespaceRestrictionLevels( $title->getNamespace(), $user ) !== [ '' ]
                                ) {
                                        $mode = $title->isProtected() ? 'unprotect' : 'protect';
                                        $content_navigation['actions'][$mode] = [
index 7307cc1..105eeaa 100644 (file)
@@ -22,6 +22,7 @@
 
 use MediaWiki\Config\ServiceOptions;
 use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
 
 /**
  * This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of
@@ -93,10 +94,8 @@ class NamespaceInfo {
                'ExtraNamespaces',
                'ExtraSignatureNamespaces',
                'NamespaceContentModels',
-               'NamespaceProtection',
                'NamespacesWithSubpages',
                'NonincludableNamespaces',
-               'RestrictionLevels',
        ];
 
        /**
@@ -572,82 +571,18 @@ class NamespaceInfo {
         * Determine which restriction levels it makes sense to use in a namespace,
         * optionally filtered by a user's rights.
         *
-        * @todo Move this to PermissionManager and remove the dependency here on permissions-related
-        * config settings.
-        *
+        * @deprecated since 1.34 User PermissionManager::getNamespaceRestrictionLevels instead.
         * @param int $index Index to check
         * @param User|null $user User to check
         * @return array
         */
        public function getRestrictionLevels( $index, User $user = null ) {
-               if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
-                       // All levels are valid if there's no namespace restriction.
-                       // But still filter by user, if necessary
-                       $levels = $this->options->get( 'RestrictionLevels' );
-                       if ( $user ) {
-                               $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
-                                       $right = $level;
-                                       if ( $right == 'sysop' ) {
-                                               $right = 'editprotected'; // BC
-                                       }
-                                       if ( $right == 'autoconfirmed' ) {
-                                               $right = 'editsemiprotected'; // BC
-                                       }
-                                       return ( $right == '' || $user->isAllowed( $right ) );
-                               } ) );
-                       }
-                       return $levels;
-               }
-
-               // $wgNamespaceProtection can require one or more rights to edit the namespace, which
-               // may be satisfied by membership in multiple groups each giving a subset of those rights.
-               // A restriction level is redundant if, for any one of the namespace rights, all groups
-               // giving that right also give the restriction level's right. Or, conversely, a
-               // restriction level is not redundant if, for every namespace right, there's at least one
-               // group giving that right without the restriction level's right.
-               //
-               // First, for each right, get a list of groups with that right.
-               $namespaceRightGroups = [];
-               foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
-                       if ( $right == 'sysop' ) {
-                               $right = 'editprotected'; // BC
-                       }
-                       if ( $right == 'autoconfirmed' ) {
-                               $right = 'editsemiprotected'; // BC
-                       }
-                       if ( $right != '' ) {
-                               $namespaceRightGroups[$right] = User::getGroupsWithPermission( $right );
-                       }
-               }
-
-               // Now, go through the protection levels one by one.
-               $usableLevels = [ '' ];
-               foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
-                       $right = $level;
-                       if ( $right == 'sysop' ) {
-                               $right = 'editprotected'; // BC
-                       }
-                       if ( $right == 'autoconfirmed' ) {
-                               $right = 'editsemiprotected'; // BC
-                       }
-
-                       if ( $right != '' &&
-                               !isset( $namespaceRightGroups[$right] ) &&
-                               ( !$user || $user->isAllowed( $right ) )
-                       ) {
-                               // Do any of the namespace rights imply the restriction right? (see explanation above)
-                               foreach ( $namespaceRightGroups as $groups ) {
-                                       if ( !array_diff( $groups, User::getGroupsWithPermission( $right ) ) ) {
-                                               // Yes, this one does.
-                                               continue 2;
-                                       }
-                               }
-                               // No, keep the restriction level
-                               $usableLevels[] = $level;
-                       }
-               }
-
-               return $usableLevels;
+               // PermissionManager is not injected because adding an explicit dependency
+               // breaks MW installer by adding a dependency chain on the database before
+               // it was set up. Also, the method is deprecated and will be soon removed.
+               return MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->getNamespaceRestrictionLevels( $index, $user );
        }
 
        /**
index 3c5f43b..88847e2 100644 (file)
@@ -15,6 +15,7 @@ use MediaWiki\MediaWikiServices;
 use MediaWiki\Permissions\PermissionManager;
 use MediaWiki\Revision\MutableRevisionRecord;
 use MediaWiki\Revision\RevisionLookup;
+use MWException;
 use TestAllServiceOptionsUsed;
 use Wikimedia\ScopedCallback;
 use MediaWiki\Session\SessionId;
@@ -738,7 +739,9 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                                        'BlockDisablesLogin' => false,
                                        'GroupPermissions' => [],
                                        'RevokePermissions' => [],
-                                       'AvailableRights' => []
+                                       'AvailableRights' => [],
+                                       'NamespaceProtection' => [],
+                                       'RestrictionLevels' => []
                                ]
                        ),
                        $services->getSpecialPageFactory(),
@@ -1788,4 +1791,75 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                return $revision;
        }
 
+       public function provideGetRestrictionLevels() {
+               return [
+                       'No namespace restriction' => [ [ '', 'autoconfirmed', 'sysop' ], NS_TALK ],
+                       'Restricted to autoconfirmed' => [ [ '', 'sysop' ], NS_MAIN ],
+                       'Restricted to sysop' => [ [ '' ], NS_USER ],
+                       'Restricted to someone in two groups' => [ [ '', 'sysop' ], 101 ],
+                       'No special permissions' => [
+                               [ '' ],
+                               NS_TALK,
+                               []
+                       ],
+                       'autoconfirmed' => [
+                               [ '', 'autoconfirmed' ],
+                               NS_TALK,
+                               [ 'autoconfirmed' ]
+                       ],
+                       'autoconfirmed revoked' => [
+                               [ '' ],
+                               NS_TALK,
+                               [ 'autoconfirmed', 'noeditsemiprotected' ]
+                       ],
+                       'sysop' => [
+                               [ '', 'autoconfirmed', 'sysop' ],
+                               NS_TALK,
+                               [ 'sysop' ]
+                       ],
+                       'sysop with autoconfirmed revoked (a bit silly)' => [
+                               [ '', 'sysop' ],
+                               NS_TALK,
+                               [ 'sysop', 'noeditsemiprotected' ]
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetRestrictionLevels
+        * @covers       \MediaWiki\Permissions\PermissionManager::getNamespaceRestrictionLevels
+        *
+        * @param array $expected
+        * @param int $ns
+        * @param array|null $userGroups
+        * @throws MWException
+        */
+       public function testGetRestrictionLevels( array $expected, $ns, array $userGroups = null ) {
+               $this->setMwGlobals( [
+                       'wgGroupPermissions' => [
+                               '*' => [ 'edit' => true ],
+                               'autoconfirmed' => [ 'editsemiprotected' => true ],
+                               'sysop' => [
+                                       'editsemiprotected' => true,
+                                       'editprotected' => true,
+                               ],
+                               'privileged' => [ 'privileged' => true ],
+                       ],
+                       'wgRevokePermissions' => [
+                               'noeditsemiprotected' => [ 'editsemiprotected' => true ],
+                       ],
+                       'wgNamespaceProtection' => [
+                               NS_MAIN => 'autoconfirmed',
+                               NS_USER => 'sysop',
+                               101 => [ 'editsemiprotected', 'privileged' ],
+                       ],
+                       'wgRestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
+                       'wgAutopromote' => []
+               ] );
+               $this->resetServices();
+               $user = is_null( $userGroups ) ? null : $this->getTestUser( $userGroups )->getUser();
+               $this->assertSame( $expected, MediaWikiServices::getInstance()
+                       ->getPermissionManager()
+                       ->getNamespaceRestrictionLevels( $ns, $user ) );
+       }
 }
index 028c438..7f97a16 100644 (file)
@@ -54,14 +54,12 @@ class NamespaceInfoTest extends MediaWikiTestCase {
                'ExtraNamespaces' => [],
                'ExtraSignatureNamespaces' => [],
                'NamespaceContentModels' => [],
-               'NamespaceProtection' => [],
                'NamespacesWithSubpages' => [
                        NS_TALK => true,
                        NS_USER => true,
                        NS_USER_TALK => true,
                ],
                'NonincludableNamespaces' => [],
-               'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
        ];
 
        private function newObj( array $options = [] ) : NamespaceInfo {
@@ -1245,53 +1243,17 @@ class NamespaceInfoTest extends MediaWikiTestCase {
         */
 
        /**
-        * This mock user can only have isAllowed() called on it.
-        *
-        * @param array $groups Groups for the mock user to have
-        * @return User
-        */
-       private function getMockUser( array $groups = [] ) : User {
-               $groups[] = '*';
-
-               $mock = $this->createMock( User::class );
-               $mock->method( 'isAllowed' )->will( $this->returnCallback(
-                       function ( $action ) use ( $groups ) {
-                               global $wgGroupPermissions, $wgRevokePermissions;
-                               if ( $action == '' ) {
-                                       return true;
-                               }
-                               foreach ( $wgRevokePermissions as $group => $rights ) {
-                                       if ( !in_array( $group, $groups ) ) {
-                                               continue;
-                                       }
-                                       if ( isset( $rights[$action] ) && $rights[$action] ) {
-                                               return false;
-                                       }
-                               }
-                               foreach ( $wgGroupPermissions as $group => $rights ) {
-                                       if ( !in_array( $group, $groups ) ) {
-                                               continue;
-                                       }
-                                       if ( isset( $rights[$action] ) && $rights[$action] ) {
-                                               return true;
-                                       }
-                               }
-                               return false;
-                       }
-               ) );
-               $mock->expects( $this->never() )->method( $this->anythingBut( 'isAllowed' ) );
-               return $mock;
-       }
-
-       /**
+        * TODO: This is superceeded by PermissionManagerTest::testGetNamespaceRestrictionLevels
+        * Remove when deprecated method is removed.
         * @dataProvider provideGetRestrictionLevels
-        * @covers NamespaceInfo::getRestrictionLevels
+        * @covers       NamespaceInfo::getRestrictionLevels
         *
         * @param array $expected
         * @param int $ns
-        * @param User|null $user
+        * @param array|null $groups
+        * @throws MWException
         */
-       public function testGetRestrictionLevels( array $expected, $ns, User $user = null ) {
+       public function testGetRestrictionLevels( array $expected, $ns, array $groups = null ) {
                $this->setMwGlobals( [
                        'wgGroupPermissions' => [
                                '*' => [ 'edit' => true ],
@@ -1305,14 +1267,17 @@ class NamespaceInfoTest extends MediaWikiTestCase {
                        'wgRevokePermissions' => [
                                'noeditsemiprotected' => [ 'editsemiprotected' => true ],
                        ],
-               ] );
-               $obj = $this->newObj( [
-                       'NamespaceProtection' => [
+                       'wgNamespaceProtection' => [
                                NS_MAIN => 'autoconfirmed',
                                NS_USER => 'sysop',
                                101 => [ 'editsemiprotected', 'privileged' ],
                        ],
+                       'wgRestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
+                       'wgAutopromote' => []
                ] );
+               $this->resetServices();
+               $obj = $this->newObj();
+               $user = is_null( $groups ) ? null : $this->getTestUser( $groups )->getUser();
                $this->assertSame( $expected, $obj->getRestrictionLevels( $ns, $user ) );
        }
 
@@ -1322,26 +1287,26 @@ class NamespaceInfoTest extends MediaWikiTestCase {
                        'Restricted to autoconfirmed' => [ [ '', 'sysop' ], NS_MAIN ],
                        'Restricted to sysop' => [ [ '' ], NS_USER ],
                        'Restricted to someone in two groups' => [ [ '', 'sysop' ], 101 ],
-                       'No special permissions' => [ [ '' ], NS_TALK, $this->getMockUser() ],
+                       'No special permissions' => [ [ '' ], NS_TALK, [] ],
                        'autoconfirmed' => [
                                [ '', 'autoconfirmed' ],
                                NS_TALK,
-                               $this->getMockUser( [ 'autoconfirmed' ] )
+                               [ 'autoconfirmed' ]
                        ],
                        'autoconfirmed revoked' => [
                                [ '' ],
                                NS_TALK,
-                               $this->getMockUser( [ 'autoconfirmed', 'noeditsemiprotected' ] )
+                               [ 'autoconfirmed', 'noeditsemiprotected' ]
                        ],
                        'sysop' => [
                                [ '', 'autoconfirmed', 'sysop' ],
                                NS_TALK,
-                               $this->getMockUser( [ 'sysop' ] )
+                               [ 'sysop' ]
                        ],
                        'sysop with autoconfirmed revoked (a bit silly)' => [
                                [ '', 'sysop' ],
                                NS_TALK,
-                               $this->getMockUser( [ 'sysop', 'noeditsemiprotected' ] )
+                               [ 'sysop', 'noeditsemiprotected' ]
                        ],
                ];
        }