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', $operation ],
112 null, [ 'operation' => $operation ] );
113 // dieWithError prevents continuation
114
115 case AuthManager::SEC_FAIL:
116 $this->module->dieWithError( 'apierror-cannotreauthenticate' );
117 // dieWithError prevents continuation
118
119 default:
120 throw new UnexpectedValueException( "Unknown status \"$status\"" );
121 }
122 }
123
130 public static function blacklistAuthenticationRequests( array $reqs, array $remove ) {
131 if ( $remove ) {
132 $remove = array_fill_keys( $remove, true );
133 $reqs = array_filter( $reqs, static function ( $req ) use ( $remove ) {
134 return !isset( $remove[get_class( $req )] );
135 } );
136 }
137 return $reqs;
138 }
139
146 public function loadAuthenticationRequests( $action, $options = [] ) {
147 $params = $this->module->extractRequestParams();
148
149 $reqs = $this->authManager->getAuthenticationRequests( $action, $this->module->getUser(), $options );
150
151 // Filter requests, if requested to do so
152 $wantedRequests = null;
153 if ( isset( $params['requests'] ) ) {
154 $wantedRequests = array_fill_keys( $params['requests'], true );
155 } elseif ( isset( $params['request'] ) ) {
156 $wantedRequests = [ $params['request'] => true ];
157 }
158 if ( $wantedRequests !== null ) {
159 $reqs = array_filter(
160 $reqs,
161 static function ( AuthenticationRequest $req ) use ( $wantedRequests ) {
162 return isset( $wantedRequests[$req->getUniqueId()] );
163 }
164 );
165 }
166
167 // Collect the fields for all the requests
168 $fields = [];
169 $sensitive = [];
170 foreach ( $reqs as $req ) {
171 $info = (array)$req->getFieldInfo();
172 $fields += $info;
173 $sensitive += array_filter( $info, static function ( $opts ) {
174 return !empty( $opts['sensitive'] );
175 } );
176 }
177
178 // Extract the request data for the fields and mark those request
179 // parameters as used
180 $data = array_intersect_key( $this->module->getRequest()->getValues(), $fields );
181 $this->module->getMain()->markParamsUsed( array_keys( $data ) );
182
183 if ( $sensitive ) {
184 $this->module->getMain()->markParamsSensitive( array_keys( $sensitive ) );
185 $this->module->requirePostedParameters( array_keys( $sensitive ), 'noprefix' );
186 }
187
188 return AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
189 }
190
197 $ret = [
198 'status' => $res->status,
199 ];
200
201 if ( $res->status === AuthenticationResponse::PASS && $res->username !== null ) {
202 $ret['username'] = $res->username;
203 }
204
205 if ( $res->status === AuthenticationResponse::REDIRECT ) {
206 $ret['redirecttarget'] = $res->redirectTarget;
207 if ( $res->redirectApiData !== null ) {
208 $ret['redirectdata'] = $res->redirectApiData;
209 }
210 }
211
212 if ( $res->status === AuthenticationResponse::REDIRECT ||
213 $res->status === AuthenticationResponse::UI ||
214 $res->status === AuthenticationResponse::RESTART
215 ) {
216 $ret += $this->formatRequests( $res->neededRequests );
217 }
218
219 if ( $res->status === AuthenticationResponse::FAIL ||
220 $res->status === AuthenticationResponse::UI ||
221 $res->status === AuthenticationResponse::RESTART
222 ) {
223 $res->message ??= new RawMessage( '' );
224 $this->formatMessage( $ret, 'message', $res->message );
225 $ret['messagecode'] = ApiMessage::create( $res->message )->getApiCode();
226 }
227
228 if ( $res->status === AuthenticationResponse::FAIL ||
229 $res->status === AuthenticationResponse::RESTART
230 ) {
231 $this->module->getRequest()->getSession()->set(
232 'ApiAuthManagerHelper::createRequest',
233 $res->createRequest
234 );
235 $ret['canpreservestate'] = $res->createRequest !== null;
236 } else {
237 $this->module->getRequest()->getSession()->remove( 'ApiAuthManagerHelper::createRequest' );
238 }
239
240 return $ret;
241 }
242
249 public function logAuthenticationResult( $event, UserIdentity $performer, AuthenticationResponse $result ) {
250 if ( !in_array( $result->status, [ AuthenticationResponse::PASS, AuthenticationResponse::FAIL ] ) ) {
251 return;
252 }
253 $accountType = $this->identityUtils->getShortUserTypeInternal( $performer );
254
255 $module = $this->module->getModuleName();
256 LoggerFactory::getInstance( 'authevents' )->info( "$module API attempt", [
257 'event' => $event,
258 'successful' => $result->status === AuthenticationResponse::PASS,
259 'status' => $result->message ? $result->message->getKey() : '-',
260 'accountType' => $accountType,
261 'module' => $module,
262 ] );
263 }
264
269 public function getPreservedRequest() {
270 $ret = $this->module->getRequest()->getSession()->get( 'ApiAuthManagerHelper::createRequest' );
271 return $ret instanceof CreateFromLoginAuthenticationRequest ? $ret : null;
272 }
273
280 public function formatRequests( array $reqs ) {
281 $params = $this->module->extractRequestParams();
282 $mergeFields = !empty( $params['mergerequestfields'] );
283
284 $ret = [ 'requests' => [] ];
285 foreach ( $reqs as $req ) {
286 $describe = $req->describeCredentials();
287 $reqInfo = [
288 'id' => $req->getUniqueId(),
289 'metadata' => $req->getMetadata() + [ ApiResult::META_TYPE => 'assoc' ],
290 ];
291 switch ( $req->required ) {
292 case AuthenticationRequest::OPTIONAL:
293 $reqInfo['required'] = 'optional';
294 break;
295 case AuthenticationRequest::REQUIRED:
296 $reqInfo['required'] = 'required';
297 break;
298 case AuthenticationRequest::PRIMARY_REQUIRED:
299 $reqInfo['required'] = 'primary-required';
300 break;
301 }
302 $this->formatMessage( $reqInfo, 'provider', $describe['provider'] );
303 $this->formatMessage( $reqInfo, 'account', $describe['account'] );
304 if ( !$mergeFields ) {
305 $reqInfo['fields'] = $this->formatFields( (array)$req->getFieldInfo() );
306 }
307 $ret['requests'][] = $reqInfo;
308 }
309
310 if ( $mergeFields ) {
311 $fields = AuthenticationRequest::mergeFieldInfo( $reqs );
312 $ret['fields'] = $this->formatFields( $fields );
313 }
314
315 return $ret;
316 }
317
325 private function formatFields( array $fields ) {
326 static $copy = [
327 'type' => true,
328 'value' => true,
329 ];
330
331 $module = $this->module;
332 $retFields = [];
333
334 foreach ( $fields as $name => $field ) {
335 $ret = array_intersect_key( $field, $copy );
336
337 if ( isset( $field['options'] ) ) {
338 $ret['options'] = array_map( static function ( $msg ) use ( $module ) {
339 return $msg->setContext( $module )->plain();
340 }, $field['options'] );
341 ApiResult::setArrayType( $ret['options'], 'assoc' );
342 }
343 $this->formatMessage( $ret, 'label', $field['label'] ?? new RawMessage( '' ) );
344 $this->formatMessage( $ret, 'help', $field['help'] );
345 $ret['optional'] = !empty( $field['optional'] );
346 $ret['sensitive'] = !empty( $field['sensitive'] );
347
348 $retFields[$name] = $ret;
349 }
350
351 ApiResult::setArrayType( $retFields, 'assoc' );
352
353 return $retFields;
354 }
355
362 public static function getStandardParams( $action, ...$wantedParams ) {
363 $params = [
364 'requests' => [
365 ParamValidator::PARAM_TYPE => 'string',
366 ParamValidator::PARAM_ISMULTI => true,
367 ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-requests', $action ],
368 ],
369 'request' => [
370 ParamValidator::PARAM_TYPE => 'string',
371 ParamValidator::PARAM_REQUIRED => true,
372 ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-request', $action ],
373 ],
374 'messageformat' => [
375 ParamValidator::PARAM_DEFAULT => 'wikitext',
376 ParamValidator::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ],
377 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-messageformat',
378 ],
379 'mergerequestfields' => [
380 ParamValidator::PARAM_DEFAULT => false,
381 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-mergerequestfields',
382 ],
383 'preservestate' => [
384 ParamValidator::PARAM_DEFAULT => false,
385 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-preservestate',
386 ],
387 'returnurl' => [
388 ParamValidator::PARAM_TYPE => 'string',
389 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-returnurl',
390 ],
391 'continue' => [
392 ParamValidator::PARAM_DEFAULT => false,
393 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-continue',
394 ],
395 ];
396
397 $ret = [];
398 foreach ( $wantedParams as $name ) {
399 if ( isset( $params[$name] ) ) {
400 $ret[$name] = $params[$name];
401 }
402 }
403 return $ret;
404 }
405}
406
408class_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.
formatRequests(array $reqs)
Format an array of AuthenticationRequests for return.
static blacklistAuthenticationRequests(array $reqs, array $remove)
Filter out authentication requests by class name.
loadAuthenticationRequests( $action, $options=[])
Fetch and load the AuthenticationRequests for an action.
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.