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