aboutsummaryrefslogtreecommitdiffstats
path: root/includes/editpage
diff options
context:
space:
mode:
Diffstat (limited to 'includes/editpage')
-rw-r--r--includes/editpage/Constraint/AuthorizationConstraint.php88
-rw-r--r--includes/editpage/Constraint/ContentModelChangeConstraint.php35
-rw-r--r--includes/editpage/Constraint/EditConstraintFactory.php63
-rw-r--r--includes/editpage/Constraint/EditRightConstraint.php105
-rw-r--r--includes/editpage/Constraint/LinkPurgeRateLimitConstraint.php (renamed from includes/editpage/Constraint/UserRateLimitConstraint.php)23
-rw-r--r--includes/editpage/Constraint/UserBlockConstraint.php79
-rw-r--r--includes/editpage/EditPage.php44
7 files changed, 141 insertions, 296 deletions
diff --git a/includes/editpage/Constraint/AuthorizationConstraint.php b/includes/editpage/Constraint/AuthorizationConstraint.php
new file mode 100644
index 000000000000..73247160fa7b
--- /dev/null
+++ b/includes/editpage/Constraint/AuthorizationConstraint.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\EditPage\Constraint;
+
+use MediaWiki\Page\PageIdentity;
+use MediaWiki\Permissions\Authority;
+use MediaWiki\Permissions\PermissionStatus;
+use StatusValue;
+
+/**
+ * Verify authorization to edit the page (user rights, rate limits, blocks).
+ *
+ * @since 1.44
+ * @internal
+ */
+class AuthorizationConstraint implements IEditConstraint {
+
+ private PermissionStatus $status;
+
+ private Authority $performer;
+ private PageIdentity $target;
+ private bool $new;
+
+ public function __construct(
+ Authority $performer,
+ PageIdentity $target,
+ bool $new
+ ) {
+ $this->performer = $performer;
+ $this->target = $target;
+ $this->new = $new;
+ }
+
+ public function checkConstraint(): string {
+ $this->status = PermissionStatus::newEmpty();
+
+ if ( $this->new && !$this->performer->authorizeWrite( 'create', $this->target, $this->status ) ) {
+ return self::CONSTRAINT_FAILED;
+ }
+
+ if ( !$this->performer->authorizeWrite( 'edit', $this->target, $this->status ) ) {
+ return self::CONSTRAINT_FAILED;
+ }
+
+ return self::CONSTRAINT_PASSED;
+ }
+
+ public function getLegacyStatus(): StatusValue {
+ $statusValue = StatusValue::newGood();
+
+ if ( !$this->status->isGood() ) {
+ // Report the most specific errors first
+ if ( $this->status->isBlocked() ) {
+ $statusValue->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
+ } elseif ( $this->status->isRateLimitExceeded() ) {
+ $statusValue->setResult( false, self::AS_RATE_LIMITED );
+ } elseif ( $this->status->getPermission() === 'create' ) {
+ $statusValue->setResult( false, self::AS_NO_CREATE_PERMISSION );
+ } elseif ( !$this->performer->isRegistered() ) {
+ $statusValue->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
+ } else {
+ $statusValue->setResult( false, self::AS_READ_ONLY_PAGE_LOGGED );
+ }
+ }
+
+ // TODO: Use error messages from the PermissionStatus ($this->status) here - T384399
+ return $statusValue;
+ }
+
+}
diff --git a/includes/editpage/Constraint/ContentModelChangeConstraint.php b/includes/editpage/Constraint/ContentModelChangeConstraint.php
index 8ed2e56debde..2581ebab3c14 100644
--- a/includes/editpage/Constraint/ContentModelChangeConstraint.php
+++ b/includes/editpage/Constraint/ContentModelChangeConstraint.php
@@ -21,6 +21,7 @@
namespace MediaWiki\EditPage\Constraint;
use MediaWiki\Permissions\Authority;
+use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Title\Title;
use StatusValue;
@@ -28,6 +29,7 @@ use StatusValue;
* Verify user permissions if changing content model:
* Must have editcontentmodel rights
* Must be able to edit under the new content model
+ * Must not have exceeded the rate limit
*
* @since 1.36
* @internal
@@ -35,10 +37,11 @@ use StatusValue;
*/
class ContentModelChangeConstraint implements IEditConstraint {
+ private PermissionStatus $status;
+
private Authority $performer;
private Title $title;
private string $newContentModel;
- private string $result;
/**
* @param Authority $performer
@@ -56,45 +59,43 @@ class ContentModelChangeConstraint implements IEditConstraint {
}
public function checkConstraint(): string {
+ $this->status = PermissionStatus::newEmpty();
+
if ( $this->newContentModel === $this->title->getContentModel() ) {
// No change
- $this->result = self::CONSTRAINT_PASSED;
return self::CONSTRAINT_PASSED;
}
- if ( !$this->performer->isAllowed( 'editcontentmodel' ) ) {
- $this->result = self::CONSTRAINT_FAILED;
+ if ( !$this->performer->authorizeWrite( 'editcontentmodel', $this->title, $this->status ) ) {
return self::CONSTRAINT_FAILED;
}
- // Make sure the user can edit the page under the new content model too
+ // Make sure the user can edit the page under the new content model too.
+ // We rely on caching in UserAuthority to avoid bumping the rate limit counter twice.
$titleWithNewContentModel = clone $this->title;
$titleWithNewContentModel->setContentModel( $this->newContentModel );
-
- $canEditModel = $this->performer->authorizeWrite(
- 'editcontentmodel',
- $titleWithNewContentModel
- );
-
if (
- !$canEditModel
- || !$this->performer->authorizeWrite( 'edit', $titleWithNewContentModel )
+ !$this->performer->authorizeWrite( 'editcontentmodel', $titleWithNewContentModel, $this->status )
+ || !$this->performer->authorizeWrite( 'edit', $titleWithNewContentModel, $this->status )
) {
- $this->result = self::CONSTRAINT_FAILED;
return self::CONSTRAINT_FAILED;
}
- $this->result = self::CONSTRAINT_PASSED;
return self::CONSTRAINT_PASSED;
}
public function getLegacyStatus(): StatusValue {
$statusValue = StatusValue::newGood();
- if ( $this->result === self::CONSTRAINT_FAILED ) {
- $statusValue->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
+ if ( !$this->status->isGood() ) {
+ if ( $this->status->isRateLimitExceeded() ) {
+ $statusValue->setResult( false, self::AS_RATE_LIMITED );
+ } else {
+ $statusValue->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
+ }
}
+ // TODO: Use error messages from the PermissionStatus ($this->status) here - T384399
return $statusValue;
}
diff --git a/includes/editpage/Constraint/EditConstraintFactory.php b/includes/editpage/Constraint/EditConstraintFactory.php
index 750c977d2312..e9fede9f256c 100644
--- a/includes/editpage/Constraint/EditConstraintFactory.php
+++ b/includes/editpage/Constraint/EditConstraintFactory.php
@@ -26,10 +26,8 @@ use MediaWiki\Context\IContextSource;
use MediaWiki\EditPage\SpamChecker;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\Language\Language;
-use MediaWiki\Linker\LinkTarget;
use MediaWiki\Logger\Spi;
use MediaWiki\MainConfigNames;
-use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Permissions\RateLimiter;
use MediaWiki\Permissions\RateLimitSubject;
use MediaWiki\Title\Title;
@@ -54,7 +52,6 @@ class EditConstraintFactory {
private ServiceOptions $options;
private Spi $loggerFactory;
- private PermissionManager $permissionManager;
private HookContainer $hookContainer;
private ReadOnlyMode $readOnlyMode;
private SpamChecker $spamRegexChecker;
@@ -74,7 +71,6 @@ class EditConstraintFactory {
*
* @param ServiceOptions $options
* @param Spi $loggerFactory
- * @param PermissionManager $permissionManager
* @param HookContainer $hookContainer
* @param ReadOnlyMode $readOnlyMode
* @param SpamChecker $spamRegexChecker
@@ -83,7 +79,6 @@ class EditConstraintFactory {
public function __construct(
ServiceOptions $options,
Spi $loggerFactory,
- PermissionManager $permissionManager,
HookContainer $hookContainer,
ReadOnlyMode $readOnlyMode,
SpamChecker $spamRegexChecker,
@@ -95,9 +90,6 @@ class EditConstraintFactory {
$this->options = $options;
$this->loggerFactory = $loggerFactory;
- // UserBlockConstraint
- $this->permissionManager = $permissionManager;
-
// EditFilterMergedContentHookConstraint
$this->hookContainer = $hookContainer;
@@ -107,7 +99,7 @@ class EditConstraintFactory {
// SpamRegexConstraint
$this->spamRegexChecker = $spamRegexChecker;
- // UserRateLimitConstraint
+ // LinkPurgeRateLimitConstraint
$this->rateLimiter = $rateLimiter;
}
@@ -163,21 +155,15 @@ class EditConstraintFactory {
/**
* @param RateLimitSubject $subject
- * @param string $oldModel
- * @param string $newModel
*
- * @return UserRateLimitConstraint
+ * @return LinkPurgeRateLimitConstraint
*/
- public function newUserRateLimitConstraint(
- RateLimitSubject $subject,
- string $oldModel,
- string $newModel
- ): UserRateLimitConstraint {
- return new UserRateLimitConstraint(
+ public function newLinkPurgeRateLimitConstraint(
+ RateLimitSubject $subject
+ ): LinkPurgeRateLimitConstraint {
+ return new LinkPurgeRateLimitConstraint(
$this->rateLimiter,
- $subject,
- $oldModel,
- $newModel
+ $subject
);
}
@@ -226,39 +212,4 @@ class EditConstraintFactory {
);
}
- /**
- * @param LinkTarget $title
- * @param User $user
- * @return UserBlockConstraint
- */
- public function newUserBlockConstraint(
- LinkTarget $title,
- User $user
- ): UserBlockConstraint {
- return new UserBlockConstraint(
- $this->permissionManager,
- $title,
- $user
- );
- }
-
- /**
- * @param User $performer
- * @param Title $title
- * @param bool $new
- * @return EditRightConstraint
- */
- public function newEditRightConstraint(
- User $performer,
- Title $title,
- bool $new
- ): EditRightConstraint {
- return new EditRightConstraint(
- $performer,
- $this->permissionManager,
- $title,
- $new
- );
- }
-
}
diff --git a/includes/editpage/Constraint/EditRightConstraint.php b/includes/editpage/Constraint/EditRightConstraint.php
deleted file mode 100644
index 47be037e4dad..000000000000
--- a/includes/editpage/Constraint/EditRightConstraint.php
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-namespace MediaWiki\EditPage\Constraint;
-
-use MediaWiki\Permissions\PermissionManager;
-use MediaWiki\Title\Title;
-use MediaWiki\User\User;
-use StatusValue;
-
-/**
- * Verify user permissions:
- * Must have edit rights
- *
- * @since 1.36
- * @internal
- * @author DannyS712
- */
-class EditRightConstraint implements IEditConstraint {
-
- private User $performer;
- private PermissionManager $permManager;
- private Title $title;
- private string $result;
- private bool $new;
-
- /**
- * @param User $performer
- * @param PermissionManager $permManager
- * @param Title $title
- * @param bool $new
- */
- public function __construct(
- User $performer,
- PermissionManager $permManager,
- Title $title,
- bool $new
- ) {
- $this->performer = $performer;
- $this->permManager = $permManager;
- $this->title = $title;
- $this->new = $new;
- }
-
- public function checkConstraint(): string {
- if ( $this->new ) {
- // Check isn't simple enough to just repeat when getting the status
- if ( !$this->performer->authorizeWrite( 'create', $this->title ) ) {
- $this->result = (string)self::AS_NO_CREATE_PERMISSION;
- return self::CONSTRAINT_FAILED;
- }
- }
-
- // Check isn't simple enough to just repeat when getting the status
- // Prior to 1.41 this checked if the user had edit rights in general
- // instead of for the specific page in question.
- if ( !$this->permManager->userCan(
- 'edit',
- $this->performer,
- $this->title
- ) ) {
- $this->result = self::CONSTRAINT_FAILED;
- return self::CONSTRAINT_FAILED;
- }
-
- $this->result = self::CONSTRAINT_PASSED;
- return self::CONSTRAINT_PASSED;
- }
-
- public function getLegacyStatus(): StatusValue {
- $statusValue = StatusValue::newGood();
-
- if ( $this->result === self::CONSTRAINT_FAILED ) {
- if ( !$this->performer->isRegistered() ) {
- $statusValue->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
- } else {
- $statusValue->fatal( 'readonlytext' );
- $statusValue->value = self::AS_READ_ONLY_PAGE_LOGGED;
- }
- } elseif ( $this->result === (string)self::AS_NO_CREATE_PERMISSION ) {
- $statusValue->fatal( 'nocreatetext' );
- $statusValue->value = self::AS_NO_CREATE_PERMISSION;
- }
-
- return $statusValue;
- }
-
-}
diff --git a/includes/editpage/Constraint/UserRateLimitConstraint.php b/includes/editpage/Constraint/LinkPurgeRateLimitConstraint.php
index 8f35dd5879d0..d25cf9336d88 100644
--- a/includes/editpage/Constraint/UserRateLimitConstraint.php
+++ b/includes/editpage/Constraint/LinkPurgeRateLimitConstraint.php
@@ -25,31 +25,26 @@ use MediaWiki\Permissions\RateLimitSubject;
use StatusValue;
/**
- * Verify user doesn't exceed rate limits
+ * Verify that the user doesn't exceed 'linkpurge' limits, which are weird and special.
+ * Other rate limits have been integrated into their respective permission checks.
*
- * @since 1.36
+ * @since 1.44
* @internal
* @author DannyS712
*/
-class UserRateLimitConstraint implements IEditConstraint {
+class LinkPurgeRateLimitConstraint implements IEditConstraint {
private RateLimitSubject $subject;
- private string $oldContentModel;
- private string $newContentModel;
private RateLimiter $limiter;
private string $result;
public function __construct(
RateLimiter $limiter,
- RateLimitSubject $subject,
- string $oldContentModel,
- string $newContentModel
+ RateLimitSubject $subject
) {
$this->limiter = $limiter;
$this->subject = $subject;
- $this->oldContentModel = $oldContentModel;
- $this->newContentModel = $newContentModel;
}
private function limit( string $action, int $inc = 1 ): bool {
@@ -57,16 +52,10 @@ class UserRateLimitConstraint implements IEditConstraint {
}
public function checkConstraint(): string {
- // Need to check for rate limits on `editcontentmodel` if it is changing
- $contentModelChange = ( $this->newContentModel !== $this->oldContentModel );
-
// TODO inject and use a ThrottleStore once available, see T261744
// Checking if the user is rate limited increments the counts, so we cannot perform
// the check again when getting the status; thus, store the result
- if ( $this->limit( 'edit' )
- || $this->limit( 'linkpurge', 0 ) // only counted after the fact
- || ( $contentModelChange && $this->limit( 'editcontentmodel' ) )
- ) {
+ if ( $this->limit( 'linkpurge', /* only counted after the fact */ 0 ) ) {
$this->result = self::CONSTRAINT_FAILED;
} else {
$this->result = self::CONSTRAINT_PASSED;
diff --git a/includes/editpage/Constraint/UserBlockConstraint.php b/includes/editpage/Constraint/UserBlockConstraint.php
deleted file mode 100644
index bdd33262d434..000000000000
--- a/includes/editpage/Constraint/UserBlockConstraint.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-namespace MediaWiki\EditPage\Constraint;
-
-use MediaWiki\Linker\LinkTarget;
-use MediaWiki\Permissions\PermissionManager;
-use MediaWiki\User\User;
-use StatusValue;
-
-/**
- * Verify user permissions:
- * Must not be blocked from the page
- *
- * @since 1.36
- * @internal
- * @author DannyS712
- */
-class UserBlockConstraint implements IEditConstraint {
-
- private PermissionManager $permissionManager;
- private LinkTarget $title;
- private User $user;
- private string $result;
-
- /**
- * @param PermissionManager $permissionManager
- * @param LinkTarget $title
- * @param User $user
- */
- public function __construct(
- PermissionManager $permissionManager,
- LinkTarget $title,
- User $user
- ) {
- $this->permissionManager = $permissionManager;
- $this->title = $title;
- $this->user = $user;
- }
-
- public function checkConstraint(): string {
- // Check isn't simple enough to just repeat when getting the status
- if ( $this->permissionManager->isBlockedFrom( $this->user, $this->title ) ) {
- $this->result = self::CONSTRAINT_FAILED;
- return self::CONSTRAINT_FAILED;
- }
-
- $this->result = self::CONSTRAINT_PASSED;
- return self::CONSTRAINT_PASSED;
- }
-
- public function getLegacyStatus(): StatusValue {
- $statusValue = StatusValue::newGood();
-
- if ( $this->result === self::CONSTRAINT_FAILED ) {
- $statusValue->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
- }
-
- return $statusValue;
- }
-
-}
diff --git a/includes/editpage/EditPage.php b/includes/editpage/EditPage.php
index 93a3ad5a28e8..f08196894d88 100644
--- a/includes/editpage/EditPage.php
+++ b/includes/editpage/EditPage.php
@@ -37,6 +37,7 @@ use MediaWiki\Context\IContextSource;
use MediaWiki\Debug\DeprecationHelper;
use MediaWiki\Deferred\DeferredUpdates;
use MediaWiki\EditPage\Constraint\AccidentalRecreationConstraint;
+use MediaWiki\EditPage\Constraint\AuthorizationConstraint;
use MediaWiki\EditPage\Constraint\BrokenRedirectConstraint;
use MediaWiki\EditPage\Constraint\ChangeTagsConstraint;
use MediaWiki\EditPage\Constraint\ContentModelChangeConstraint;
@@ -54,7 +55,6 @@ use MediaWiki\EditPage\Constraint\PageSizeConstraint;
use MediaWiki\EditPage\Constraint\SelfRedirectConstraint;
use MediaWiki\EditPage\Constraint\SpamRegexConstraint;
use MediaWiki\EditPage\Constraint\UnicodeConstraint;
-use MediaWiki\EditPage\Constraint\UserBlockConstraint;
use MediaWiki\Exception\ErrorPageError;
use MediaWiki\Exception\MWContentSerializationException;
use MediaWiki\Exception\MWException;
@@ -2189,24 +2189,31 @@ class EditPage implements IEditObject {
)
);
$constraintRunner->addConstraint(
- $constraintFactory->newUserBlockConstraint( $this->mTitle, $requestUser )
+ $constraintFactory->newReadOnlyConstraint()
);
+
+ // Load the page data from the primary DB. If anything changes in the meantime,
+ // we detect it by using page_latest like a token in a 1 try compare-and-swap.
+ $this->page->loadPageData( IDBAccessObject::READ_LATEST );
+ $new = !$this->page->exists();
+
$constraintRunner->addConstraint(
- new ContentModelChangeConstraint(
+ new AuthorizationConstraint(
$authority,
$this->mTitle,
- $this->contentModel
+ $new
)
);
-
$constraintRunner->addConstraint(
- $constraintFactory->newReadOnlyConstraint()
+ new ContentModelChangeConstraint(
+ $authority,
+ $this->mTitle,
+ $this->contentModel
+ )
);
$constraintRunner->addConstraint(
- $constraintFactory->newUserRateLimitConstraint(
- $requestUser->toRateLimitSubject(),
- $this->mTitle->getContentModel(),
- $this->contentModel
+ $constraintFactory->newLinkPurgeRateLimitConstraint(
+ $requestUser->toRateLimitSubject()
)
);
$constraintRunner->addConstraint(
@@ -2230,16 +2237,6 @@ class EditPage implements IEditObject {
)
);
- // Load the page data from the primary DB. If anything changes in the meantime,
- // we detect it by using page_latest like a token in a 1 try compare-and-swap.
- $this->page->loadPageData( IDBAccessObject::READ_LATEST );
- $new = !$this->page->exists();
-
- // We do this last, as some of the other constraints are more specific
- $constraintRunner->addConstraint(
- $constraintFactory->newEditRightConstraint( $this->getUserForPermissions(), $this->mTitle, $new )
- );
-
// Check the constraints
if ( !$constraintRunner->checkConstraints() ) {
$failed = $constraintRunner->getFailedConstraint();
@@ -2625,9 +2622,12 @@ class EditPage implements IEditObject {
* result from the backend.
*/
private function handleFailedConstraint( IEditConstraint $failed ): void {
- if ( $failed instanceof UserBlockConstraint ) {
+ if ( $failed instanceof AuthorizationConstraint ) {
// Auto-block user's IP if the account was "hard" blocked
- if ( !MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
+ if (
+ !MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly()
+ && $failed->getLegacyStatus()->value === self::AS_BLOCKED_PAGE_FOR_USER
+ ) {
$this->context->getUser()->spreadAnyEditBlock();
}
} elseif ( $failed instanceof DefaultTextConstraint ) {