MediaWiki master
ApiAuthManagerHelper.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Api;
25
36use UnexpectedValueException;
38
46
48 private $module;
49
51 private $messageFormat;
52
53 private AuthManager $authManager;
54
55 private UserIdentityUtils $identityUtils;
56
62 public function __construct(
63 ApiBase $module,
64 ?AuthManager $authManager = null,
65 ?UserIdentityUtils $identityUtils = null
66 ) {
67 $this->module = $module;
68
69 $params = $module->extractRequestParams();
70 $this->messageFormat = $params['messageformat'] ?? 'wikitext';
71 $this->authManager = $authManager ?: MediaWikiServices::getInstance()->getAuthManager();
72 // TODO: inject this as currently it's always taken from container
73 $this->identityUtils = $identityUtils ?: MediaWikiServices::getInstance()->getUserIdentityUtils();
74 }
75
82 public static function newForModule( ApiBase $module, ?AuthManager $authManager = null ) {
83 return new self( $module, $authManager );
84 }
85
92 private function formatMessage( array &$res, $key, Message $message ) {
93 switch ( $this->messageFormat ) {
94 case 'none':
95 break;
96
97 case 'wikitext':
98 $res[$key] = $message->setContext( $this->module )->text();
99 break;
100
101 case 'html':
102 $res[$key] = $message->setContext( $this->module )->parseAsBlock();
103 $res[$key] = Parser::stripOuterParagraph( $res[$key] );
104 break;
105
106 case 'raw':
107 $params = $message->getParams();
108 $res[$key] = [
109 'key' => $message->getKey(),
110 'params' => $params,
111 ];
113 break;
114 }
115 }
116
122 public function securitySensitiveOperation( $operation ) {
123 $status = $this->authManager->securitySensitiveOperationStatus( $operation );
124 switch ( $status ) {
125 case AuthManager::SEC_OK:
126 return;
127
128 case AuthManager::SEC_REAUTH:
129 $this->module->dieWithError( 'apierror-reauthenticate' );
130 // dieWithError prevents continuation
131
132 case AuthManager::SEC_FAIL:
133 $this->module->dieWithError( 'apierror-cannotreauthenticate' );
134 // dieWithError prevents continuation
135
136 default:
137 throw new UnexpectedValueException( "Unknown status \"$status\"" );
138 }
139 }
140
147 public static function blacklistAuthenticationRequests( array $reqs, array $remove ) {
148 if ( $remove ) {
149 $remove = array_fill_keys( $remove, true );
150 $reqs = array_filter( $reqs, static function ( $req ) use ( $remove ) {
151 return !isset( $remove[get_class( $req )] );
152 } );
153 }
154 return $reqs;
155 }
156
162 public function loadAuthenticationRequests( $action ) {
163 $params = $this->module->extractRequestParams();
164
165 $reqs = $this->authManager->getAuthenticationRequests( $action, $this->module->getUser() );
166
167 // Filter requests, if requested to do so
168 $wantedRequests = null;
169 if ( isset( $params['requests'] ) ) {
170 $wantedRequests = array_fill_keys( $params['requests'], true );
171 } elseif ( isset( $params['request'] ) ) {
172 $wantedRequests = [ $params['request'] => true ];
173 }
174 if ( $wantedRequests !== null ) {
175 $reqs = array_filter(
176 $reqs,
177 static function ( AuthenticationRequest $req ) use ( $wantedRequests ) {
178 return isset( $wantedRequests[$req->getUniqueId()] );
179 }
180 );
181 }
182
183 // Collect the fields for all the requests
184 $fields = [];
185 $sensitive = [];
186 foreach ( $reqs as $req ) {
187 $info = (array)$req->getFieldInfo();
188 $fields += $info;
189 $sensitive += array_filter( $info, static function ( $opts ) {
190 return !empty( $opts['sensitive'] );
191 } );
192 }
193
194 // Extract the request data for the fields and mark those request
195 // parameters as used
196 $data = array_intersect_key( $this->module->getRequest()->getValues(), $fields );
197 $this->module->getMain()->markParamsUsed( array_keys( $data ) );
198
199 if ( $sensitive ) {
200 $this->module->getMain()->markParamsSensitive( array_keys( $sensitive ) );
201 $this->module->requirePostedParameters( array_keys( $sensitive ), 'noprefix' );
202 }
203
204 return AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
205 }
206
213 $ret = [
214 'status' => $res->status,
215 ];
216
217 if ( $res->status === AuthenticationResponse::PASS && $res->username !== null ) {
218 $ret['username'] = $res->username;
219 }
220
221 if ( $res->status === AuthenticationResponse::REDIRECT ) {
222 $ret['redirecttarget'] = $res->redirectTarget;
223 if ( $res->redirectApiData !== null ) {
224 $ret['redirectdata'] = $res->redirectApiData;
225 }
226 }
227
228 if ( $res->status === AuthenticationResponse::REDIRECT ||
229 $res->status === AuthenticationResponse::UI ||
230 $res->status === AuthenticationResponse::RESTART
231 ) {
232 $ret += $this->formatRequests( $res->neededRequests );
233 }
234
235 if ( $res->status === AuthenticationResponse::FAIL ||
236 $res->status === AuthenticationResponse::UI ||
237 $res->status === AuthenticationResponse::RESTART
238 ) {
239 $this->formatMessage( $ret, 'message', $res->message );
240 $ret['messagecode'] = ApiMessage::create( $res->message )->getApiCode();
241 }
242
243 if ( $res->status === AuthenticationResponse::FAIL ||
244 $res->status === AuthenticationResponse::RESTART
245 ) {
246 $this->module->getRequest()->getSession()->set(
247 'ApiAuthManagerHelper::createRequest',
248 $res->createRequest
249 );
250 $ret['canpreservestate'] = $res->createRequest !== null;
251 } else {
252 $this->module->getRequest()->getSession()->remove( 'ApiAuthManagerHelper::createRequest' );
253 }
254
255 return $ret;
256 }
257
264 public function logAuthenticationResult( $event, UserIdentity $performer, AuthenticationResponse $result ) {
265 if ( !in_array( $result->status, [ AuthenticationResponse::PASS, AuthenticationResponse::FAIL ] ) ) {
266 return;
267 }
268 $accountType = $this->identityUtils->getShortUserTypeInternal( $performer );
269
270 $module = $this->module->getModuleName();
271 LoggerFactory::getInstance( 'authevents' )->info( "$module API attempt", [
272 'event' => $event,
273 'successful' => $result->status === AuthenticationResponse::PASS,
274 'status' => $result->message ? $result->message->getKey() : '-',
275 'accountType' => $accountType,
276 'module' => $module,
277 ] );
278 }
279
284 public function getPreservedRequest() {
285 $ret = $this->module->getRequest()->getSession()->get( 'ApiAuthManagerHelper::createRequest' );
286 return $ret instanceof CreateFromLoginAuthenticationRequest ? $ret : null;
287 }
288
295 public function formatRequests( array $reqs ) {
296 $params = $this->module->extractRequestParams();
297 $mergeFields = !empty( $params['mergerequestfields'] );
298
299 $ret = [ 'requests' => [] ];
300 foreach ( $reqs as $req ) {
301 $describe = $req->describeCredentials();
302 $reqInfo = [
303 'id' => $req->getUniqueId(),
304 'metadata' => $req->getMetadata() + [ ApiResult::META_TYPE => 'assoc' ],
305 ];
306 switch ( $req->required ) {
307 case AuthenticationRequest::OPTIONAL:
308 $reqInfo['required'] = 'optional';
309 break;
310 case AuthenticationRequest::REQUIRED:
311 $reqInfo['required'] = 'required';
312 break;
313 case AuthenticationRequest::PRIMARY_REQUIRED:
314 $reqInfo['required'] = 'primary-required';
315 break;
316 }
317 $this->formatMessage( $reqInfo, 'provider', $describe['provider'] );
318 $this->formatMessage( $reqInfo, 'account', $describe['account'] );
319 if ( !$mergeFields ) {
320 $reqInfo['fields'] = $this->formatFields( (array)$req->getFieldInfo() );
321 }
322 $ret['requests'][] = $reqInfo;
323 }
324
325 if ( $mergeFields ) {
326 $fields = AuthenticationRequest::mergeFieldInfo( $reqs );
327 $ret['fields'] = $this->formatFields( $fields );
328 }
329
330 return $ret;
331 }
332
340 private function formatFields( array $fields ) {
341 static $copy = [
342 'type' => true,
343 'value' => true,
344 ];
345
346 $module = $this->module;
347 $retFields = [];
348
349 foreach ( $fields as $name => $field ) {
350 $ret = array_intersect_key( $field, $copy );
351
352 if ( isset( $field['options'] ) ) {
353 $ret['options'] = array_map( static function ( $msg ) use ( $module ) {
354 return $msg->setContext( $module )->plain();
355 }, $field['options'] );
356 ApiResult::setArrayType( $ret['options'], 'assoc' );
357 }
358 $this->formatMessage( $ret, 'label', $field['label'] );
359 $this->formatMessage( $ret, 'help', $field['help'] );
360 $ret['optional'] = !empty( $field['optional'] );
361 $ret['sensitive'] = !empty( $field['sensitive'] );
362
363 $retFields[$name] = $ret;
364 }
365
366 ApiResult::setArrayType( $retFields, 'assoc' );
367
368 return $retFields;
369 }
370
377 public static function getStandardParams( $action, ...$wantedParams ) {
378 $params = [
379 'requests' => [
380 ParamValidator::PARAM_TYPE => 'string',
381 ParamValidator::PARAM_ISMULTI => true,
382 ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-requests', $action ],
383 ],
384 'request' => [
385 ParamValidator::PARAM_TYPE => 'string',
386 ParamValidator::PARAM_REQUIRED => true,
387 ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-request', $action ],
388 ],
389 'messageformat' => [
390 ParamValidator::PARAM_DEFAULT => 'wikitext',
391 ParamValidator::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ],
392 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-messageformat',
393 ],
394 'mergerequestfields' => [
395 ParamValidator::PARAM_DEFAULT => false,
396 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-mergerequestfields',
397 ],
398 'preservestate' => [
399 ParamValidator::PARAM_DEFAULT => false,
400 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-preservestate',
401 ],
402 'returnurl' => [
403 ParamValidator::PARAM_TYPE => 'string',
404 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-returnurl',
405 ],
406 'continue' => [
407 ParamValidator::PARAM_DEFAULT => false,
408 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-continue',
409 ],
410 ];
411
412 $ret = [];
413 foreach ( $wantedParams as $name ) {
414 if ( isset( $params[$name] ) ) {
415 $ret[$name] = $params[$name];
416 }
417 }
418 return $ret;
419 }
420}
421
423class_alias( ApiAuthManagerHelper::class, 'ApiAuthManagerHelper' );
array $params
The job parameters.
Helper class for AuthManager-using API modules.
static getStandardParams( $action,... $wantedParams)
Fetch the standard parameters this helper recognizes.
securitySensitiveOperation( $operation)
Call $manager->securitySensitiveOperationStatus()
logAuthenticationResult( $event, UserIdentity $performer, AuthenticationResponse $result)
Logs successful or failed authentication.
formatAuthenticationResponse(AuthenticationResponse $res)
Format an AuthenticationResponse for return.
getPreservedRequest()
Fetch the preserved CreateFromLoginAuthenticationRequest, if any.
static newForModule(ApiBase $module, ?AuthManager $authManager=null)
Static version of the constructor, for chaining.
__construct(ApiBase $module, ?AuthManager $authManager=null, ?UserIdentityUtils $identityUtils=null)
loadAuthenticationRequests( $action)
Fetch and load the AuthenticationRequests for an action.
formatRequests(array $reqs)
Format an array of AuthenticationRequests for return.
static blacklistAuthenticationRequests(array $reqs, array $remove)
Filter out authentication requests by class name.
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:76
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:571
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:184
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:851
static create( $msg, $code=null, ?array $data=null)
Create an IApiMessage for the message.
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
const META_TYPE
Key for the 'type' metadata item.
This serves as the entry point to the authentication system.
This is a value object for authentication requests.
getUniqueId()
Supply a unique key for deduplication.
This is a value object to hold authentication response data.
This transfers state between the login and account creation flows.
Create PSR-3 logger objects.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:155
getParams()
Returns the message parameters.
Definition Message.php:412
setContext(IContextSource $context)
Set the language and the title from a context object.
Definition Message.php:849
getKey()
Returns the message key.
Definition Message.php:401
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:148
Convenience functions for interpreting UserIdentity objects using additional services or config.
Service for formatting and validating API parameters.
Interface for objects representing user identity.