use Action;
use Exception;
use Hooks;
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Revision\RevisionLookup;
use MediaWiki\Revision\RevisionRecord;
/** @var string Does cheap and expensive checks, using the master as needed */
const RIGOR_SECURE = 'secure';
+ /**
+ * TODO Make this const when HHVM support is dropped (T192166)
+ *
+ * @since 1.34
+ * @var array
+ */
+ public static $constructorOptions = [
+ 'WhitelistRead',
+ 'WhitelistReadRegexp',
+ 'EmailConfirmToEdit',
+ 'BlockDisablesLogin',
+ 'GroupPermissions',
+ 'RevokePermissions',
+ 'AvailableRights',
+ 'NamespaceProtection',
+ 'RestrictionLevels'
+ ];
+
+ /** @var ServiceOptions */
+ private $options;
+
/** @var SpecialPageFactory */
private $specialPageFactory;
/** @var RevisionLookup */
private $revisionLookup;
- /** @var string[] List of pages names anonymous user may see */
- private $whitelistRead;
-
- /** @var string[] Whitelists publicly readable titles with regular expressions */
- private $whitelistReadRegexp;
-
- /** @var bool Require users to confirm email address before they can edit */
- private $emailConfirmToEdit;
-
- /** @var bool If set to true, blocked users will no longer be allowed to log in */
- private $blockDisablesLogin;
-
/** @var NamespaceInfo */
private $nsInfo;
- /** @var string[][] Access rights for groups and users in these groups */
- private $groupPermissions;
-
- /** @var string[][] Permission keys revoked from users in each group */
- private $revokePermissions;
-
- /** @var string[] A list of available rights, in addition to the ones defined by the core */
- private $availableRights;
-
/** @var string[] Cached results of getAllRights() */
private $allRights = false;
];
/**
+ * @param ServiceOptions $options
* @param SpecialPageFactory $specialPageFactory
* @param RevisionLookup $revisionLookup
- * @param string[] $whitelistRead
- * @param string[] $whitelistReadRegexp
- * @param bool $emailConfirmToEdit
- * @param bool $blockDisablesLogin
- * @param string[][] $groupPermissions
- * @param string[][] $revokePermissions
- * @param string[] $availableRights
* @param NamespaceInfo $nsInfo
*/
public function __construct(
+ ServiceOptions $options,
SpecialPageFactory $specialPageFactory,
RevisionLookup $revisionLookup,
- $whitelistRead,
- $whitelistReadRegexp,
- $emailConfirmToEdit,
- $blockDisablesLogin,
- $groupPermissions,
- $revokePermissions,
- $availableRights,
NamespaceInfo $nsInfo
) {
+ $options->assertRequiredOptions( self::$constructorOptions );
+ $this->options = $options;
$this->specialPageFactory = $specialPageFactory;
$this->revisionLookup = $revisionLookup;
- $this->whitelistRead = $whitelistRead;
- $this->whitelistReadRegexp = $whitelistReadRegexp;
- $this->emailConfirmToEdit = $emailConfirmToEdit;
- $this->blockDisablesLogin = $blockDisablesLogin;
- $this->groupPermissions = $groupPermissions;
- $this->revokePermissions = $revokePermissions;
- $this->availableRights = $availableRights;
$this->nsInfo = $nsInfo;
}
}
/**
- * Check if user is blocked from editing a particular article
+ * Check if user is blocked from editing a particular article. If the user does not
+ * have a block, this will return false.
*
* @param User $user
* @param LinkTarget $page Title to check
* @return bool
*/
public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
- $blocked = $user->isHidden();
+ $block = $user->getBlock( $fromReplica );
+ if ( !$block ) {
+ return false;
+ }
// TODO: remove upon further migration to LinkTarget
$title = Title::newFromLinkTarget( $page );
+ $blocked = $user->isHidden();
if ( !$blocked ) {
- $block = $user->getBlock( $fromReplica );
- if ( $block ) {
- // Special handling for a user's own talk page. The block is not aware
- // of the user, so this must be done here.
- if ( $title->equals( $user->getTalkPage() ) ) {
- $blocked = $block->appliesToUsertalk( $title );
- } else {
- $blocked = $block->appliesToTitle( $title );
- }
+ // Special handling for a user's own talk page. The block is not aware
+ // of the user, so this must be done here.
+ if ( $title->equals( $user->getTalkPage() ) ) {
+ $blocked = $block->appliesToUsertalk( $title );
+ } else {
+ $blocked = $block->appliesToTitle( $title );
}
}
// only for the purpose of the hook. We really don't need this here.
$allowUsertalk = $user->isAllowUsertalk();
+ // Allow extensions to let a blocked user access a particular page
Hooks::run( 'UserIsBlockedFrom', [ $user, $title, &$blocked, &$allowUsertalk ] );
return $blocked;
// TODO: remove when LinkTarget usage will expand further
$title = Title::newFromLinkTarget( $page );
+ $whiteListRead = $this->options->get( 'WhitelistRead' );
$whitelisted = false;
- if ( User::isEveryoneAllowed( 'read' ) ) {
+ if ( $this->isEveryoneAllowed( 'read' ) ) {
# Shortcut for public wikis, allows skipping quite a bit of code
$whitelisted = true;
- } elseif ( $user->isAllowed( 'read' ) ) {
+ } elseif ( $this->userHasRight( $user, 'read' ) ) {
# If the user is allowed to read pages, he is allowed to read all pages
$whitelisted = true;
} elseif ( $this->isSameSpecialPage( 'Userlogin', $title )
# Always grant access to the login page.
# Even anons need to be able to log in.
$whitelisted = true;
- } elseif ( is_array( $this->whitelistRead ) && count( $this->whitelistRead ) ) {
+ } elseif ( is_array( $whiteListRead ) && count( $whiteListRead ) ) {
# Time to check the whitelist
# Only do these checks is there's something to check against
$name = $title->getPrefixedText();
$dbName = $title->getPrefixedDBkey();
// Check for explicit whitelisting with and without underscores
- if ( in_array( $name, $this->whitelistRead, true )
- || in_array( $dbName, $this->whitelistRead, true ) ) {
+ if ( in_array( $name, $whiteListRead, true )
+ || in_array( $dbName, $whiteListRead, true ) ) {
$whitelisted = true;
} elseif ( $title->getNamespace() == NS_MAIN ) {
# Old settings might have the title prefixed with
# a colon for main-namespace pages
- if ( in_array( ':' . $name, $this->whitelistRead ) ) {
+ if ( in_array( ':' . $name, $whiteListRead ) ) {
$whitelisted = true;
}
} elseif ( $title->isSpecialPage() ) {
$this->specialPageFactory->resolveAlias( $name );
if ( $name ) {
$pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
- if ( in_array( $pure, $this->whitelistRead, true ) ) {
+ if ( in_array( $pure, $whiteListRead, true ) ) {
$whitelisted = true;
}
}
}
}
- if ( !$whitelisted && is_array( $this->whitelistReadRegexp )
- && !empty( $this->whitelistReadRegexp ) ) {
+ $whitelistReadRegexp = $this->options->get( 'WhitelistReadRegexp' );
+ if ( !$whitelisted && is_array( $whitelistReadRegexp )
+ && !empty( $whitelistReadRegexp ) ) {
$name = $title->getPrefixedText();
// Check for regex whitelisting
- foreach ( $this->whitelistReadRegexp as $listItem ) {
+ foreach ( $whitelistReadRegexp as $listItem ) {
if ( preg_match( $listItem, $name ) ) {
$whitelisted = true;
break;
}
// Optimize for a very common case
- if ( $action === 'read' && !$this->blockDisablesLogin ) {
+ if ( $action === 'read' && !$this->options->get( 'BlockDisablesLogin' ) ) {
return $errors;
}
- if ( $this->emailConfirmToEdit
+ if ( $this->options->get( 'EmailConfirmToEdit' )
&& !$user->isEmailConfirmed()
&& $action === 'edit'
) {
if ( $action == 'create' ) {
if (
( $this->nsInfo->isTalk( $title->getNamespace() ) &&
- !$user->isAllowed( 'createtalk' ) ) ||
+ !$this->userHasRight( $user, 'createtalk' ) ) ||
( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
- !$user->isAllowed( 'createpage' ) )
+ !$this->userHasRight( $user, 'createpage' ) )
) {
$errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
}
} elseif ( $action == 'move' ) {
- if ( !$user->isAllowed( 'move-rootuserpages' )
+ if ( !$this->userHasRight( $user, 'move-rootuserpages' )
&& $title->getNamespace() == NS_USER && !$isSubPage ) {
// Show user page-specific message only if the user can move other pages
$errors[] = [ 'cant-move-user-page' ];
}
// Check if user is allowed to move files if it's a file
- if ( $title->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
+ if ( $title->getNamespace() == NS_FILE &&
+ !$this->userHasRight( $user, 'movefile' ) ) {
$errors[] = [ 'movenotallowedfile' ];
}
// Check if user is allowed to move category pages if it's a category page
- if ( $title->getNamespace() == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
+ if ( $title->getNamespace() == NS_CATEGORY &&
+ !$this->userHasRight( $user, 'move-categorypages' ) ) {
$errors[] = [ 'cant-move-category-page' ];
}
- if ( !$user->isAllowed( 'move' ) ) {
+ if ( !$this->userHasRight( $user, 'move' ) ) {
// User can't move anything
- $userCanMove = User::groupHasPermission( 'user', 'move' );
- $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
+ $userCanMove = $this->groupHasPermission( 'user', 'move' );
+ $autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
// custom message if logged-in users without any special rights can move
$errors[] = [ 'movenologintext' ];
}
}
} elseif ( $action == 'move-target' ) {
- if ( !$user->isAllowed( 'move' ) ) {
+ if ( !$this->userHasRight( $user, 'move' ) ) {
// User can't move anything
$errors[] = [ 'movenotallowed' ];
- } elseif ( !$user->isAllowed( 'move-rootuserpages' )
+ } elseif ( !$this->userHasRight( $user, 'move-rootuserpages' )
&& $title->getNamespace() == NS_USER && !$isSubPage ) {
// Show user page-specific message only if the user can move other pages
$errors[] = [ 'cant-move-to-user-page' ];
- } elseif ( !$user->isAllowed( 'move-categorypages' )
+ } elseif ( !$this->userHasRight( $user, 'move-categorypages' )
&& $title->getNamespace() == NS_CATEGORY ) {
// Show category page-specific message only if the user can move other pages
$errors[] = [ 'cant-move-to-category-page' ];
}
- } elseif ( !$user->isAllowed( $action ) ) {
+ } elseif ( !$this->userHasRight( $user, $action ) ) {
$errors[] = $this->missingPermissionError( $action, $short );
}
if ( $right == '' ) {
continue;
}
- if ( !$user->isAllowed( $right ) ) {
+ if ( !$this->userHasRight( $user, $right ) ) {
$errors[] = [ 'protectedpagetext', $right, $action ];
- } elseif ( $title->areRestrictionsCascading() && !$user->isAllowed( 'protect' ) ) {
+ } elseif ( $title->areRestrictionsCascading() &&
+ !$this->userHasRight( $user, 'protect' ) ) {
$errors[] = [ 'protectedpagetext', 'protect', $action ];
}
}
* 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 ) {
$title_protection = $title->getTitleProtection();
if ( $title_protection ) {
if ( $title_protection['permission'] == ''
- || !$user->isAllowed( $title_protection['permission'] )
+ || !$this->userHasRight( $user, $title_protection['permission'] )
) {
$errors[] = [
'titleprotected',
* Check permissions on special pages & namespaces
*
* @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 checkSpecialsAndNSPermissions(
$action,
- User $user,
+ UserIdentity $user,
$errors,
$rigor,
$short,
}
# Check $wgNamespaceProtection for restricted namespaces
- if ( $title->isNamespaceProtected( $user ) ) {
+ if ( $this->isNamespaceProtected( $title->getNamespace(), $user ) ) {
$ns = $title->getNamespace() == NS_MAIN ?
wfMessage( 'nstab-main' )->text() : $title->getNsText();
$errors[] = $title->getNamespace() == NS_MEDIAWIKI ?
$error = null;
// Sitewide CSS/JSON/JS changes, like all NS_MEDIAWIKI changes, also require the
// editinterface right. That's implemented as a restriction so no check needed here.
- if ( $title->isSiteCssConfigPage() && !$user->isAllowed( 'editsitecss' ) ) {
+ if ( $title->isSiteCssConfigPage() && !$this->userHasRight( $user, 'editsitecss' ) ) {
$error = [ 'sitecssprotected', $action ];
- } elseif ( $title->isSiteJsonConfigPage() && !$user->isAllowed( 'editsitejson' ) ) {
+ } elseif ( $title->isSiteJsonConfigPage() && !$this->userHasRight( $user, 'editsitejson' ) ) {
$error = [ 'sitejsonprotected', $action ];
- } elseif ( $title->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) {
+ } elseif ( $title->isSiteJsConfigPage() && !$this->userHasRight( $user, 'editsitejs' ) ) {
$error = [ 'sitejsprotected', $action ];
} elseif ( $title->isRawHtmlMessage() ) {
// Raw HTML can be used to deploy CSS or JS so require rights for both.
- if ( !$user->isAllowed( 'editsitejs' ) ) {
+ if ( !$this->userHasRight( $user, 'editsitejs' ) ) {
$error = [ 'sitejsprotected', $action ];
- } elseif ( !$user->isAllowed( 'editsitecss' ) ) {
+ } elseif ( !$this->userHasRight( $user, 'editsitecss' ) ) {
$error = [ 'sitecssprotected', $action ];
}
}
if ( $error ) {
- if ( $user->isAllowed( 'editinterface' ) ) {
+ if ( $this->userHasRight( $user, 'editinterface' ) ) {
// Most users / site admins will probably find out about the new, more restrictive
// permissions by failing to edit something. Give them more info.
// TODO remove this a few release cycles after 1.32
* 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 );
if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
if (
$title->isUserCssConfigPage()
- && !$user->isAllowed( 'editusercss' )
+ && !$this->userHasRight( $user, 'editusercss' )
) {
$errors[] = [ 'customcssprotected', $action ];
} elseif (
$title->isUserJsonConfigPage()
- && !$user->isAllowed( 'edituserjson' )
+ && !$this->userHasRight( $user, 'edituserjson' )
) {
$errors[] = [ 'customjsonprotected', $action ];
} elseif (
$title->isUserJsConfigPage()
- && !$user->isAllowed( 'edituserjs' )
+ && !$this->userHasRight( $user, 'edituserjs' )
) {
$errors[] = [ 'customjsprotected', $action ];
}
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.
*
*/
public function getUserPermissions( UserIdentity $user ) {
$user = User::newFromIdentity( $user );
- if ( !isset( $this->usersRights[ $user->getId() ] ) ) {
- $this->usersRights[ $user->getId() ] = $this->getGroupPermissions(
+ $rightsCacheKey = $this->getRightsCacheKey( $user );
+ if ( !isset( $this->usersRights[ $rightsCacheKey ] ) ) {
+ $this->usersRights[ $rightsCacheKey ] = $this->getGroupPermissions(
$user->getEffectiveGroups()
);
- Hooks::run( 'UserGetRights', [ $user, &$this->usersRights[ $user->getId() ] ] );
+ Hooks::run( 'UserGetRights', [ $user, &$this->usersRights[ $rightsCacheKey ] ] );
// Deny any rights denied by the user's session, unless this
// endpoint has no sessions.
// 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() ],
+ $this->usersRights[ $rightsCacheKey ] = array_intersect(
+ $this->usersRights[ $rightsCacheKey ],
$allowedRights
);
}
}
- Hooks::run( 'UserGetRightsRemove', [ $user, &$this->usersRights[ $user->getId() ] ] );
+ Hooks::run( 'UserGetRightsRemove', [ $user, &$this->usersRights[ $rightsCacheKey ] ] );
// Force reindexation of rights when a hook has unset one of them
- $this->usersRights[ $user->getId() ] = array_values(
- array_unique( $this->usersRights[ $user->getId() ] )
+ $this->usersRights[ $rightsCacheKey ] = array_values(
+ array_unique( $this->usersRights[ $rightsCacheKey ] )
);
if (
$user->isLoggedIn() &&
- $this->blockDisablesLogin &&
+ $this->options->get( 'BlockDisablesLogin' ) &&
$user->getBlock()
) {
$anon = new User;
- $this->usersRights[ $user->getId() ] = array_intersect(
- $this->usersRights[ $user->getId() ],
+ $this->usersRights[ $rightsCacheKey ] = array_intersect(
+ $this->usersRights[ $rightsCacheKey ],
$this->getUserPermissions( $anon )
);
}
}
- $rights = $this->usersRights[ $user->getId() ];
+ $rights = $this->usersRights[ $rightsCacheKey ];
foreach ( $this->temporaryUserRights[ $user->getId() ] ?? [] as $overrides ) {
$rights = array_values( array_unique( array_merge( $rights, $overrides ) ) );
}
*/
public function invalidateUsersRightsCache( $user = null ) {
if ( $user !== null ) {
- if ( isset( $this->usersRights[ $user->getId() ] ) ) {
- unset( $this->usersRights[$user->getId()] );
+ $rightsCacheKey = $this->getRightsCacheKey( $user );
+ if ( isset( $this->usersRights[ $rightsCacheKey ] ) ) {
+ unset( $this->usersRights[ $rightsCacheKey ] );
}
} else {
$this->usersRights = null;
}
}
+ /**
+ * Gets a unique key for user rights cache.
+ * @param UserIdentity $user
+ * @return string
+ */
+ private function getRightsCacheKey( UserIdentity $user ) {
+ return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
+ }
+
/**
* Check, if the given group has the given permission
*
* @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] );
+ $groupPermissions = $this->options->get( 'GroupPermissions' );
+ $revokePermissions = $this->options->get( 'RevokePermissions' );
+ return isset( $groupPermissions[$group][$role] ) && $groupPermissions[$group][$role] &&
+ !( isset( $revokePermissions[$group][$role] ) && $revokePermissions[$group][$role] );
}
/**
$rights = [];
// grant every granted permission first
foreach ( $groups as $group ) {
- if ( isset( $this->groupPermissions[$group] ) ) {
+ if ( isset( $this->options->get( 'GroupPermissions' )[$group] ) ) {
$rights = array_merge( $rights,
// array_filter removes empty items
- array_keys( array_filter( $this->groupPermissions[$group] ) ) );
+ array_keys( array_filter( $this->options->get( 'GroupPermissions' )[$group] ) ) );
}
}
// now revoke the revoked permissions
foreach ( $groups as $group ) {
- if ( isset( $this->revokePermissions[$group] ) ) {
+ if ( isset( $this->options->get( 'RevokePermissions' )[$group] ) ) {
$rights = array_diff( $rights,
- array_keys( array_filter( $this->revokePermissions[$group] ) ) );
+ array_keys( array_filter( $this->options->get( 'RevokePermissions' )[$group] ) ) );
}
}
return array_unique( $rights );
*/
public function getGroupsWithPermission( $role ) {
$allowedGroups = [];
- foreach ( array_keys( $this->groupPermissions ) as $group ) {
+ foreach ( array_keys( $this->options->get( 'GroupPermissions' ) ) as $group ) {
if ( $this->groupHasPermission( $group, $role ) ) {
$allowedGroups[] = $group;
}
return $this->cachedRights[$right];
}
- if ( !isset( $this->groupPermissions['*'][$right] )
- || !$this->groupPermissions['*'][$right] ) {
+ if ( !isset( $this->options->get( 'GroupPermissions' )['*'][$right] )
+ || !$this->options->get( 'GroupPermissions' )['*'][$right] ) {
$this->cachedRights[$right] = false;
return false;
}
// If it's revoked anywhere, then everyone doesn't have it
- foreach ( $this->revokePermissions as $rights ) {
+ foreach ( $this->options->get( 'RevokePermissions' ) as $rights ) {
if ( isset( $rights[$right] ) && $rights[$right] ) {
$this->cachedRights[$right] = false;
return false;
*/
public function getAllPermissions() {
if ( $this->allRights === false ) {
- if ( count( $this->availableRights ) ) {
+ if ( count( $this->options->get( 'AvailableRights' ) ) ) {
$this->allRights = array_unique( array_merge(
$this->coreRights,
- $this->availableRights
+ $this->options->get( 'AvailableRights' )
) );
} else {
$this->allRights = $this->coreRights;
return $this->allRights;
}
+ /**
+ * Determines if $user is unable to edit pages in namespace because it has been protected.
+ * @param $index
+ * @param UserIdentity $user
+ * @return bool
+ */
+ private function isNamespaceProtected( $index, UserIdentity $user ) {
+ $namespaceProtection = $this->options->get( 'NamespaceProtection' );
+ if ( isset( $namespaceProtection[$index] ) ) {
+ return !$this->userHasAllRights( $user, ...(array)$namespaceProtection[$index] );
+ }
+ return false;
+ }
+
+ /**
+ * 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
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 ];
+ $this->usersRights[ $this->getRightsCacheKey( $user ) ] =
+ is_array( $rights ) ? $rights : [ $rights ];
}
}