+ /**
+ * Testing a permission
+ *
+ * @since 1.34
+ *
+ * @param UserIdentity $user
+ * @param string $action
+ *
+ * @return bool
+ */
+ public function userHasRight( UserIdentity $user, $action = '' ) {
+ if ( $action === '' ) {
+ return true; // In the spirit of DWIM
+ }
+ // Use strict parameter to avoid matching numeric 0 accidentally inserted
+ // by misconfiguration: 0 == 'foo'
+ return in_array( $action, $this->getUserPermissions( $user ), true );
+ }
+
+ /**
+ * Get the permissions this user has.
+ *
+ * @since 1.34
+ *
+ * @param UserIdentity $user
+ *
+ * @return string[] permission names
+ */
+ public function getUserPermissions( UserIdentity $user ) {
+ $user = User::newFromIdentity( $user );
+ if ( !isset( $this->usersRights[ $user->getId() ] ) ) {
+ $this->usersRights[ $user->getId() ] = $this->getGroupPermissions(
+ $user->getEffectiveGroups()
+ );
+ Hooks::run( 'UserGetRights', [ $user, &$this->usersRights[ $user->getId() ] ] );
+
+ // Deny any rights denied by the user's session, unless this
+ // endpoint has no sessions.
+ if ( !defined( 'MW_NO_SESSION' ) ) {
+ // FIXME: $user->getRequest().. need to be replaced with something else
+ $allowedRights = $user->getRequest()->getSession()->getAllowedUserRights();
+ if ( $allowedRights !== null ) {
+ $this->usersRights[ $user->getId() ] = array_intersect(
+ $this->usersRights[ $user->getId() ],
+ $allowedRights
+ );
+ }
+ }
+
+ Hooks::run( 'UserGetRightsRemove', [ $user, &$this->usersRights[ $user->getId() ] ] );
+ // Force reindexation of rights when a hook has unset one of them
+ $this->usersRights[ $user->getId() ] = array_values(
+ array_unique( $this->usersRights[ $user->getId() ] )
+ );
+
+ if (
+ $user->isLoggedIn() &&
+ $this->blockDisablesLogin &&
+ $user->getBlock()
+ ) {
+ $anon = new User;
+ $this->usersRights[ $user->getId() ] = array_intersect(
+ $this->usersRights[ $user->getId() ],
+ $this->getUserPermissions( $anon )
+ );
+ }
+ }
+ return $this->usersRights[ $user->getId() ];
+ }
+
+ /**
+ * Clears users permissions cache, if specific user is provided it tries to clear
+ * permissions cache only for provided user.
+ *
+ * @since 1.34
+ *
+ * @param User|null $user
+ */
+ public function invalidateUsersRightsCache( $user = null ) {
+ if ( $user !== null ) {
+ if ( isset( $this->usersRights[ $user->getId() ] ) ) {
+ unset( $this->usersRights[$user->getId()] );
+ }
+ } else {
+ $this->usersRights = null;
+ }
+ }
+
+ /**
+ * Check, if the given group has the given permission
+ *
+ * If you're wanting to check whether all users have a permission, use
+ * PermissionManager::isEveryoneAllowed() instead. That properly checks if it's revoked
+ * from anyone.
+ *
+ * @since 1.34
+ *
+ * @param string $group Group to check
+ * @param string $role Role to check
+ *
+ * @return bool
+ */
+ public function groupHasPermission( $group, $role ) {
+ return isset( $this->groupPermissions[$group][$role] ) &&
+ $this->groupPermissions[$group][$role] &&
+ !( isset( $this->revokePermissions[$group][$role] ) &&
+ $this->revokePermissions[$group][$role] );
+ }
+
+ /**
+ * Get the permissions associated with a given list of groups
+ *
+ * @since 1.34
+ *
+ * @param array $groups Array of Strings List of internal group names
+ * @return array Array of Strings List of permission key names for given groups combined
+ */
+ public function getGroupPermissions( $groups ) {
+ $rights = [];
+ // grant every granted permission first
+ foreach ( $groups as $group ) {
+ if ( isset( $this->groupPermissions[$group] ) ) {
+ $rights = array_merge( $rights,
+ // array_filter removes empty items
+ array_keys( array_filter( $this->groupPermissions[$group] ) ) );
+ }
+ }
+ // now revoke the revoked permissions
+ foreach ( $groups as $group ) {
+ if ( isset( $this->revokePermissions[$group] ) ) {
+ $rights = array_diff( $rights,
+ array_keys( array_filter( $this->revokePermissions[$group] ) ) );
+ }
+ }
+ return array_unique( $rights );
+ }
+
+ /**
+ * Get all the groups who have a given permission
+ *
+ * @since 1.34
+ *
+ * @param string $role Role to check
+ * @return array Array of Strings List of internal group names with the given permission
+ */
+ public function getGroupsWithPermission( $role ) {
+ $allowedGroups = [];
+ foreach ( array_keys( $this->groupPermissions ) as $group ) {
+ if ( $this->groupHasPermission( $group, $role ) ) {
+ $allowedGroups[] = $group;
+ }
+ }
+ return $allowedGroups;
+ }
+
+ /**
+ * Check if all users may be assumed to have the given permission
+ *
+ * We generally assume so if the right is granted to '*' and isn't revoked
+ * on any group. It doesn't attempt to take grants or other extension
+ * limitations on rights into account in the general case, though, as that
+ * would require it to always return false and defeat the purpose.
+ * Specifically, session-based rights restrictions (such as OAuth or bot
+ * passwords) are applied based on the current session.
+ *
+ * @param string $right Right to check
+ *
+ * @return bool
+ * @since 1.34
+ */
+ public function isEveryoneAllowed( $right ) {
+ // Use the cached results, except in unit tests which rely on
+ // being able change the permission mid-request
+ if ( isset( $this->cachedRights[$right] ) ) {
+ return $this->cachedRights[$right];
+ }
+
+ if ( !isset( $this->groupPermissions['*'][$right] )
+ || !$this->groupPermissions['*'][$right] ) {
+ $this->cachedRights[$right] = false;
+ return false;
+ }
+
+ // If it's revoked anywhere, then everyone doesn't have it
+ foreach ( $this->revokePermissions as $rights ) {
+ if ( isset( $rights[$right] ) && $rights[$right] ) {
+ $this->cachedRights[$right] = false;
+ return false;
+ }
+ }
+
+ // Remove any rights that aren't allowed to the global-session user,
+ // unless there are no sessions for this endpoint.
+ if ( !defined( 'MW_NO_SESSION' ) ) {
+
+ // XXX: think what could be done with the below
+ $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
+ if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
+ $this->cachedRights[$right] = false;
+ return false;
+ }
+ }
+
+ // Allow extensions to say false
+ if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
+ $this->cachedRights[$right] = false;
+ return false;
+ }
+
+ $this->cachedRights[$right] = true;
+ return true;
+ }
+
+ /**
+ * Get a list of all available permissions.
+ *
+ * @since 1.34
+ *
+ * @return string[] Array of permission names
+ */
+ public function getAllPermissions() {
+ if ( $this->allRights === false ) {
+ if ( count( $this->availableRights ) ) {
+ $this->allRights = array_unique( array_merge(
+ $this->coreRights,
+ $this->availableRights
+ ) );
+ } else {
+ $this->allRights = $this->coreRights;
+ }
+ Hooks::run( 'UserGetAllRights', [ &$this->allRights ] );
+ }
+ return $this->allRights;
+ }
+
+ /**
+ * Overrides user permissions cache
+ *
+ * @since 1.34
+ *
+ * @param User $user
+ * @param string[]|string $rights
+ *
+ * @throws Exception
+ */
+ public function overrideUserRightsForTesting( $user, $rights = [] ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new Exception( __METHOD__ . ' can not be called outside of tests' );
+ }
+ $this->usersRights[ $user->getId() ] = is_array( $rights ) ? $rights : [ $rights ];
+ }
+