Mobrovac has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/384994 )
Change subject: Create SpecialRunSingleJob.php ...................................................................... Create SpecialRunSingleJob.php Bug: T175146 Change-Id: Id0f0857f62250b3341c3ca41fc05a9830a20735e --- A SpecialRunSingleJob.php M extension.json 2 files changed, 153 insertions(+), 1 deletion(-) Approvals: Mobrovac: Verified; Looks good to me, approved jenkins-bot: Verified diff --git a/SpecialRunSingleJob.php b/SpecialRunSingleJob.php new file mode 100644 index 0000000..93e0b51 --- /dev/null +++ b/SpecialRunSingleJob.php @@ -0,0 +1,148 @@ +<?php +/** + * Implements Special:RunSingleJob + * + * 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 + * @ingroup SpecialPage + */ + +use MediaWiki\Logger\LoggerFactory; +use MediaWiki\MediaWikiServices; +use Firebase\JWT\JWT; +use Psr\Log\LoggerInterface; + +/** + * Special page designed for running a single background task (internal use only) + * + * @ingroup SpecialPage + */ +class SpecialRunSingleJob extends UnlistedSpecialPage { + + /** @var LoggerInterface instance for all SpecialRunJobs instances */ + private static $logger; + + public function __construct() { + parent::__construct( 'RunSingleJob' ); + } + + public function doesWrites() { + return true; + } + + public function execute( $par = '' ) { + $this->getOutput()->disable(); + + if ( wfReadOnly() ) { + wfHttpError( 423, 'Locked', 'Wiki is in read-only mode.' ); + return; + } + + // Validate request method + if ( !$this->getRequest()->wasPosted() ) { + wfHttpError( 400, 'Bad Request', 'Request must be POSTed.' ); + return; + } + + // get the info contained in the body + $event = null; + try { + $event = FormatJson::decode( file_get_contents( "php://input" ), true ); + } catch ( Exception $e ) { + wfHttpError( 500, 'Server Error', 'Could not decode the event' ); + return; + } + + // check that we have the needed components of the event + if ( !isset( $event['database'] ) + || !isset( $event['type'] ) + || !isset( $event['page_title'] ) + || !isset( $event['params'] ) + ) { + wfHttpError( 400, 'Bad Request', 'Invalid event received' ); + return; + } + + if ( !isset( $event['mediawiki_signature'] ) ) { + wfHttpError( 403, 'Forbidden', 'Missing mediawiki signature' ); + return; + } + $signature = $event['mediawiki_signature']; + unset( $event['mediawiki_signature'] ); + $expected_signature = JWT::encode( + $event, + MediaWikiServices::getInstance()->getMainConfig()->get( 'SecretKey' ) + ); + if ( !hash_equals( $expected_signature, $signature ) ) { + wfHttpError( 403, 'Forbidden', 'Invalid mediawiki signature' ); + return; + } + + // check if there are any base64-encoded parameters and if so decode them + foreach ( $event['params'] as $key => &$value ) { + if ( !is_string( $value ) ) { + continue; + } + if ( preg_match( '/^data:application/octet-stream;base64,([\s\S]+)$/', $value, $match ) ) { + $value = base64_decode( $match[1], true ); + if ( $value === false ) { + wfHttpError( + 500, + 'Internal Server Error', + "base64_decode() failed for parameter {$key} ({$match[1]})" + ); + return; + } + } + } + unset( $value ); + + $executor = new JobExecutor(); + + try { + // execute the job + $response = $executor->execute( $event ); + if ( $response['status'] === true ) { + HttpStatus::header( 200 ); + return; + } else { + wfHttpError( 500, 'Internal Server Error', $response['message'] ); + } + } catch ( Exception $e ) { + self::logger()->error( + 'Error running job ' . $event['meta']['id'] . ' of type ' . $event['type'], + [ + 'exception' => $e + ] + ); + wfHttpError( 500, 'Internal Server Error', $e->getMessage() ); + } + } + + /** + * Returns a singleton logger instance for all JobExecutor instances. + * Use like: self::logger()->info( $mesage ) + * We use this so we don't have to check if the logger has been created + * before attempting to log a message. + */ + private static function logger() { + if ( !self::$logger ) { + self::$logger = LoggerFactory::getInstance( 'RunSingleJob' ); + } + return self::$logger; + } +} diff --git a/extension.json b/extension.json index f887d04..4a4dea8 100644 --- a/extension.json +++ b/extension.json @@ -22,7 +22,8 @@ "EventBusRCFeedEngine": "EventBusRCFeedEngine.php", "EventBusRCFeedFormatter": "EventBusRCFeedFormatter.php", "JobQueueEventBus": "JobQueueEventBus.php", - "JobExecutor": "JobExecutor.php" + "JobExecutor": "JobExecutor.php", + "SpecialRunSingleJob": "SpecialRunSingleJob.php" }, "MessagesDirs": { "EventBus": [ @@ -64,5 +65,8 @@ "EventBusHooks::onArticleProtectComplete" ] }, + "SpecialPages": { + "RunSingleJob": "SpecialRunSingleJob" + }, "manifest_version": 1 } -- To view, visit https://gerrit.wikimedia.org/r/384994 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Id0f0857f62250b3341c3ca41fc05a9830a20735e Gerrit-PatchSet: 5 Gerrit-Project: mediawiki/extensions/EventBus Gerrit-Branch: master Gerrit-Owner: Ppchelko <ppche...@wikimedia.org> Gerrit-Reviewer: Mobrovac <mobro...@wikimedia.org> Gerrit-Reviewer: Ppchelko <ppche...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits