MediaWiki  1.27.1
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 
47  const PRIMARY_REQUIRED = 2;
48 
53  public $action = null;
54 
57  public $required = self::REQUIRED;
58 
60  public $returnToUrl = null;
61 
63  public $username = null;
64 
80  public function getUniqueId() {
81  return get_called_class();
82  }
83 
107  abstract public function getFieldInfo();
108 
119  public function getMetadata() {
120  return [];
121  }
122 
132  public function loadFromSubmission( array $data ) {
133  $fields = array_filter( $this->getFieldInfo(), function ( $info ) {
134  return $info['type'] !== 'null';
135  } );
136  if ( !$fields ) {
137  return false;
138  }
139 
140  foreach ( $fields as $field => $info ) {
141  // Checkboxes and buttons are special. Depending on the method used
142  // to populate $data, they might be unset meaning false or they
143  // might be boolean. Further, image buttons might submit the
144  // coordinates of the click rather than the expected value.
145  if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
146  $this->$field = isset( $data[$field] ) && $data[$field] !== false
147  || isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false;
148  if ( !$this->$field && empty( $info['optional'] ) ) {
149  return false;
150  }
151  continue;
152  }
153 
154  // Multiselect are too, slightly
155  if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
156  $data[$field] = [];
157  }
158 
159  if ( !isset( $data[$field] ) ) {
160  return false;
161  }
162  if ( $data[$field] === '' || $data[$field] === [] ) {
163  if ( empty( $info['optional'] ) ) {
164  return false;
165  }
166  } else {
167  switch ( $info['type'] ) {
168  case 'select':
169  if ( !isset( $info['options'][$data[$field]] ) ) {
170  return false;
171  }
172  break;
173 
174  case 'multiselect':
175  $data[$field] = (array)$data[$field];
176  $allowed = array_keys( $info['options'] );
177  if ( array_diff( $data[$field], $allowed ) !== [] ) {
178  return false;
179  }
180  break;
181  }
182  }
183 
184  $this->$field = $data[$field];
185  }
186 
187  return true;
188  }
189 
206  public function describeCredentials() {
207  return [
208  'provider' => new \RawMessage( '$1', [ get_called_class() ] ),
209  'account' => new \RawMessage( '$1', [ $this->getUniqueId() ] ),
210  ];
211  }
212 
219  public static function loadRequestsFromSubmission( array $reqs, array $data ) {
220  return array_values( array_filter( $reqs, function ( $req ) use ( $data ) {
221  return $req->loadFromSubmission( $data );
222  } ) );
223  }
224 
234  public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
235  $requests = array_filter( $reqs, function ( $req ) use ( $class, $allowSubclasses ) {
236  if ( $allowSubclasses ) {
237  return is_a( $req, $class, false );
238  } else {
239  return get_class( $req ) === $class;
240  }
241  } );
242  return count( $requests ) === 1 ? reset( $requests ) : null;
243  }
244 
254  public static function getUsernameFromRequests( array $reqs ) {
255  $username = null;
256  $otherClass = null;
257  foreach ( $reqs as $req ) {
258  $info = $req->getFieldInfo();
259  if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
260  if ( $username === null ) {
261  $username = $req->username;
262  $otherClass = get_class( $req );
263  } elseif ( $username !== $req->username ) {
264  $requestClass = get_class( $req );
265  throw new \UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
266  . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
267  }
268  }
269  }
270  return $username;
271  }
272 
279  public static function mergeFieldInfo( array $reqs ) {
280  $merged = [];
281 
282  // fields that are required by some primary providers but not others are not actually required
283  $primaryRequests = array_filter( $reqs, function ( $req ) {
284  return $req->required === AuthenticationRequest::PRIMARY_REQUIRED;
285  } );
286  $sharedRequiredPrimaryFields = array_reduce( $primaryRequests, function ( $shared, $req ) {
287  $required = array_keys( array_filter( $req->getFieldInfo(), function ( $options ) {
288  return empty( $options['optional'] );
289  } ) );
290  if ( $shared === null ) {
291  return $required;
292  } else {
293  return array_intersect( $shared, $required );
294  }
295  }, null );
296 
297  foreach ( $reqs as $req ) {
298  $info = $req->getFieldInfo();
299  if ( !$info ) {
300  continue;
301  }
302 
303  foreach ( $info as $name => $options ) {
304  if (
305  // If the request isn't required, its fields aren't required either.
306  $req->required === self::OPTIONAL
307  // If there is a primary not requiring this field, no matter how many others do,
308  // authentication can proceed without it.
309  || $req->required === self::PRIMARY_REQUIRED
310  && !in_array( $name, $sharedRequiredPrimaryFields, true )
311  ) {
312  $options['optional'] = true;
313  } else {
314  $options['optional'] = !empty( $options['optional'] );
315  }
316 
317  if ( !array_key_exists( $name, $merged ) ) {
318  $merged[$name] = $options;
319  } elseif ( $merged[$name]['type'] !== $options['type'] ) {
320  throw new \UnexpectedValueException( "Field type conflict for \"$name\", " .
321  "\"{$merged[$name]['type']}\" vs \"{$options['type']}\""
322  );
323  } else {
324  if ( isset( $options['options'] ) ) {
325  if ( isset( $merged[$name]['options'] ) ) {
326  $merged[$name]['options'] += $options['options'];
327  } else {
328  // @codeCoverageIgnoreStart
329  $merged[$name]['options'] = $options['options'];
330  // @codeCoverageIgnoreEnd
331  }
332  }
333 
334  $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
335 
336  // No way to merge 'value', 'image', 'help', or 'label', so just use
337  // the value from the first request.
338  }
339  }
340  }
341 
342  return $merged;
343  }
344 
350  public static function __set_state( $data ) {
351  $ret = new static();
352  foreach ( $data as $k => $v ) {
353  $ret->$k = $v;
354  }
355  return $ret;
356  }
357 }
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provdier, but other primary authen...
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.