wiki.techinc.nl/includes/Rest/Handler/EditHandler.php
Daimona Eaytoy 3b81a1ef27 Add TokenAwareHandlerTrait for handlers supporting CSRF-unsafe providers
Returning a known-good token is not part of the trait method because I
think handlers other than the ones based on the action API wouldn't even
need a token if the session is safe against CSRF.

Bug: T305043
Change-Id: If41749722b28c8c0e9898b3d3e7937167653fb10
2022-05-25 02:07:36 +00:00

173 lines
4.4 KiB
PHP

<?php
namespace MediaWiki\Rest\Handler;
use Config;
use IApiMessage;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\MainConfigNames;
use MediaWiki\Rest\HttpException;
use MediaWiki\Rest\LocalizedHttpException;
use MediaWiki\Rest\Response;
use MediaWiki\Rest\TokenAwareHandlerTrait;
use MediaWiki\Revision\RevisionLookup;
use MediaWiki\Revision\SlotRecord;
use TitleFormatter;
use TitleParser;
use WebResponse;
use Wikimedia\Message\MessageValue;
/**
* Base class for REST API handlers that perform page edits (main slot only).
*/
abstract class EditHandler extends ActionModuleBasedHandler {
use TokenAwareHandlerTrait;
/** @var Config */
protected $config;
/**
* @var IContentHandlerFactory
*/
protected $contentHandlerFactory;
/**
* @var TitleParser
*/
protected $titleParser;
/**
* @var TitleFormatter
*/
protected $titleFormatter;
/**
* @var RevisionLookup
*/
protected $revisionLookup;
/**
* @param Config $config
* @param IContentHandlerFactory $contentHandlerFactory
* @param TitleParser $titleParser
* @param TitleFormatter $titleFormatter
* @param RevisionLookup $revisionLookup
*/
public function __construct(
Config $config,
IContentHandlerFactory $contentHandlerFactory,
TitleParser $titleParser,
TitleFormatter $titleFormatter,
RevisionLookup $revisionLookup
) {
$this->config = $config;
$this->contentHandlerFactory = $contentHandlerFactory;
$this->titleParser = $titleParser;
$this->titleFormatter = $titleFormatter;
$this->revisionLookup = $revisionLookup;
}
public function needsWriteAccess() {
return true;
}
/**
* Returns the requested title.
*
* @return string
*/
abstract protected function getTitleParameter();
/**
* @inheritDoc
*/
protected function mapActionModuleResult( array $data ) {
if ( isset( $data['error'] ) ) {
throw new LocalizedHttpException( new MessageValue( 'apierror-' . $data['error'] ), 400 );
}
if ( !isset( $data['edit'] ) || !$data['edit']['result'] ) {
throw new HttpException( 'Bad result structure received from ApiEditPage' );
}
if ( $data['edit']['result'] !== 'Success' ) {
// Probably an edit conflict
// TODO: which code for null edits?
throw new HttpException( $data['edit']['result'], 409 );
}
$title = $this->titleParser->parseTitle( $data['edit']['title'] );
// This seems wasteful. This is the downside of delegating to the action API module:
// if we need additional data in the response, we have to load it.
$revision = $this->revisionLookup->getRevisionById( (int)$data['edit']['newrevid'] );
$content = $revision->getContent( SlotRecord::MAIN );
return [
'id' => $data['edit']['pageid'],
'title' => $this->titleFormatter->getPrefixedText( $title ),
'key' => $this->titleFormatter->getPrefixedDBkey( $title ),
'latest' => [
'id' => $data['edit']['newrevid'],
'timestamp' => $data['edit']['newtimestamp'],
],
'license' => [
'url' => $this->config->get( MainConfigNames::RightsUrl ),
'title' => $this->config->get( MainConfigNames::RightsText )
],
'content_model' => $data['edit']['contentmodel'],
'source' => $content->serialize(),
];
}
/**
* @inheritDoc
*/
protected function throwHttpExceptionForActionModuleError( IApiMessage $msg, $statusCode = 400 ) {
$code = $msg->getApiCode();
if ( $code === 'protectedpage' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 403 );
}
if ( $code === 'badtoken' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 403 );
}
if ( $code === 'missingtitle' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 404 );
}
if ( $code === 'articleexists' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 409 );
}
if ( $code === 'editconflict' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 409 );
}
if ( $code === 'ratelimited' ) {
throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 429 );
}
// Fall through to generic handling of the error (status 400).
parent::throwHttpExceptionForActionModuleError( $msg, $statusCode );
}
protected function mapActionModuleResponse(
WebResponse $actionModuleResponse,
array $actionModuleResult,
Response $response
) {
parent::mapActionModuleResponse(
$actionModuleResponse,
$actionModuleResult,
$response
);
if ( $actionModuleResult['edit']['new'] ?? false ) {
$response->setStatus( 201 );
}
}
}