Merge "Move getRestrictionLevels from NamespaceInfo to PermissionManager."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 23 Aug 2019 11:27:45 +0000 (11:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 23 Aug 2019 11:27:45 +0000 (11:27 +0000)
1  2 
includes/Permissions/PermissionManager.php
includes/skins/SkinTemplate.php

@@@ -68,7 -68,9 +68,9 @@@ class PermissionManager 
                'BlockDisablesLogin',
                'GroupPermissions',
                'RevokePermissions',
-               'AvailableRights'
+               'AvailableRights',
+               'NamespaceProtection',
+               'RestrictionLevels'
        ];
  
        /** @var ServiceOptions */
         * Check restrictions on cascading pages.
         *
         * @param string $action The action to check
 -       * @param User $user User to check
 +       * @param UserIdentity $user User to check
         * @param array $errors List of current errors
         * @param string $rigor One of PermissionManager::RIGOR_ constants
         *   - RIGOR_QUICK  : does cheap permission checks from replica DBs (usable for GUI creation)
         */
        private function checkCascadingSourcesRestrictions(
                $action,
 -              User $user,
 +              UserIdentity $user,
                $errors,
                $rigor,
                $short,
                                        if ( $right == 'autoconfirmed' ) {
                                                $right = 'editsemiprotected';
                                        }
 -                                      if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
 +                                      if ( $right != '' && !$this->userHasAllRights( $user, 'protect', $right ) ) {
                                                $wikiPages = '';
                                                /** @var Title $wikiPage */
                                                foreach ( $cascadingSources as $wikiPage ) {
         * Check CSS/JSON/JS sub-page permissions
         *
         * @param string $action The action to check
 -       * @param User $user User to check
 +       * @param UserIdentity $user User to check
         * @param array $errors List of current errors
         * @param string $rigor One of PermissionManager::RIGOR_ constants
         *   - RIGOR_QUICK  : does cheap permission checks from replica DBs (usable for GUI creation)
         */
        private function checkUserConfigPermissions(
                $action,
 -              User $user,
 +              UserIdentity $user,
                $errors,
                $rigor,
                $short,
                        // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
                        if (
                                $title->isUserCssConfigPage()
 -                              && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
 +                              && !$this->userHasAnyRight( $user, 'editmyusercss', 'editusercss' )
                        ) {
                                $errors[] = [ 'mycustomcssprotected', $action ];
                        } elseif (
                                $title->isUserJsonConfigPage()
 -                              && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
 +                              && !$this->userHasAnyRight( $user, 'editmyuserjson', 'edituserjson' )
                        ) {
                                $errors[] = [ 'mycustomjsonprotected', $action ];
                        } elseif (
                                $title->isUserJsConfigPage()
 -                              && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
 +                              && !$this->userHasAnyRight( $user, 'editmyuserjs', 'edituserjs' )
                        ) {
                                $errors[] = [ 'mycustomjsprotected', $action ];
                        } elseif (
                                $title->isUserJsConfigPage()
 -                              && !$user->isAllowedAny( 'edituserjs', 'editmyuserjsredirect' )
 +                              && !$this->userHasAnyRight( $user, 'edituserjs', 'editmyuserjsredirect' )
                        ) {
                                // T207750 - do not allow users to edit a redirect if they couldn't edit the target
                                $rev = $this->revisionLookup->getRevisionByTitle( $title );
                return in_array( $action, $this->getUserPermissions( $user ), true );
        }
  
 +      /**
 +       * Check if user is allowed to make any action
 +       *
 +       * @param UserIdentity $user
 +       * // TODO: HHVM can't create mocks with variable params @param string ...$actions
 +       * @return bool True if user is allowed to perform *any* of the given actions
 +       * @since 1.34
 +       */
 +      public function userHasAnyRight( UserIdentity $user ) {
 +              $actions = array_slice( func_get_args(), 1 );
 +              foreach ( $actions as $action ) {
 +                      if ( $this->userHasRight( $user, $action ) ) {
 +                              return true;
 +                      }
 +              }
 +              return false;
 +      }
 +
 +      /**
 +       * Check if user is allowed to make all actions
 +       *
 +       * @param UserIdentity $user
 +       * // TODO: HHVM can't create mocks with variable params @param string ...$actions
 +       * @return bool True if user is allowed to perform *all* of the given actions
 +       * @since 1.34
 +       */
 +      public function userHasAllRights( UserIdentity $user ) {
 +              $actions = array_slice( func_get_args(), 1 );
 +              foreach ( $actions as $action ) {
 +                      if ( !$this->userHasRight( $user, $action ) ) {
 +                              return false;
 +                      }
 +              }
 +              return true;
 +      }
 +
        /**
         * Get the permissions this user has.
         *
                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
@@@ -585,7 -585,6 +585,7 @@@ class SkinTemplate extends Skin 
                $request = $this->getRequest();
                $pageurl = $title->getLocalURL();
                $authManager = AuthManager::singleton();
 +              $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
  
                /* set up the default links for the personal toolbar */
                $personal_urls = [];
                        ];
  
                        // No need to show Talk and Contributions to anons if they can't contribute!
 -                      if ( User::groupHasPermission( '*', 'edit' ) ) {
 +                      if ( $permissionManager->groupHasPermission( '*', 'edit' ) ) {
                                // Because of caching, we can't link directly to the IP talk and
                                // contributions pages. Instead we use the special page shortcuts
                                // (which work correctly regardless of caching). This means we can't
                        }
  
                        if ( $authManager->canAuthenticateNow() ) {
 -                              $key = User::groupHasPermission( '*', 'read' )
 +                              $key = $permissionManager->groupHasPermission( '*', 'read' )
                                        ? 'login'
                                        : 'login-private';
                                $personal_urls[$key] = $login_url;
                                }
  
                                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] = [
                                }
  
                                // Checks if the user is logged in
 -                              if ( $this->loggedin && $user->isAllowedAll( 'viewmywatchlist', 'editmywatchlist' ) ) {
 +                              if ( $this->loggedin && MediaWikiServices::getInstance()
 +                                              ->getPermissionManager()
 +                                              ->userHasAllRights( $user, 'viewmywatchlist', 'editmywatchlist' )
 +                              ) {
                                        /**
                                         * The following actions use messages which, if made particular to
                                         * the any specific skins, would break the Ajax code which makes this