MediaWiki  master
ApiAuthManagerHelper.php
Go to the documentation of this file.
1 <?php
32 
40 
42  private $module;
43 
45  private $messageFormat;
46 
47  private AuthManager $authManager;
48 
53  public function __construct( ApiBase $module, AuthManager $authManager = null ) {
54  $this->module = $module;
55 
56  $params = $module->extractRequestParams();
57  $this->messageFormat = $params['messageformat'] ?? 'wikitext';
58  $this->authManager = $authManager ?: MediaWikiServices::getInstance()->getAuthManager();
59  }
60 
67  public static function newForModule( ApiBase $module, AuthManager $authManager = null ) {
68  return new self( $module, $authManager );
69  }
70 
77  private function formatMessage( array &$res, $key, Message $message ) {
78  switch ( $this->messageFormat ) {
79  case 'none':
80  break;
81 
82  case 'wikitext':
83  $res[$key] = $message->setContext( $this->module )->text();
84  break;
85 
86  case 'html':
87  $res[$key] = $message->setContext( $this->module )->parseAsBlock();
88  $res[$key] = Parser::stripOuterParagraph( $res[$key] );
89  break;
90 
91  case 'raw':
92  $params = $message->getParams();
93  $res[$key] = [
94  'key' => $message->getKey(),
95  'params' => $params,
96  ];
97  ApiResult::setIndexedTagName( $params, 'param' );
98  break;
99  }
100  }
101 
107  public function securitySensitiveOperation( $operation ) {
108  $status = $this->authManager->securitySensitiveOperationStatus( $operation );
109  switch ( $status ) {
110  case AuthManager::SEC_OK:
111  return;
112 
113  case AuthManager::SEC_REAUTH:
114  $this->module->dieWithError( 'apierror-reauthenticate' );
115  // dieWithError prevents continuation
116 
117  case AuthManager::SEC_FAIL:
118  $this->module->dieWithError( 'apierror-cannotreauthenticate' );
119  // dieWithError prevents continuation
120 
121  default:
122  throw new UnexpectedValueException( "Unknown status \"$status\"" );
123  }
124  }
125 
132  public static function blacklistAuthenticationRequests( array $reqs, array $remove ) {
133  if ( $remove ) {
134  $remove = array_fill_keys( $remove, true );
135  $reqs = array_filter( $reqs, static function ( $req ) use ( $remove ) {
136  return !isset( $remove[get_class( $req )] );
137  } );
138  }
139  return $reqs;
140  }
141 
147  public function loadAuthenticationRequests( $action ) {
148  $params = $this->module->extractRequestParams();
149 
150  $reqs = $this->authManager->getAuthenticationRequests( $action, $this->module->getUser() );
151 
152  // Filter requests, if requested to do so
153  $wantedRequests = null;
154  if ( isset( $params['requests'] ) ) {
155  $wantedRequests = array_fill_keys( $params['requests'], true );
156  } elseif ( isset( $params['request'] ) ) {
157  $wantedRequests = [ $params['request'] => true ];
158  }
159  if ( $wantedRequests !== null ) {
160  $reqs = array_filter(
161  $reqs,
162  static function ( AuthenticationRequest $req ) use ( $wantedRequests ) {
163  return isset( $wantedRequests[$req->getUniqueId()] );
164  }
165  );
166  }
167 
168  // Collect the fields for all the requests
169  $fields = [];
170  $sensitive = [];
171  foreach ( $reqs as $req ) {
172  $info = (array)$req->getFieldInfo();
173  $fields += $info;
174  $sensitive += array_filter( $info, static function ( $opts ) {
175  return !empty( $opts['sensitive'] );
176  } );
177  }
178 
179  // Extract the request data for the fields and mark those request
180  // parameters as used
181  $data = array_intersect_key( $this->module->getRequest()->getValues(), $fields );
182  $this->module->getMain()->markParamsUsed( array_keys( $data ) );
183 
184  if ( $sensitive ) {
185  $this->module->getMain()->markParamsSensitive( array_keys( $sensitive ) );
186  $this->module->requirePostedParameters( array_keys( $sensitive ), 'noprefix' );
187  }
188 
189  return AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
190  }
191 
198  $ret = [
199  'status' => $res->status,
200  ];
201 
202  if ( $res->status === AuthenticationResponse::PASS && $res->username !== null ) {
203  $ret['username'] = $res->username;
204  }
205 
206  if ( $res->status === AuthenticationResponse::REDIRECT ) {
207  $ret['redirecttarget'] = $res->redirectTarget;
208  if ( $res->redirectApiData !== null ) {
209  $ret['redirectdata'] = $res->redirectApiData;
210  }
211  }
212 
213  if ( $res->status === AuthenticationResponse::REDIRECT ||
214  $res->status === AuthenticationResponse::UI ||
215  $res->status === AuthenticationResponse::RESTART
216  ) {
217  $ret += $this->formatRequests( $res->neededRequests );
218  }
219 
220  if ( $res->status === AuthenticationResponse::FAIL ||
221  $res->status === AuthenticationResponse::UI ||
222  $res->status === AuthenticationResponse::RESTART
223  ) {
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 
248  public function logAuthenticationResult( $event, $result ) {
249  if ( is_string( $result ) ) {
250  $status = Status::newFatal( $result );
251  } elseif ( $result->status === AuthenticationResponse::PASS ) {
252  $status = Status::newGood();
253  } elseif ( $result->status === AuthenticationResponse::FAIL ) {
254  $status = Status::newFatal( $result->message );
255  } else {
256  return;
257  }
258 
259  $module = $this->module->getModuleName();
260  LoggerFactory::getInstance( 'authevents' )->info( "$module API attempt", [
261  'event' => $event,
262  'status' => strval( $status ),
263  'module' => $module,
264  ] );
265  }
266 
271  public function getPreservedRequest() {
272  $ret = $this->module->getRequest()->getSession()->get( 'ApiAuthManagerHelper::createRequest' );
273  return $ret instanceof CreateFromLoginAuthenticationRequest ? $ret : null;
274  }
275 
282  public function formatRequests( array $reqs ) {
283  $params = $this->module->extractRequestParams();
284  $mergeFields = !empty( $params['mergerequestfields'] );
285 
286  $ret = [ 'requests' => [] ];
287  foreach ( $reqs as $req ) {
288  $describe = $req->describeCredentials();
289  $reqInfo = [
290  'id' => $req->getUniqueId(),
291  'metadata' => $req->getMetadata() + [ ApiResult::META_TYPE => 'assoc' ],
292  ];
293  switch ( $req->required ) {
294  case AuthenticationRequest::OPTIONAL:
295  $reqInfo['required'] = 'optional';
296  break;
297  case AuthenticationRequest::REQUIRED:
298  $reqInfo['required'] = 'required';
299  break;
300  case AuthenticationRequest::PRIMARY_REQUIRED:
301  $reqInfo['required'] = 'primary-required';
302  break;
303  }
304  $this->formatMessage( $reqInfo, 'provider', $describe['provider'] );
305  $this->formatMessage( $reqInfo, 'account', $describe['account'] );
306  if ( !$mergeFields ) {
307  $reqInfo['fields'] = $this->formatFields( (array)$req->getFieldInfo() );
308  }
309  $ret['requests'][] = $reqInfo;
310  }
311 
312  if ( $mergeFields ) {
313  $fields = AuthenticationRequest::mergeFieldInfo( $reqs );
314  $ret['fields'] = $this->formatFields( $fields );
315  }
316 
317  return $ret;
318  }
319 
327  private function formatFields( array $fields ) {
328  static $copy = [
329  'type' => true,
330  'value' => true,
331  ];
332 
333  $module = $this->module;
334  $retFields = [];
335 
336  foreach ( $fields as $name => $field ) {
337  $ret = array_intersect_key( $field, $copy );
338 
339  if ( isset( $field['options'] ) ) {
340  $ret['options'] = array_map( static function ( $msg ) use ( $module ) {
341  return $msg->setContext( $module )->plain();
342  }, $field['options'] );
343  ApiResult::setArrayType( $ret['options'], 'assoc' );
344  }
345  $this->formatMessage( $ret, 'label', $field['label'] );
346  $this->formatMessage( $ret, 'help', $field['help'] );
347  $ret['optional'] = !empty( $field['optional'] );
348  $ret['sensitive'] = !empty( $field['sensitive'] );
349 
350  $retFields[$name] = $ret;
351  }
352 
353  ApiResult::setArrayType( $retFields, 'assoc' );
354 
355  return $retFields;
356  }
357 
364  public static function getStandardParams( $action, ...$wantedParams ) {
365  $params = [
366  'requests' => [
367  ParamValidator::PARAM_TYPE => 'string',
368  ParamValidator::PARAM_ISMULTI => true,
369  ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-requests', $action ],
370  ],
371  'request' => [
372  ParamValidator::PARAM_TYPE => 'string',
373  ParamValidator::PARAM_REQUIRED => true,
374  ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-request', $action ],
375  ],
376  'messageformat' => [
377  ParamValidator::PARAM_DEFAULT => 'wikitext',
378  ParamValidator::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ],
379  ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-messageformat',
380  ],
381  'mergerequestfields' => [
382  ParamValidator::PARAM_DEFAULT => false,
383  ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-mergerequestfields',
384  ],
385  'preservestate' => [
386  ParamValidator::PARAM_DEFAULT => false,
387  ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-preservestate',
388  ],
389  'returnurl' => [
390  ParamValidator::PARAM_TYPE => 'string',
391  ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-returnurl',
392  ],
393  'continue' => [
394  ParamValidator::PARAM_DEFAULT => false,
395  ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-continue',
396  ],
397  ];
398 
399  $ret = [];
400  foreach ( $wantedParams as $name ) {
401  if ( isset( $params[$name] ) ) {
402  $ret[$name] = $params[$name];
403  }
404  }
405  return $ret;
406  }
407 }
Helper class for AuthManager-using API modules.
static newForModule(ApiBase $module, AuthManager $authManager=null)
Static version of the constructor, for chaining.
logAuthenticationResult( $event, $result)
Logs successful or failed authentication.
getPreservedRequest()
Fetch the preserved CreateFromLoginAuthenticationRequest, if any.
static getStandardParams( $action,... $wantedParams)
Fetch the standard parameters this helper recognizes.
static blacklistAuthenticationRequests(array $reqs, array $remove)
Filter out authentication requests by class name.
formatAuthenticationResponse(AuthenticationResponse $res)
Format an AuthenticationResponse for return.
__construct(ApiBase $module, AuthManager $authManager=null)
formatRequests(array $reqs)
Format an array of AuthenticationRequests for return.
securitySensitiveOperation( $operation)
Call $manager->securitySensitiveOperationStatus()
loadAuthenticationRequests( $action)
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:63
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:808
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:170
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:529
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:45
const META_TYPE
Key for the 'type' metadata item.
Definition: ApiResult.php:110
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:716
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
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.
PSR-3 logger instance factory.
Service locator for MediaWiki core services.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:58
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:376
getKey()
Returns the message key.
Definition: Message.php:365
setContext(IContextSource $context)
Set the language and the title from a context object.
Definition: Message.php:819
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6352
Service for formatting and validating API parameters.
return true
Definition: router.php:90