MediaWiki master
ApiAuthManagerHelper.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\Api;
11
22use UnexpectedValueException;
24
32
34 private $module;
35
37 private $messageFormat;
38
39 private AuthManager $authManager;
40
41 private UserIdentityUtils $identityUtils;
42
48 public function __construct(
49 ApiBase $module,
50 ?AuthManager $authManager = null,
51 ?UserIdentityUtils $identityUtils = null
52 ) {
53 $this->module = $module;
54
55 $params = $module->extractRequestParams();
56 $this->messageFormat = $params['messageformat'] ?? 'wikitext';
57 $this->authManager = $authManager ?? MediaWikiServices::getInstance()->getAuthManager();
58 // TODO: inject this as currently it's always taken from container
59 $this->identityUtils = $identityUtils ?? MediaWikiServices::getInstance()->getUserIdentityUtils();
60 }
61
68 public static function newForModule( ApiBase $module, ?AuthManager $authManager = null ) {
69 return new self( $module, $authManager );
70 }
71
78 private function formatMessage( array &$res, $key, Message $message ) {
79 switch ( $this->messageFormat ) {
80 case 'none':
81 break;
82
83 case 'wikitext':
84 $res[$key] = $message->setContext( $this->module )->text();
85 break;
86
87 case 'html':
88 $res[$key] = $message->setContext( $this->module )->parseAsBlock();
89 $res[$key] = Parser::stripOuterParagraph( $res[$key] );
90 break;
91
92 case 'raw':
93 $params = $message->getParams();
94 $res[$key] = [
95 'key' => $message->getKey(),
96 'params' => $params,
97 ];
98 ApiResult::setIndexedTagName( $params, 'param' );
99 break;
100 }
101 }
102
108 public function securitySensitiveOperation( $operation ) {
109 $status = $this->authManager->securitySensitiveOperationStatus( $operation );
110 switch ( $status ) {
111 case AuthManager::SEC_OK:
112 return;
113
114 case AuthManager::SEC_REAUTH:
115 $this->module->dieWithError( 'apierror-reauthenticate' );
116 // dieWithError prevents continuation
117
118 case AuthManager::SEC_FAIL:
119 $this->module->dieWithError( 'apierror-cannotreauthenticate' );
120 // dieWithError prevents continuation
121
122 default:
123 throw new UnexpectedValueException( "Unknown status \"$status\"" );
124 }
125 }
126
133 public static function blacklistAuthenticationRequests( array $reqs, array $remove ) {
134 if ( $remove ) {
135 $remove = array_fill_keys( $remove, true );
136 $reqs = array_filter( $reqs, static function ( $req ) use ( $remove ) {
137 return !isset( $remove[get_class( $req )] );
138 } );
139 }
140 return $reqs;
141 }
142
148 public function loadAuthenticationRequests( $action ) {
149 $params = $this->module->extractRequestParams();
150
151 $reqs = $this->authManager->getAuthenticationRequests( $action, $this->module->getUser() );
152
153 // Filter requests, if requested to do so
154 $wantedRequests = null;
155 if ( isset( $params['requests'] ) ) {
156 $wantedRequests = array_fill_keys( $params['requests'], true );
157 } elseif ( isset( $params['request'] ) ) {
158 $wantedRequests = [ $params['request'] => true ];
159 }
160 if ( $wantedRequests !== null ) {
161 $reqs = array_filter(
162 $reqs,
163 static function ( AuthenticationRequest $req ) use ( $wantedRequests ) {
164 return isset( $wantedRequests[$req->getUniqueId()] );
165 }
166 );
167 }
168
169 // Collect the fields for all the requests
170 $fields = [];
171 $sensitive = [];
172 foreach ( $reqs as $req ) {
173 $info = (array)$req->getFieldInfo();
174 $fields += $info;
175 $sensitive += array_filter( $info, static function ( $opts ) {
176 return !empty( $opts['sensitive'] );
177 } );
178 }
179
180 // Extract the request data for the fields and mark those request
181 // parameters as used
182 $data = array_intersect_key( $this->module->getRequest()->getValues(), $fields );
183 $this->module->getMain()->markParamsUsed( array_keys( $data ) );
184
185 if ( $sensitive ) {
186 $this->module->getMain()->markParamsSensitive( array_keys( $sensitive ) );
187 $this->module->requirePostedParameters( array_keys( $sensitive ), 'noprefix' );
188 }
189
190 return AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
191 }
192
199 $ret = [
200 'status' => $res->status,
201 ];
202
203 if ( $res->status === AuthenticationResponse::PASS && $res->username !== null ) {
204 $ret['username'] = $res->username;
205 }
206
207 if ( $res->status === AuthenticationResponse::REDIRECT ) {
208 $ret['redirecttarget'] = $res->redirectTarget;
209 if ( $res->redirectApiData !== null ) {
210 $ret['redirectdata'] = $res->redirectApiData;
211 }
212 }
213
214 if ( $res->status === AuthenticationResponse::REDIRECT ||
215 $res->status === AuthenticationResponse::UI ||
216 $res->status === AuthenticationResponse::RESTART
217 ) {
218 $ret += $this->formatRequests( $res->neededRequests );
219 }
220
221 if ( $res->status === AuthenticationResponse::FAIL ||
222 $res->status === AuthenticationResponse::UI ||
223 $res->status === AuthenticationResponse::RESTART
224 ) {
225 $this->formatMessage( $ret, 'message', $res->message );
226 $ret['messagecode'] = ApiMessage::create( $res->message )->getApiCode();
227 }
228
229 if ( $res->status === AuthenticationResponse::FAIL ||
230 $res->status === AuthenticationResponse::RESTART
231 ) {
232 $this->module->getRequest()->getSession()->set(
233 'ApiAuthManagerHelper::createRequest',
234 $res->createRequest
235 );
236 $ret['canpreservestate'] = $res->createRequest !== null;
237 } else {
238 $this->module->getRequest()->getSession()->remove( 'ApiAuthManagerHelper::createRequest' );
239 }
240
241 return $ret;
242 }
243
250 public function logAuthenticationResult( $event, UserIdentity $performer, AuthenticationResponse $result ) {
251 if ( !in_array( $result->status, [ AuthenticationResponse::PASS, AuthenticationResponse::FAIL ] ) ) {
252 return;
253 }
254 $accountType = $this->identityUtils->getShortUserTypeInternal( $performer );
255
256 $module = $this->module->getModuleName();
257 LoggerFactory::getInstance( 'authevents' )->info( "$module API attempt", [
258 'event' => $event,
259 'successful' => $result->status === AuthenticationResponse::PASS,
260 'status' => $result->message ? $result->message->getKey() : '-',
261 'accountType' => $accountType,
262 'module' => $module,
263 ] );
264 }
265
270 public function getPreservedRequest() {
271 $ret = $this->module->getRequest()->getSession()->get( 'ApiAuthManagerHelper::createRequest' );
272 return $ret instanceof CreateFromLoginAuthenticationRequest ? $ret : null;
273 }
274
281 public function formatRequests( array $reqs ) {
282 $params = $this->module->extractRequestParams();
283 $mergeFields = !empty( $params['mergerequestfields'] );
284
285 $ret = [ 'requests' => [] ];
286 foreach ( $reqs as $req ) {
287 $describe = $req->describeCredentials();
288 $reqInfo = [
289 'id' => $req->getUniqueId(),
290 'metadata' => $req->getMetadata() + [ ApiResult::META_TYPE => 'assoc' ],
291 ];
292 switch ( $req->required ) {
293 case AuthenticationRequest::OPTIONAL:
294 $reqInfo['required'] = 'optional';
295 break;
296 case AuthenticationRequest::REQUIRED:
297 $reqInfo['required'] = 'required';
298 break;
299 case AuthenticationRequest::PRIMARY_REQUIRED:
300 $reqInfo['required'] = 'primary-required';
301 break;
302 }
303 $this->formatMessage( $reqInfo, 'provider', $describe['provider'] );
304 $this->formatMessage( $reqInfo, 'account', $describe['account'] );
305 if ( !$mergeFields ) {
306 $reqInfo['fields'] = $this->formatFields( (array)$req->getFieldInfo() );
307 }
308 $ret['requests'][] = $reqInfo;
309 }
310
311 if ( $mergeFields ) {
312 $fields = AuthenticationRequest::mergeFieldInfo( $reqs );
313 $ret['fields'] = $this->formatFields( $fields );
314 }
315
316 return $ret;
317 }
318
326 private function formatFields( array $fields ) {
327 static $copy = [
328 'type' => true,
329 'value' => true,
330 ];
331
332 $module = $this->module;
333 $retFields = [];
334
335 foreach ( $fields as $name => $field ) {
336 $ret = array_intersect_key( $field, $copy );
337
338 if ( isset( $field['options'] ) ) {
339 $ret['options'] = array_map( static function ( $msg ) use ( $module ) {
340 return $msg->setContext( $module )->plain();
341 }, $field['options'] );
342 ApiResult::setArrayType( $ret['options'], 'assoc' );
343 }
344 $this->formatMessage( $ret, 'label', $field['label'] );
345 $this->formatMessage( $ret, 'help', $field['help'] );
346 $ret['optional'] = !empty( $field['optional'] );
347 $ret['sensitive'] = !empty( $field['sensitive'] );
348
349 $retFields[$name] = $ret;
350 }
351
352 ApiResult::setArrayType( $retFields, 'assoc' );
353
354 return $retFields;
355 }
356
363 public static function getStandardParams( $action, ...$wantedParams ) {
364 $params = [
365 'requests' => [
366 ParamValidator::PARAM_TYPE => 'string',
367 ParamValidator::PARAM_ISMULTI => true,
368 ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-requests', $action ],
369 ],
370 'request' => [
371 ParamValidator::PARAM_TYPE => 'string',
372 ParamValidator::PARAM_REQUIRED => true,
373 ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-request', $action ],
374 ],
375 'messageformat' => [
376 ParamValidator::PARAM_DEFAULT => 'wikitext',
377 ParamValidator::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ],
378 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-messageformat',
379 ],
380 'mergerequestfields' => [
381 ParamValidator::PARAM_DEFAULT => false,
382 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-mergerequestfields',
383 ],
384 'preservestate' => [
385 ParamValidator::PARAM_DEFAULT => false,
386 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-preservestate',
387 ],
388 'returnurl' => [
389 ParamValidator::PARAM_TYPE => 'string',
390 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-returnurl',
391 ],
392 'continue' => [
393 ParamValidator::PARAM_DEFAULT => false,
394 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-continue',
395 ],
396 ];
397
398 $ret = [];
399 foreach ( $wantedParams as $name ) {
400 if ( isset( $params[$name] ) ) {
401 $ret[$name] = $params[$name];
402 }
403 }
404 return $ret;
405 }
406}
407
409class_alias( ApiAuthManagerHelper::class, 'ApiAuthManagerHelper' );
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:61
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:543
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:167
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:823
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.
AuthManager is the authentication system in MediaWiki and serves entry point for authentication.
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:144
getParams()
Returns the message parameters.
Definition Message.php:402
setContext(IContextSource $context)
Set the language and the title from a context object.
Definition Message.php:870
getKey()
Returns the message key.
Definition Message.php:391
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:135
Convenience functions for interpreting UserIdentity objects using additional services or config.
Service for formatting and validating API parameters.
Interface for objects representing user identity.