MediaWiki  1.28.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 
51  const PRIMARY_REQUIRED = 2;
52 
57  public $action = null;
58 
61  public $required = self::REQUIRED;
62 
64  public $returnToUrl = null;
65 
68  public $username = null;
69 
85  public function getUniqueId() {
86  return get_called_class();
87  }
88 
123  abstract public function getFieldInfo();
124 
135  public function getMetadata() {
136  return [];
137  }
138 
151  public function loadFromSubmission( array $data ) {
152  $fields = array_filter( $this->getFieldInfo(), function ( $info ) {
153  return $info['type'] !== 'null';
154  } );
155  if ( !$fields ) {
156  return false;
157  }
158 
159  foreach ( $fields as $field => $info ) {
160  // Checkboxes and buttons are special. Depending on the method used
161  // to populate $data, they might be unset meaning false or they
162  // might be boolean. Further, image buttons might submit the
163  // coordinates of the click rather than the expected value.
164  if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
165  $this->$field = isset( $data[$field] ) && $data[$field] !== false
166  || isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false;
167  if ( !$this->$field && empty( $info['optional'] ) ) {
168  return false;
169  }
170  continue;
171  }
172 
173  // Multiselect are too, slightly
174  if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
175  $data[$field] = [];
176  }
177 
178  if ( !isset( $data[$field] ) ) {
179  return false;
180  }
181  if ( $data[$field] === '' || $data[$field] === [] ) {
182  if ( empty( $info['optional'] ) ) {
183  return false;
184  }
185  } else {
186  switch ( $info['type'] ) {
187  case 'select':
188  if ( !isset( $info['options'][$data[$field]] ) ) {
189  return false;
190  }
191  break;
192 
193  case 'multiselect':
194  $data[$field] = (array)$data[$field];
195  $allowed = array_keys( $info['options'] );
196  if ( array_diff( $data[$field], $allowed ) !== [] ) {
197  return false;
198  }
199  break;
200  }
201  }
202 
203  $this->$field = $data[$field];
204  }
205 
206  return true;
207  }
208 
225  public function describeCredentials() {
226  return [
227  'provider' => new \RawMessage( '$1', [ get_called_class() ] ),
228  'account' => new \RawMessage( '$1', [ $this->getUniqueId() ] ),
229  ];
230  }
231 
238  public static function loadRequestsFromSubmission( array $reqs, array $data ) {
239  return array_values( array_filter( $reqs, function ( $req ) use ( $data ) {
240  return $req->loadFromSubmission( $data );
241  } ) );
242  }
243 
253  public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
254  $requests = array_filter( $reqs, function ( $req ) use ( $class, $allowSubclasses ) {
255  if ( $allowSubclasses ) {
256  return is_a( $req, $class, false );
257  } else {
258  return get_class( $req ) === $class;
259  }
260  } );
261  return count( $requests ) === 1 ? reset( $requests ) : null;
262  }
263 
273  public static function getUsernameFromRequests( array $reqs ) {
274  $username = null;
275  $otherClass = null;
276  foreach ( $reqs as $req ) {
277  $info = $req->getFieldInfo();
278  if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
279  if ( $username === null ) {
280  $username = $req->username;
281  $otherClass = get_class( $req );
282  } elseif ( $username !== $req->username ) {
283  $requestClass = get_class( $req );
284  throw new \UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
285  . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
286  }
287  }
288  }
289  return $username;
290  }
291 
298  public static function mergeFieldInfo( array $reqs ) {
299  $merged = [];
300 
301  // fields that are required by some primary providers but not others are not actually required
302  $primaryRequests = array_filter( $reqs, function ( $req ) {
303  return $req->required === AuthenticationRequest::PRIMARY_REQUIRED;
304  } );
305  $sharedRequiredPrimaryFields = array_reduce( $primaryRequests, function ( $shared, $req ) {
306  $required = array_keys( array_filter( $req->getFieldInfo(), function ( $options ) {
307  return empty( $options['optional'] );
308  } ) );
309  if ( $shared === null ) {
310  return $required;
311  } else {
312  return array_intersect( $shared, $required );
313  }
314  }, null );
315 
316  foreach ( $reqs as $req ) {
317  $info = $req->getFieldInfo();
318  if ( !$info ) {
319  continue;
320  }
321 
322  foreach ( $info as $name => $options ) {
323  if (
324  // If the request isn't required, its fields aren't required either.
325  $req->required === self::OPTIONAL
326  // If there is a primary not requiring this field, no matter how many others do,
327  // authentication can proceed without it.
328  || $req->required === self::PRIMARY_REQUIRED
329  && !in_array( $name, $sharedRequiredPrimaryFields, true )
330  ) {
331  $options['optional'] = true;
332  } else {
333  $options['optional'] = !empty( $options['optional'] );
334  }
335 
336  $options['sensitive'] = !empty( $options['sensitive'] );
337 
338  if ( !array_key_exists( $name, $merged ) ) {
339  $merged[$name] = $options;
340  } elseif ( $merged[$name]['type'] !== $options['type'] ) {
341  throw new \UnexpectedValueException( "Field type conflict for \"$name\", " .
342  "\"{$merged[$name]['type']}\" vs \"{$options['type']}\""
343  );
344  } else {
345  if ( isset( $options['options'] ) ) {
346  if ( isset( $merged[$name]['options'] ) ) {
347  $merged[$name]['options'] += $options['options'];
348  } else {
349  // @codeCoverageIgnoreStart
350  $merged[$name]['options'] = $options['options'];
351  // @codeCoverageIgnoreEnd
352  }
353  }
354 
355  $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
356  $merged[$name]['sensitive'] = $merged[$name]['sensitive'] || $options['sensitive'];
357 
358  // No way to merge 'value', 'image', 'help', or 'label', so just use
359  // the value from the first request.
360  }
361  }
362  }
363 
364  return $merged;
365  }
366 
372  public static function __set_state( $data ) {
373  $ret = new static();
374  foreach ( $data as $k => $v ) {
375  $ret->$k = $v;
376  }
377  return $ret;
378  }
379 }
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provider.
the array() calling protocol came about after MediaWiki 1.4rc1.
static mergeFieldInfo(array $reqs)
Merge the output of multiple AuthenticationRequest::getFieldInfo() calls.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1936
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static loadRequestsFromSubmission(array $reqs, array $data)
Update a set of requests with form submit data, discarding ones that fail.
static getUsernameFromRequests(array $reqs)
Get the username from the set of requests.
getFieldInfo()
Fetch input field info.
getUniqueId()
Supply a unique key for deduplication.
int $required
For login, continue, and link actions, one of self::OPTIONAL, self::REQUIRED, or self::PRIMARY_REQUIR...
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1046
const OPTIONAL
Indicates that the request is not required for authentication to proceed.
const REQUIRED
Indicates that the request is required for authentication to proceed.
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead.&$feedLinks hooks can tweak the array to change how login etc forms should look $requests
Definition: hooks.txt:302
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getMetadata()
Returns metadata about this request.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
this hook is for auditing only $req
Definition: hooks.txt:1007
string null $returnToUrl
Return-to URL, in case of redirect.
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
static __set_state($data)
Implementing this mainly for use from the unit tests.
string null $action
The AuthManager::ACTION_* constant this request was created to be used for.
This is a value object for authentication requests.
loadFromSubmission(array $data)
Initialize form submitted form data.
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:300
describeCredentials()
Describe the credentials represented by this request.