MediaWiki  master
AuthenticationRequest.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Auth;
25 
26 use Message;
27 
38 abstract class AuthenticationRequest {
39 
41  public const OPTIONAL = 0;
42 
47  public const REQUIRED = 1;
48 
53  public const PRIMARY_REQUIRED = 2;
54 
59  public $action = null;
60 
65 
67  public $returnToUrl = null;
68 
72  public $username = null;
73 
90  public function getUniqueId() {
91  return get_called_class();
92  }
93 
129  abstract public function getFieldInfo();
130 
142  public function getMetadata() {
143  return [];
144  }
145 
159  public function loadFromSubmission( array $data ) {
160  $fields = array_filter( $this->getFieldInfo(), function ( $info ) {
161  return $info['type'] !== 'null';
162  } );
163  if ( !$fields ) {
164  return false;
165  }
166 
167  foreach ( $fields as $field => $info ) {
168  // Checkboxes and buttons are special. Depending on the method used
169  // to populate $data, they might be unset meaning false or they
170  // might be boolean. Further, image buttons might submit the
171  // coordinates of the click rather than the expected value.
172  if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
173  $this->$field = isset( $data[$field] ) && $data[$field] !== false
174  || isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false;
175  if ( !$this->$field && empty( $info['optional'] ) ) {
176  return false;
177  }
178  continue;
179  }
180 
181  // Multiselect are too, slightly
182  if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
183  $data[$field] = [];
184  }
185 
186  if ( !isset( $data[$field] ) ) {
187  return false;
188  }
189  if ( $data[$field] === '' || $data[$field] === [] ) {
190  if ( empty( $info['optional'] ) ) {
191  return false;
192  }
193  } else {
194  switch ( $info['type'] ) {
195  case 'select':
196  if ( !isset( $info['options'][$data[$field]] ) ) {
197  return false;
198  }
199  break;
200 
201  case 'multiselect':
202  $data[$field] = (array)$data[$field];
203  $allowed = array_keys( $info['options'] );
204  if ( array_diff( $data[$field], $allowed ) !== [] ) {
205  return false;
206  }
207  break;
208  }
209  }
210 
211  $this->$field = $data[$field];
212  }
213 
214  return true;
215  }
216 
235  public function describeCredentials() {
236  return [
237  'provider' => new \RawMessage( '$1', [ get_called_class() ] ),
238  'account' => new \RawMessage( '$1', [ $this->getUniqueId() ] ),
239  ];
240  }
241 
249  public static function loadRequestsFromSubmission( array $reqs, array $data ) {
250  $result = [];
251  foreach ( $reqs as $req ) {
252  if ( $req->loadFromSubmission( $data ) ) {
253  $result[] = $req;
254  }
255  }
256  return $result;
257  }
258 
274  public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
275  $requests = array_filter( $reqs, function ( $req ) use ( $class, $allowSubclasses ) {
276  if ( $allowSubclasses ) {
277  return is_a( $req, $class, false );
278  } else {
279  return get_class( $req ) === $class;
280  }
281  } );
282  return count( $requests ) === 1 ? reset( $requests ) : null;
283  }
284 
294  public static function getUsernameFromRequests( array $reqs ) {
295  $username = null;
296  $otherClass = null;
297  foreach ( $reqs as $req ) {
298  $info = $req->getFieldInfo();
299  if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
300  if ( $username === null ) {
301  $username = $req->username;
302  $otherClass = get_class( $req );
303  } elseif ( $username !== $req->username ) {
304  $requestClass = get_class( $req );
305  throw new \UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
306  . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
307  }
308  }
309  }
310  return $username;
311  }
312 
319  public static function mergeFieldInfo( array $reqs ) {
320  $merged = [];
321 
322  // fields that are required by some primary providers but not others are not actually required
323  $sharedRequiredPrimaryFields = null;
324  foreach ( $reqs as $req ) {
325  if ( $req->required !== self::PRIMARY_REQUIRED ) {
326  continue;
327  }
328  $required = [];
329  foreach ( $req->getFieldInfo() as $fieldName => $options ) {
330  if ( empty( $options['optional'] ) ) {
331  $required[] = $fieldName;
332  }
333  }
334  if ( $sharedRequiredPrimaryFields === null ) {
335  $sharedRequiredPrimaryFields = $required;
336  } else {
337  $sharedRequiredPrimaryFields = array_intersect( $sharedRequiredPrimaryFields, $required );
338  }
339  }
340 
341  foreach ( $reqs as $req ) {
342  $info = $req->getFieldInfo();
343  if ( !$info ) {
344  continue;
345  }
346 
347  foreach ( $info as $name => $options ) {
348  if (
349  // If the request isn't required, its fields aren't required either.
350  $req->required === self::OPTIONAL
351  // If there is a primary not requiring this field, no matter how many others do,
352  // authentication can proceed without it.
353  || $req->required === self::PRIMARY_REQUIRED
354  && !in_array( $name, $sharedRequiredPrimaryFields, true )
355  ) {
356  $options['optional'] = true;
357  } else {
358  $options['optional'] = !empty( $options['optional'] );
359  }
360 
361  $options['sensitive'] = !empty( $options['sensitive'] );
362  $type = $options['type'];
363 
364  if ( !array_key_exists( $name, $merged ) ) {
365  $merged[$name] = $options;
366  } elseif ( $merged[$name]['type'] !== $type ) {
367  throw new \UnexpectedValueException( "Field type conflict for \"$name\", " .
368  "\"{$merged[$name]['type']}\" vs \"$type\""
369  );
370  } else {
371  if ( isset( $options['options'] ) ) {
372  if ( isset( $merged[$name]['options'] ) ) {
373  $merged[$name]['options'] += $options['options'];
374  } else {
375  // @codeCoverageIgnoreStart
376  $merged[$name]['options'] = $options['options'];
377  // @codeCoverageIgnoreEnd
378  }
379  }
380 
381  $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
382  $merged[$name]['sensitive'] = $merged[$name]['sensitive'] || $options['sensitive'];
383 
384  // No way to merge 'value', 'image', 'help', or 'label', so just use
385  // the value from the first request.
386  }
387  }
388  }
389 
390  return $merged;
391  }
392 
398  public static function __set_state( $data ) {
399  // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic
400  $ret = new static();
401  foreach ( $data as $k => $v ) {
402  $ret->$k = $v;
403  }
404  return $ret;
405  }
406 }
MediaWiki\Auth\AuthenticationRequest\OPTIONAL
const OPTIONAL
Indicates that the request is not required for authentication to proceed.
Definition: AuthenticationRequest.php:41
MediaWiki\Auth\AuthenticationRequest\$returnToUrl
string null $returnToUrl
Return-to URL, in case of redirect.
Definition: AuthenticationRequest.php:67
MediaWiki\Auth\AuthenticationRequest\getMetadata
getMetadata()
Returns metadata about this request.
Definition: AuthenticationRequest.php:142
MediaWiki\Auth\AuthenticationRequest\mergeFieldInfo
static mergeFieldInfo(array $reqs)
Merge the output of multiple AuthenticationRequest::getFieldInfo() calls.
Definition: AuthenticationRequest.php:319
MediaWiki\Auth\AuthenticationRequest\loadRequestsFromSubmission
static loadRequestsFromSubmission(array $reqs, array $data)
Update a set of requests with form submit data, discarding ones that fail.
Definition: AuthenticationRequest.php:249
MediaWiki\Auth\AuthenticationRequest\describeCredentials
describeCredentials()
Describe the credentials represented by this request.
Definition: AuthenticationRequest.php:235
MediaWiki\Auth\AuthenticationRequest\$required
int $required
For login, continue, and link actions, one of self::OPTIONAL, self::REQUIRED, or self::PRIMARY_REQUIR...
Definition: AuthenticationRequest.php:64
MediaWiki\Auth\AuthenticationRequest\getRequestByClass
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
Definition: AuthenticationRequest.php:274
MediaWiki\Auth\AuthenticationRequest\$action
string null $action
The AuthManager::ACTION_* constant this request was created to be used for.
Definition: AuthenticationRequest.php:59
MediaWiki\Auth\AuthenticationRequest\$username
string null $username
Username.
Definition: AuthenticationRequest.php:72
MediaWiki\Auth\AuthenticationRequest\getUsernameFromRequests
static getUsernameFromRequests(array $reqs)
Get the username from the set of requests.
Definition: AuthenticationRequest.php:294
MediaWiki\Auth\AuthenticationRequest\PRIMARY_REQUIRED
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provider.
Definition: AuthenticationRequest.php:53
MediaWiki\Auth\AuthenticationRequest\getUniqueId
getUniqueId()
Supply a unique key for deduplication.
Definition: AuthenticationRequest.php:90
MediaWiki\Auth\AuthenticationRequest\getFieldInfo
getFieldInfo()
Fetch input field info.
MediaWiki\Auth\AuthenticationRequest\loadFromSubmission
loadFromSubmission(array $data)
Initialize form submitted form data.
Definition: AuthenticationRequest.php:159
Message
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:161
MediaWiki\Auth
Definition: AbstractAuthenticationProvider.php:22
MediaWiki\Auth\AuthenticationRequest\__set_state
static __set_state( $data)
Implementing this mainly for use from the unit tests.
Definition: AuthenticationRequest.php:398
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:38
MediaWiki\Auth\AuthenticationRequest\REQUIRED
const REQUIRED
Indicates that the request is required for authentication to proceed.
Definition: AuthenticationRequest.php:47
$type
$type
Definition: testCompression.php:52