Add perm checks to UndeletePage and make it a real service

Add entry in MediaWikiServices, add wiring code, inject all dependencies.

Also add an alternative entry point with permission checks, like for
DeletePage.

The new service is no longer @unstable, and the relevant methods in
PageArchive were deprecated.

Bug: T290021
Change-Id: I452a98679f5bfea3f7367aacd5c930acffd32102
This commit is contained in:
Daimona Eaytoy 2021-08-30 22:55:05 +02:00 committed by MusikAnimal
parent 08766dcad4
commit 249306e112
9 changed files with 151 additions and 27 deletions

View file

@ -146,6 +146,8 @@ because of Phabricator reports.
=== Deprecations in 1.38 ===
* The MWGrants class is deprecated in favor of the new GrantsInfo and
GrantsLocalization services.
* PageArchive::undeleteAsUser(), ::getFileStatus() and ::getRevisionStatus()
were deprecated. Use UndeletePage instead.
* The global functions wfReadOnly() and wfReadOnlyReason() have been
deprecated in favor of the ReadOnlyMode service.
* PageProps::getInstance() has been deprecated. Use

View file

@ -72,6 +72,7 @@ use MediaWiki\Page\PageStore;
use MediaWiki\Page\PageStoreFactory;
use MediaWiki\Page\ParserOutputAccess;
use MediaWiki\Page\RollbackPageFactory;
use MediaWiki\Page\UndeletePageFactory;
use MediaWiki\Page\WikiPageFactory;
use MediaWiki\Parser\ParserCacheFactory;
use MediaWiki\Permissions\GrantsInfo;
@ -1662,6 +1663,14 @@ class MediaWikiServices extends ServiceContainer {
return $this->getService( 'UnblockUserFactory' );
}
/**
* @since 1.38
* @return UndeletePageFactory
*/
public function getUndeletePageFactory(): UndeletePageFactory {
return $this->getService( 'UndeletePageFactory' );
}
/**
* @since 1.32
* @return UploadRevisionImporter

View file

@ -104,6 +104,7 @@ use MediaWiki\Page\PageStore;
use MediaWiki\Page\PageStoreFactory;
use MediaWiki\Page\ParserOutputAccess;
use MediaWiki\Page\RollbackPageFactory;
use MediaWiki\Page\UndeletePageFactory;
use MediaWiki\Page\WikiPageFactory;
use MediaWiki\Parser\ParserCacheFactory;
use MediaWiki\Parser\ParserObserver;
@ -1702,6 +1703,10 @@ return [
return $services->getService( '_UserBlockCommandFactory' );
},
'UndeletePageFactory' => static function ( MediaWikiServices $services ): UndeletePageFactory {
return $services->getService( '_PageCommandFactory' );
},
'UploadRevisionImporter' => static function ( MediaWikiServices $services ): UploadRevisionImporter {
return new ImportableUploadRevisionImporter(
$services->getMainConfig()->get( 'EnableUploads' ),
@ -1978,7 +1983,8 @@ return [
ObjectCache::getInstance( 'db-replicated' ),
WikiMap::getCurrentWikiDbDomain()->getId(),
WebRequest::getRequestId(),
$services->getBacklinkCacheFactory()
$services->getBacklinkCacheFactory(),
LoggerFactory::getInstance( 'UndeletePage' )
);
},

View file

@ -388,6 +388,7 @@ class PageArchive {
* (depending what operations are attempted).
*
* @since 1.35
* @deprecated since 1.38, use UndeletePage instead
*
* @param array $timestamps Pass an empty array to restore all revisions,
* otherwise list the ones to undelete.
@ -408,9 +409,10 @@ class PageArchive {
$unsuppress = false,
$tags = null
) {
$page = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $this->title );
$user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $user );
$up = new UndeletePage( $page, $user );
$services = MediaWikiServices::getInstance();
$page = $services->getWikiPageFactory()->newFromTitle( $this->title );
$user = $services->getUserFactory()->newFromUserIdentity( $user );
$up = $services->getUndeletePageFactory()->newUndeletePage( $page, $user );
if ( is_string( $tags ) ) {
$tags = [ $tags ];
} elseif ( $tags === null ) {
@ -421,7 +423,7 @@ class PageArchive {
->setUndeleteOnlyFileVersions( $fileVersions ?: [] )
->setUnsuppress( $unsuppress )
->setTags( $tags ?: [] )
->undelete( $comment );
->undeleteUnsafe( $comment );
// BC with old return format
if ( $status->isGood() ) {
$restoredRevs = $status->getValue()[UndeletePage::REVISIONS_RESTORED];
@ -440,6 +442,7 @@ class PageArchive {
}
/**
* @deprecated since 1.38 The entrypoints in UndeletePage return a StatusValue
* @return Status|null
*/
public function getFileStatus() {
@ -447,6 +450,7 @@ class PageArchive {
}
/**
* @deprecated since 1.38 The entrypoints in UndeletePage return a StatusValue
* @return Status|null
*/
public function getRevisionStatus() {

View file

@ -43,6 +43,7 @@ use MediaWiki\User\UserIdentity;
use MergeHistory;
use MovePage;
use NamespaceInfo;
use Psr\Log\LoggerInterface;
use ReadOnlyMode;
use RepoGroup;
use Title;
@ -62,7 +63,8 @@ class PageCommandFactory implements
DeletePageFactory,
MergeHistoryFactory,
MovePageFactory,
RollbackPageFactory
RollbackPageFactory,
UndeletePageFactory
{
/** @var Config */
@ -137,6 +139,9 @@ class PageCommandFactory implements
/** @var BacklinkCacheFactory */
private $backlinkCacheFactory;
/** @var LoggerInterface */
private $undeletePageLogger;
public function __construct(
Config $config,
LBFactory $lbFactory,
@ -161,7 +166,8 @@ class PageCommandFactory implements
BagOStuff $dbReplicatedCache,
string $localWikiID,
string $webRequestID,
BacklinkCacheFactory $backlinkCacheFactory
BacklinkCacheFactory $backlinkCacheFactory,
LoggerInterface $undeletePageLogger
) {
$this->config = $config;
$this->lbFactory = $lbFactory;
@ -187,6 +193,7 @@ class PageCommandFactory implements
$this->localWikiID = $localWikiID;
$this->webRequestID = $webRequestID;
$this->backlinkCacheFactory = $backlinkCacheFactory;
$this->undeletePageLogger = $undeletePageLogger;
}
/**
@ -315,4 +322,23 @@ class PageCommandFactory implements
$byUser
);
}
/**
* @inheritDoc
*/
public function newUndeletePage( ProperPageIdentity $page, Authority $authority ): UndeletePage {
return new UndeletePage(
$this->hookContainer,
$this->jobQueueGroup,
$this->lbFactory->getMainLB(),
$this->readOnlyMode,
$this->repoGroup,
$this->undeletePageLogger,
$this->revisionStore,
$this->userFactory,
$this->wikiPageFactory,
$page,
$authority
);
}
}

View file

@ -20,15 +20,16 @@
namespace MediaWiki\Page;
use ChangeTags;
use File;
use HTMLCacheUpdateJob;
use JobQueueGroup;
use LocalFile;
use ManualLogEntry;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\HookContainer\HookRunner;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Permissions\Authority;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\User\UserFactory;
@ -45,7 +46,6 @@ use WikiPage;
/**
* @since 1.38
* @package MediaWiki\Page
* @unstable
*/
class UndeletePage {
@ -90,21 +90,40 @@ class UndeletePage {
private $tags = [];
/**
* @param HookContainer $hookContainer
* @param JobQueueGroup $jobQueueGroup
* @param ILoadBalancer $loadBalancer
* @param ReadOnlyMode $readOnlyMode
* @param RepoGroup $repoGroup
* @param LoggerInterface $logger
* @param RevisionStore $revisionStore
* @param UserFactory $userFactory
* @param WikiPageFactory $wikiPageFactory
* @param ProperPageIdentity $page
* @param Authority $performer
*/
public function __construct( ProperPageIdentity $page, Authority $performer ) {
$services = MediaWikiServices::getInstance();
$this->hookRunner = new HookRunner( $services->getHookContainer() );
$this->jobQueueGroup = $services->getJobQueueGroup();
$this->loadBalancer = $services->getDBLoadBalancer();
$this->readOnlyMode = $services->getReadOnlyMode();
$this->repoGroup = $services->getRepoGroup();
$this->logger = LoggerFactory::getInstance( 'PageArchive' );
$this->revisionStore = $services->getRevisionStore();
$this->userFactory = $services->getUserFactory();
$this->wikiPageFactory = $services->getWikiPageFactory();
public function __construct(
HookContainer $hookContainer,
JobQueueGroup $jobQueueGroup,
ILoadBalancer $loadBalancer,
ReadOnlyMode $readOnlyMode,
RepoGroup $repoGroup,
LoggerInterface $logger,
RevisionStore $revisionStore,
UserFactory $userFactory,
WikiPageFactory $wikiPageFactory,
ProperPageIdentity $page,
Authority $performer
) {
$this->hookRunner = new HookRunner( $hookContainer );
$this->jobQueueGroup = $jobQueueGroup;
$this->loadBalancer = $loadBalancer;
$this->readOnlyMode = $readOnlyMode;
$this->repoGroup = $repoGroup;
$this->logger = $logger;
$this->revisionStore = $revisionStore;
$this->userFactory = $userFactory;
$this->wikiPageFactory = $wikiPageFactory;
$this->page = $page;
$this->performer = $performer;
@ -154,6 +173,33 @@ class UndeletePage {
return $this;
}
/**
* Same as undeleteUnsafe, but checks permissions.
*
* @param string $comment
* @return StatusValue
*/
public function undeleteIfAllowed( string $comment ): StatusValue {
$status = $this->authorizeUndeletion();
if ( !$status->isGood() ) {
return $status;
}
return $this->undeleteUnsafe( $comment );
}
/**
* @return PermissionStatus
*/
private function authorizeUndeletion(): PermissionStatus {
$status = PermissionStatus::newEmpty();
$this->performer->authorizeWrite( 'undelete', $this->page, $status );
if ( $this->tags ) {
$status->merge( ChangeTags::canAddTagsAccompanyingChange( $this->tags, $this->performer ) );
}
return $status;
}
/**
* Restore the given (or all) text and file revisions for the page.
* Once restored, the items will be removed from the archive tables.
@ -162,6 +208,8 @@ class UndeletePage {
* This also sets Status objects, $this->fileStatus and $this->revisionStatus
* (depending what operations are attempted).
*
* @note This method doesn't check user permissions. Use undeleteIfAllowed for that.
*
* @param string $comment
* @return StatusValue Good Status with the following value on success:
* [
@ -170,7 +218,7 @@ class UndeletePage {
* ]
* Fatal Status on failure.
*/
public function undelete( string $comment ): StatusValue {
public function undeleteUnsafe( string $comment ): StatusValue {
$hookStatus = StatusValue::newGood();
$hookRes = $this->hookRunner->onPageUndelete(
$this->page,

View file

@ -0,0 +1,18 @@
<?php
namespace MediaWiki\Page;
use MediaWiki\Permissions\Authority;
/**
* @since 1.38
*/
interface UndeletePageFactory {
/**
* @param ProperPageIdentity $page
* @param Authority $authority
* @return UndeletePage
*/
public function newUndeletePage( ProperPageIdentity $page, Authority $authority ): UndeletePage;
}

View file

@ -1,6 +1,6 @@
<?php
use MediaWiki\Page\UndeletePage;
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\MutableRevisionRecord;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Storage\SlotRecord;
@ -94,7 +94,7 @@ class UndeletePageTest extends MediaWikiIntegrationTestCase {
}
/**
* @covers ::undelete
* @covers ::undeleteUnsafe
* @covers ::undeleteRevisions
*/
public function testUndeleteRevisions() {
@ -123,8 +123,11 @@ class UndeletePageTest extends MediaWikiIntegrationTestCase {
$this->assertFalse( $row );
// Restore the page
$undeletePage = new UndeletePage( $this->page, $this->getTestSysop()->getUser() );
$undeletePage->undelete( '' );
$undeletePage = MediaWikiServices::getInstance()->getUndeletePageFactory()->newUndeletePage(
$this->page,
$this->getTestSysop()->getUser()
);
$undeletePage->undeleteUnsafe( '' );
// Should be back in revision
$revQuery = $revisionStore->getQueryInfo();

View file

@ -10,6 +10,7 @@ use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Page\ProperPageIdentity;
use MediaWiki\Page\RollbackPage;
use MediaWiki\Page\UndeletePage;
use MediaWiki\Permissions\Authority;
use MediaWiki\Tests\Unit\MockServiceDependenciesTrait;
use MediaWiki\User\UserIdentity;
@ -112,4 +113,11 @@ class PageCommandFactoryTest extends MediaWikiUnitTestCase {
);
}
public function testUndeletePage() {
$undeletePage = $this->getFactory()->newUndeletePage(
$this->createMock( ProperPageIdentity::class ),
$this->createMock( Authority::class )
);
$this->assertInstanceOf( UndeletePage::class, $undeletePage );
}
}