diff options
Diffstat (limited to 'www/wiki/includes/api/ApiAuthManagerHelper.php')
-rw-r--r-- | www/wiki/includes/api/ApiAuthManagerHelper.php | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/www/wiki/includes/api/ApiAuthManagerHelper.php b/www/wiki/includes/api/ApiAuthManagerHelper.php new file mode 100644 index 00000000..d6b9f761 --- /dev/null +++ b/www/wiki/includes/api/ApiAuthManagerHelper.php @@ -0,0 +1,396 @@ +<?php +/** + * Copyright © 2016 Wikimedia Foundation and contributors + * + * 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 + * @since 1.27 + */ + +use MediaWiki\Auth\AuthManager; +use MediaWiki\Auth\AuthenticationRequest; +use MediaWiki\Auth\AuthenticationResponse; +use MediaWiki\Auth\CreateFromLoginAuthenticationRequest; +use MediaWiki\Logger\LoggerFactory; + +/** + * Helper class for AuthManager-using API modules. Intended for use via + * composition. + * + * @ingroup API + */ +class ApiAuthManagerHelper { + + /** @var ApiBase API module, for context and parameters */ + private $module; + + /** @var string Message output format */ + private $messageFormat; + + /** + * @param ApiBase $module API module, for context and parameters + */ + public function __construct( ApiBase $module ) { + $this->module = $module; + + $params = $module->extractRequestParams(); + $this->messageFormat = isset( $params['messageformat'] ) ? $params['messageformat'] : 'wikitext'; + } + + /** + * Static version of the constructor, for chaining + * @param ApiBase $module API module, for context and parameters + * @return ApiAuthManagerHelper + */ + public static function newForModule( ApiBase $module ) { + return new self( $module ); + } + + /** + * Format a message for output + * @param array &$res Result array + * @param string $key Result key + * @param Message $message + */ + private function formatMessage( array &$res, $key, Message $message ) { + switch ( $this->messageFormat ) { + case 'none': + break; + + case 'wikitext': + $res[$key] = $message->setContext( $this->module )->text(); + break; + + case 'html': + $res[$key] = $message->setContext( $this->module )->parseAsBlock(); + $res[$key] = Parser::stripOuterParagraph( $res[$key] ); + break; + + case 'raw': + $res[$key] = [ + 'key' => $message->getKey(), + 'params' => $message->getParams(), + ]; + ApiResult::setIndexedTagName( $res[$key]['params'], 'param' ); + break; + } + } + + /** + * Call $manager->securitySensitiveOperationStatus() + * @param string $operation Operation being checked. + * @throws ApiUsageException + */ + public function securitySensitiveOperation( $operation ) { + $status = AuthManager::singleton()->securitySensitiveOperationStatus( $operation ); + switch ( $status ) { + case AuthManager::SEC_OK: + return; + + case AuthManager::SEC_REAUTH: + $this->module->dieWithError( 'apierror-reauthenticate' ); + + case AuthManager::SEC_FAIL: + $this->module->dieWithError( 'apierror-cannotreauthenticate' ); + + default: + throw new UnexpectedValueException( "Unknown status \"$status\"" ); + } + } + + /** + * Filter out authentication requests by class name + * @param AuthenticationRequest[] $reqs Requests to filter + * @param string[] $blacklist Class names to remove + * @return AuthenticationRequest[] + */ + public static function blacklistAuthenticationRequests( array $reqs, array $blacklist ) { + if ( $blacklist ) { + $blacklist = array_flip( $blacklist ); + $reqs = array_filter( $reqs, function ( $req ) use ( $blacklist ) { + return !isset( $blacklist[get_class( $req )] ); + } ); + } + return $reqs; + } + + /** + * Fetch and load the AuthenticationRequests for an action + * @param string $action One of the AuthManager::ACTION_* constants + * @return AuthenticationRequest[] + */ + public function loadAuthenticationRequests( $action ) { + $params = $this->module->extractRequestParams(); + + $manager = AuthManager::singleton(); + $reqs = $manager->getAuthenticationRequests( $action, $this->module->getUser() ); + + // Filter requests, if requested to do so + $wantedRequests = null; + if ( isset( $params['requests'] ) ) { + $wantedRequests = array_flip( $params['requests'] ); + } elseif ( isset( $params['request'] ) ) { + $wantedRequests = [ $params['request'] => true ]; + } + if ( $wantedRequests !== null ) { + $reqs = array_filter( $reqs, function ( $req ) use ( $wantedRequests ) { + return isset( $wantedRequests[$req->getUniqueId()] ); + } ); + } + + // Collect the fields for all the requests + $fields = []; + $sensitive = []; + foreach ( $reqs as $req ) { + $info = (array)$req->getFieldInfo(); + $fields += $info; + $sensitive += array_filter( $info, function ( $opts ) { + return !empty( $opts['sensitive'] ); + } ); + } + + // Extract the request data for the fields and mark those request + // parameters as used + $data = array_intersect_key( $this->module->getRequest()->getValues(), $fields ); + $this->module->getMain()->markParamsUsed( array_keys( $data ) ); + + if ( $sensitive ) { + $this->module->getMain()->markParamsSensitive( array_keys( $sensitive ) ); + $this->module->requirePostedParameters( array_keys( $sensitive ), 'noprefix' ); + } + + return AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data ); + } + + /** + * Format an AuthenticationResponse for return + * @param AuthenticationResponse $res + * @return array + */ + public function formatAuthenticationResponse( AuthenticationResponse $res ) { + $ret = [ + 'status' => $res->status, + ]; + + if ( $res->status === AuthenticationResponse::PASS && $res->username !== null ) { + $ret['username'] = $res->username; + } + + if ( $res->status === AuthenticationResponse::REDIRECT ) { + $ret['redirecttarget'] = $res->redirectTarget; + if ( $res->redirectApiData !== null ) { + $ret['redirectdata'] = $res->redirectApiData; + } + } + + if ( $res->status === AuthenticationResponse::REDIRECT || + $res->status === AuthenticationResponse::UI || + $res->status === AuthenticationResponse::RESTART + ) { + $ret += $this->formatRequests( $res->neededRequests ); + } + + if ( $res->status === AuthenticationResponse::FAIL || + $res->status === AuthenticationResponse::UI || + $res->status === AuthenticationResponse::RESTART + ) { + $this->formatMessage( $ret, 'message', $res->message ); + $ret['messagecode'] = ApiMessage::create( $res->message )->getApiCode(); + } + + if ( $res->status === AuthenticationResponse::FAIL || + $res->status === AuthenticationResponse::RESTART + ) { + $this->module->getRequest()->getSession()->set( + 'ApiAuthManagerHelper::createRequest', + $res->createRequest + ); + $ret['canpreservestate'] = $res->createRequest !== null; + } else { + $this->module->getRequest()->getSession()->remove( 'ApiAuthManagerHelper::createRequest' ); + } + + return $ret; + } + + /** + * Logs successful or failed authentication. + * @param string $event Event type (e.g. 'accountcreation') + * @param string|AuthenticationResponse $result Response or error message + */ + public function logAuthenticationResult( $event, $result ) { + if ( is_string( $result ) ) { + $status = Status::newFatal( $result ); + } elseif ( $result->status === AuthenticationResponse::PASS ) { + $status = Status::newGood(); + } elseif ( $result->status === AuthenticationResponse::FAIL ) { + $status = Status::newFatal( $result->message ); + } else { + return; + } + + $module = $this->module->getModuleName(); + LoggerFactory::getInstance( 'authevents' )->info( "$module API attempt", [ + 'event' => $event, + 'status' => $status, + 'module' => $module, + ] ); + } + + /** + * Fetch the preserved CreateFromLoginAuthenticationRequest, if any + * @return CreateFromLoginAuthenticationRequest|null + */ + public function getPreservedRequest() { + $ret = $this->module->getRequest()->getSession()->get( 'ApiAuthManagerHelper::createRequest' ); + return $ret instanceof CreateFromLoginAuthenticationRequest ? $ret : null; + } + + /** + * Format an array of AuthenticationRequests for return + * @param AuthenticationRequest[] $reqs + * @return array Will have a 'requests' key, and also 'fields' if $module's + * params include 'mergerequestfields'. + */ + public function formatRequests( array $reqs ) { + $params = $this->module->extractRequestParams(); + $mergeFields = !empty( $params['mergerequestfields'] ); + + $ret = [ 'requests' => [] ]; + foreach ( $reqs as $req ) { + $describe = $req->describeCredentials(); + $reqInfo = [ + 'id' => $req->getUniqueId(), + 'metadata' => $req->getMetadata() + [ ApiResult::META_TYPE => 'assoc' ], + ]; + switch ( $req->required ) { + case AuthenticationRequest::OPTIONAL: + $reqInfo['required'] = 'optional'; + break; + case AuthenticationRequest::REQUIRED: + $reqInfo['required'] = 'required'; + break; + case AuthenticationRequest::PRIMARY_REQUIRED: + $reqInfo['required'] = 'primary-required'; + break; + } + $this->formatMessage( $reqInfo, 'provider', $describe['provider'] ); + $this->formatMessage( $reqInfo, 'account', $describe['account'] ); + if ( !$mergeFields ) { + $reqInfo['fields'] = $this->formatFields( (array)$req->getFieldInfo() ); + } + $ret['requests'][] = $reqInfo; + } + + if ( $mergeFields ) { + $fields = AuthenticationRequest::mergeFieldInfo( $reqs ); + $ret['fields'] = $this->formatFields( $fields ); + } + + return $ret; + } + + /** + * Clean up a field array for output + * @param ApiBase $module For context and parameters 'mergerequestfields' + * and 'messageformat' + * @param array $fields + * @return array + */ + private function formatFields( array $fields ) { + static $copy = [ + 'type' => true, + 'value' => true, + ]; + + $module = $this->module; + $retFields = []; + + foreach ( $fields as $name => $field ) { + $ret = array_intersect_key( $field, $copy ); + + if ( isset( $field['options'] ) ) { + $ret['options'] = array_map( function ( $msg ) use ( $module ) { + return $msg->setContext( $module )->plain(); + }, $field['options'] ); + ApiResult::setArrayType( $ret['options'], 'assoc' ); + } + $this->formatMessage( $ret, 'label', $field['label'] ); + $this->formatMessage( $ret, 'help', $field['help'] ); + $ret['optional'] = !empty( $field['optional'] ); + $ret['sensitive'] = !empty( $field['sensitive'] ); + + $retFields[$name] = $ret; + } + + ApiResult::setArrayType( $retFields, 'assoc' ); + + return $retFields; + } + + /** + * Fetch the standard parameters this helper recognizes + * @param string $action AuthManager action + * @param string $param,... Parameters to use + * @return array + */ + public static function getStandardParams( $action, $param /* ... */ ) { + $params = [ + 'requests' => [ + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-requests', $action ], + ], + 'request' => [ + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true, + ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-request', $action ], + ], + 'messageformat' => [ + ApiBase::PARAM_DFLT => 'wikitext', + ApiBase::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ], + ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-messageformat', + ], + 'mergerequestfields' => [ + ApiBase::PARAM_DFLT => false, + ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-mergerequestfields', + ], + 'preservestate' => [ + ApiBase::PARAM_DFLT => false, + ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-preservestate', + ], + 'returnurl' => [ + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-returnurl', + ], + 'continue' => [ + ApiBase::PARAM_DFLT => false, + ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-continue', + ], + ]; + + $ret = []; + $wantedParams = func_get_args(); + array_shift( $wantedParams ); + foreach ( $wantedParams as $name ) { + if ( isset( $params[$name] ) ) { + $ret[$name] = $params[$name]; + } + } + return $ret; + } +} |