MediaWiki  1.34.0
AuthenticationRequest.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Auth;
25 
26 use Message;
27 
37 abstract class AuthenticationRequest {
38 
40  const OPTIONAL = 0;
41 
46  const REQUIRED = 1;
47 
52  const PRIMARY_REQUIRED = 2;
53 
58  public $action = null;
59 
64 
66  public $returnToUrl = null;
67 
71  public $username = null;
72 
88  public function getUniqueId() {
89  return get_called_class();
90  }
91 
127  abstract public function getFieldInfo();
128 
139  public function getMetadata() {
140  return [];
141  }
142 
155  public function loadFromSubmission( array $data ) {
156  $fields = array_filter( $this->getFieldInfo(), function ( $info ) {
157  return $info['type'] !== 'null';
158  } );
159  if ( !$fields ) {
160  return false;
161  }
162 
163  foreach ( $fields as $field => $info ) {
164  // Checkboxes and buttons are special. Depending on the method used
165  // to populate $data, they might be unset meaning false or they
166  // might be boolean. Further, image buttons might submit the
167  // coordinates of the click rather than the expected value.
168  if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
169  $this->$field = isset( $data[$field] ) && $data[$field] !== false
170  || isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false;
171  if ( !$this->$field && empty( $info['optional'] ) ) {
172  return false;
173  }
174  continue;
175  }
176 
177  // Multiselect are too, slightly
178  if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
179  $data[$field] = [];
180  }
181 
182  if ( !isset( $data[$field] ) ) {
183  return false;
184  }
185  if ( $data[$field] === '' || $data[$field] === [] ) {
186  if ( empty( $info['optional'] ) ) {
187  return false;
188  }
189  } else {
190  switch ( $info['type'] ) {
191  case 'select':
192  if ( !isset( $info['options'][$data[$field]] ) ) {
193  return false;
194  }
195  break;
196 
197  case 'multiselect':
198  $data[$field] = (array)$data[$field];
199  $allowed = array_keys( $info['options'] );
200  if ( array_diff( $data[$field], $allowed ) !== [] ) {
201  return false;
202  }
203  break;
204  }
205  }
206 
207  $this->$field = $data[$field];
208  }
209 
210  return true;
211  }
212 
229  public function describeCredentials() {
230  return [
231  'provider' => new \RawMessage( '$1', [ get_called_class() ] ),
232  'account' => new \RawMessage( '$1', [ $this->getUniqueId() ] ),
233  ];
234  }
235 
242  public static function loadRequestsFromSubmission( array $reqs, array $data ) {
243  return array_values( array_filter( $reqs, function ( $req ) use ( $data ) {
244  return $req->loadFromSubmission( $data );
245  } ) );
246  }
247 
263  public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
264  $requests = array_filter( $reqs, function ( $req ) use ( $class, $allowSubclasses ) {
265  if ( $allowSubclasses ) {
266  return is_a( $req, $class, false );
267  } else {
268  return get_class( $req ) === $class;
269  }
270  } );
271  return count( $requests ) === 1 ? reset( $requests ) : null;
272  }
273 
283  public static function getUsernameFromRequests( array $reqs ) {
284  $username = null;
285  $otherClass = null;
286  foreach ( $reqs as $req ) {
287  $info = $req->getFieldInfo();
288  if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
289  if ( $username === null ) {
290  $username = $req->username;
291  $otherClass = get_class( $req );
292  } elseif ( $username !== $req->username ) {
293  $requestClass = get_class( $req );
294  throw new \UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
295  . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
296  }
297  }
298  }
299  return $username;
300  }
301 
309  public static function mergeFieldInfo( array $reqs ) {
310  $merged = [];
311 
312  // fields that are required by some primary providers but not others are not actually required
313  $primaryRequests = array_filter( $reqs, function ( $req ) {
314  return $req->required === AuthenticationRequest::PRIMARY_REQUIRED;
315  } );
316  $sharedRequiredPrimaryFields = array_reduce( $primaryRequests, function ( $shared, $req ) {
317  $required = array_keys( array_filter( $req->getFieldInfo(), function ( $options ) {
318  return empty( $options['optional'] );
319  } ) );
320  if ( $shared === null ) {
321  return $required;
322  } else {
323  return array_intersect( $shared, $required );
324  }
325  }, null );
326 
327  foreach ( $reqs as $req ) {
328  $info = $req->getFieldInfo();
329  if ( !$info ) {
330  continue;
331  }
332 
333  foreach ( $info as $name => $options ) {
334  if (
335  // If the request isn't required, its fields aren't required either.
336  $req->required === self::OPTIONAL
337  // If there is a primary not requiring this field, no matter how many others do,
338  // authentication can proceed without it.
339  || $req->required === self::PRIMARY_REQUIRED
340  && !in_array( $name, $sharedRequiredPrimaryFields, true )
341  ) {
342  $options['optional'] = true;
343  } else {
344  $options['optional'] = !empty( $options['optional'] );
345  }
346 
347  $options['sensitive'] = !empty( $options['sensitive'] );
348  $type = $options['type'];
349 
350  if ( !array_key_exists( $name, $merged ) ) {
351  $merged[$name] = $options;
352  } elseif ( $merged[$name]['type'] !== $type ) {
353  throw new \UnexpectedValueException( "Field type conflict for \"$name\", " .
354  "\"{$merged[$name]['type']}\" vs \"$type\""
355  );
356  } else {
357  if ( isset( $options['options'] ) ) {
358  if ( isset( $merged[$name]['options'] ) ) {
359  $merged[$name]['options'] += $options['options'];
360  } else {
361  // @codeCoverageIgnoreStart
362  $merged[$name]['options'] = $options['options'];
363  // @codeCoverageIgnoreEnd
364  }
365  }
366 
367  $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
368  $merged[$name]['sensitive'] = $merged[$name]['sensitive'] || $options['sensitive'];
369 
370  // No way to merge 'value', 'image', 'help', or 'label', so just use
371  // the value from the first request.
372  }
373  }
374  }
375 
376  return $merged;
377  }
378 
384  public static function __set_state( $data ) {
385  // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic
386  $ret = new static();
387  foreach ( $data as $k => $v ) {
388  $ret->$k = $v;
389  }
390  return $ret;
391  }
392 }
MediaWiki\Auth\AuthenticationRequest\OPTIONAL
const OPTIONAL
Indicates that the request is not required for authentication to proceed.
Definition: AuthenticationRequest.php:40
MediaWiki\Auth\AuthenticationRequest\$returnToUrl
string null $returnToUrl
Return-to URL, in case of redirect.
Definition: AuthenticationRequest.php:66
MediaWiki\Auth\AuthenticationRequest\getMetadata
getMetadata()
Returns metadata about this request.
Definition: AuthenticationRequest.php:139
MediaWiki\Auth\AuthenticationRequest\mergeFieldInfo
static mergeFieldInfo(array $reqs)
Merge the output of multiple AuthenticationRequest::getFieldInfo() calls.
Definition: AuthenticationRequest.php:309
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:242
MediaWiki\Auth\AuthenticationRequest\describeCredentials
describeCredentials()
Describe the credentials represented by this request.
Definition: AuthenticationRequest.php:229
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:63
Message
MediaWiki\Auth\AuthenticationRequest\getRequestByClass
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
Definition: AuthenticationRequest.php:263
MediaWiki\Auth\AuthenticationRequest\$action
string null $action
The AuthManager::ACTION_* constant this request was created to be used for.
Definition: AuthenticationRequest.php:58
MediaWiki\Auth\AuthenticationRequest\$username
string null $username
Username.
Definition: AuthenticationRequest.php:71
MediaWiki\Auth\AuthenticationRequest\getUsernameFromRequests
static getUsernameFromRequests(array $reqs)
Get the username from the set of requests.
Definition: AuthenticationRequest.php:283
MediaWiki\Auth\AuthenticationRequest\PRIMARY_REQUIRED
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provider.
Definition: AuthenticationRequest.php:52
MediaWiki\Auth\AuthenticationRequest\getUniqueId
getUniqueId()
Supply a unique key for deduplication.
Definition: AuthenticationRequest.php:88
MediaWiki\Auth\AuthenticationRequest\getFieldInfo
getFieldInfo()
Fetch input field info.
MediaWiki\Auth\AuthenticationRequest\loadFromSubmission
loadFromSubmission(array $data)
Initialize form submitted form data.
Definition: AuthenticationRequest.php:155
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:384
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:37
MediaWiki\Auth\AuthenticationRequest\REQUIRED
const REQUIRED
Indicates that the request is required for authentication to proceed.
Definition: AuthenticationRequest.php:46
$type
$type
Definition: testCompression.php:48