MediaWiki  1.27.2
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 
43  const REQUIRED = 1;
44 
48  const PRIMARY_REQUIRED = 2;
49 
54  public $action = null;
55 
58  public $required = self::REQUIRED;
59 
61  public $returnToUrl = null;
62 
64  public $username = null;
65 
81  public function getUniqueId() {
82  return get_called_class();
83  }
84 
112  abstract public function getFieldInfo();
113 
124  public function getMetadata() {
125  return [];
126  }
127 
137  public function loadFromSubmission( array $data ) {
138  $fields = array_filter( $this->getFieldInfo(), function ( $info ) {
139  return $info['type'] !== 'null';
140  } );
141  if ( !$fields ) {
142  return false;
143  }
144 
145  foreach ( $fields as $field => $info ) {
146  // Checkboxes and buttons are special. Depending on the method used
147  // to populate $data, they might be unset meaning false or they
148  // might be boolean. Further, image buttons might submit the
149  // coordinates of the click rather than the expected value.
150  if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
151  $this->$field = isset( $data[$field] ) && $data[$field] !== false
152  || isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false;
153  if ( !$this->$field && empty( $info['optional'] ) ) {
154  return false;
155  }
156  continue;
157  }
158 
159  // Multiselect are too, slightly
160  if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
161  $data[$field] = [];
162  }
163 
164  if ( !isset( $data[$field] ) ) {
165  return false;
166  }
167  if ( $data[$field] === '' || $data[$field] === [] ) {
168  if ( empty( $info['optional'] ) ) {
169  return false;
170  }
171  } else {
172  switch ( $info['type'] ) {
173  case 'select':
174  if ( !isset( $info['options'][$data[$field]] ) ) {
175  return false;
176  }
177  break;
178 
179  case 'multiselect':
180  $data[$field] = (array)$data[$field];
181  $allowed = array_keys( $info['options'] );
182  if ( array_diff( $data[$field], $allowed ) !== [] ) {
183  return false;
184  }
185  break;
186  }
187  }
188 
189  $this->$field = $data[$field];
190  }
191 
192  return true;
193  }
194 
211  public function describeCredentials() {
212  return [
213  'provider' => new \RawMessage( '$1', [ get_called_class() ] ),
214  'account' => new \RawMessage( '$1', [ $this->getUniqueId() ] ),
215  ];
216  }
217 
224  public static function loadRequestsFromSubmission( array $reqs, array $data ) {
225  return array_values( array_filter( $reqs, function ( $req ) use ( $data ) {
226  return $req->loadFromSubmission( $data );
227  } ) );
228  }
229 
239  public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
240  $requests = array_filter( $reqs, function ( $req ) use ( $class, $allowSubclasses ) {
241  if ( $allowSubclasses ) {
242  return is_a( $req, $class, false );
243  } else {
244  return get_class( $req ) === $class;
245  }
246  } );
247  return count( $requests ) === 1 ? reset( $requests ) : null;
248  }
249 
259  public static function getUsernameFromRequests( array $reqs ) {
260  $username = null;
261  $otherClass = null;
262  foreach ( $reqs as $req ) {
263  $info = $req->getFieldInfo();
264  if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
265  if ( $username === null ) {
266  $username = $req->username;
267  $otherClass = get_class( $req );
268  } elseif ( $username !== $req->username ) {
269  $requestClass = get_class( $req );
270  throw new \UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
271  . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
272  }
273  }
274  }
275  return $username;
276  }
277 
284  public static function mergeFieldInfo( array $reqs ) {
285  $merged = [];
286 
287  // fields that are required by some primary providers but not others are not actually required
288  $primaryRequests = array_filter( $reqs, function ( $req ) {
289  return $req->required === AuthenticationRequest::PRIMARY_REQUIRED;
290  } );
291  $sharedRequiredPrimaryFields = array_reduce( $primaryRequests, function ( $shared, $req ) {
292  $required = array_keys( array_filter( $req->getFieldInfo(), function ( $options ) {
293  return empty( $options['optional'] );
294  } ) );
295  if ( $shared === null ) {
296  return $required;
297  } else {
298  return array_intersect( $shared, $required );
299  }
300  }, null );
301 
302  foreach ( $reqs as $req ) {
303  $info = $req->getFieldInfo();
304  if ( !$info ) {
305  continue;
306  }
307 
308  foreach ( $info as $name => $options ) {
309  if (
310  // If the request isn't required, its fields aren't required either.
311  $req->required === self::OPTIONAL
312  // If there is a primary not requiring this field, no matter how many others do,
313  // authentication can proceed without it.
314  || $req->required === self::PRIMARY_REQUIRED
315  && !in_array( $name, $sharedRequiredPrimaryFields, true )
316  ) {
317  $options['optional'] = true;
318  } else {
319  $options['optional'] = !empty( $options['optional'] );
320  }
321 
322  if ( !array_key_exists( $name, $merged ) ) {
323  $merged[$name] = $options;
324  } elseif ( $merged[$name]['type'] !== $options['type'] ) {
325  throw new \UnexpectedValueException( "Field type conflict for \"$name\", " .
326  "\"{$merged[$name]['type']}\" vs \"{$options['type']}\""
327  );
328  } else {
329  if ( isset( $options['options'] ) ) {
330  if ( isset( $merged[$name]['options'] ) ) {
331  $merged[$name]['options'] += $options['options'];
332  } else {
333  // @codeCoverageIgnoreStart
334  $merged[$name]['options'] = $options['options'];
335  // @codeCoverageIgnoreEnd
336  }
337  }
338 
339  $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
340 
341  // No way to merge 'value', 'image', 'help', or 'label', so just use
342  // the value from the first request.
343  }
344  }
345  }
346 
347  return $merged;
348  }
349 
355  public static function __set_state( $data ) {
356  $ret = new static();
357  foreach ( $data as $k => $v ) {
358  $ret->$k = $v;
359  }
360  return $ret;
361  }
362 }
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provdier.
the array() calling protocol came about after MediaWiki 1.4rc1.
static mergeFieldInfo(array $reqs)
Merge the output of multiple AuthenticationRequest::getFieldInfo() calls.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
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:1798
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:1004
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:312
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:965
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:310
describeCredentials()
Describe the credentials represented by this request.