MediaWiki master
AuthenticationRequest.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Auth;
25
28use UnexpectedValueException;
29
45abstract class AuthenticationRequest {
46
48 public const OPTIONAL = 0;
49
55 public const REQUIRED = 1;
56
62 public const PRIMARY_REQUIRED = 2;
63
71 public $action = null;
72
84
89 public $returnToUrl = null;
90
99 public $username = null;
100
117 public function getUniqueId() {
118 return get_called_class();
119 }
120
162 abstract public function getFieldInfo();
163
175 public function getMetadata() {
176 return [];
177 }
178
192 public function loadFromSubmission( array $data ) {
193 $fields = array_filter( $this->getFieldInfo(), static function ( $info ) {
194 return $info['type'] !== 'null';
195 } );
196 if ( !$fields ) {
197 return false;
198 }
199
200 foreach ( $fields as $field => $info ) {
201 // Checkboxes and buttons are special. Depending on the method used
202 // to populate $data, they might be unset meaning false or they
203 // might be boolean. Further, image buttons might submit the
204 // coordinates of the click rather than the expected value.
205 if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
206 $this->$field = ( isset( $data[$field] ) && $data[$field] !== false )
207 || ( isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false );
208 if ( !$this->$field && empty( $info['optional'] ) ) {
209 return false;
210 }
211 continue;
212 }
213
214 // Multiselect are too, slightly
215 if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
216 $data[$field] = [];
217 }
218
219 if ( !isset( $data[$field] ) ) {
220 return false;
221 }
222 if ( $data[$field] === '' || $data[$field] === [] ) {
223 if ( empty( $info['optional'] ) ) {
224 return false;
225 }
226 } else {
227 switch ( $info['type'] ) {
228 case 'select':
229 if ( !isset( $info['options'][$data[$field]] ) ) {
230 return false;
231 }
232 break;
233
234 case 'multiselect':
235 $data[$field] = (array)$data[$field];
236 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset required for multiselect
237 $allowed = array_keys( $info['options'] );
238 if ( array_diff( $data[$field], $allowed ) !== [] ) {
239 return false;
240 }
241 break;
242 }
243 }
244
245 $this->$field = $data[$field];
246 }
247
248 return true;
249 }
250
269 public function describeCredentials() {
270 return [
271 'provider' => new RawMessage( '$1', [ get_called_class() ] ),
272 'account' => new RawMessage( '$1', [ $this->getUniqueId() ] ),
273 ];
274 }
275
283 public static function loadRequestsFromSubmission( array $reqs, array $data ) {
284 $result = [];
285 foreach ( $reqs as $req ) {
286 if ( $req->loadFromSubmission( $data ) ) {
287 $result[] = $req;
288 }
289 }
290 return $result;
291 }
292
306 public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
307 $requests = array_filter( $reqs, static function ( $req ) use ( $class, $allowSubclasses ) {
308 if ( $allowSubclasses ) {
309 return is_a( $req, $class, false );
310 } else {
311 return get_class( $req ) === $class;
312 }
313 } );
314 // @phan-suppress-next-line PhanTypeMismatchReturn False positive
315 return count( $requests ) === 1 ? reset( $requests ) : null;
316 }
317
327 public static function getUsernameFromRequests( array $reqs ) {
328 $username = null;
329 $otherClass = null;
330 foreach ( $reqs as $req ) {
331 $info = $req->getFieldInfo();
332 if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
333 if ( $username === null ) {
334 $username = $req->username;
335 $otherClass = get_class( $req );
336 } elseif ( $username !== $req->username ) {
337 $requestClass = get_class( $req );
338 throw new UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
339 // @phan-suppress-next-line PhanTypeSuspiciousStringExpression $otherClass always set
340 . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
341 }
342 }
343 }
344 return $username;
345 }
346
354 public static function mergeFieldInfo( array $reqs ) {
355 $merged = [];
356
357 // fields that are required by some primary providers but not others are not actually required
358 $sharedRequiredPrimaryFields = null;
359 foreach ( $reqs as $req ) {
360 if ( $req->required !== self::PRIMARY_REQUIRED ) {
361 continue;
362 }
363 $required = [];
364 foreach ( $req->getFieldInfo() as $fieldName => $options ) {
365 if ( empty( $options['optional'] ) ) {
366 $required[] = $fieldName;
367 }
368 }
369 if ( $sharedRequiredPrimaryFields === null ) {
370 $sharedRequiredPrimaryFields = $required;
371 } else {
372 $sharedRequiredPrimaryFields = array_intersect( $sharedRequiredPrimaryFields, $required );
373 }
374 }
375
376 foreach ( $reqs as $req ) {
377 $info = $req->getFieldInfo();
378 if ( !$info ) {
379 continue;
380 }
381
382 foreach ( $info as $name => $options ) {
383 if (
384 // If the request isn't required, its fields aren't required either.
385 $req->required === self::OPTIONAL
386 // If there is a primary not requiring this field, no matter how many others do,
387 // authentication can proceed without it.
388 || ( $req->required === self::PRIMARY_REQUIRED
389 // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal False positive
390 && !in_array( $name, $sharedRequiredPrimaryFields, true ) )
391 ) {
392 $options['optional'] = true;
393 } else {
394 $options['optional'] = !empty( $options['optional'] );
395 }
396
397 $options['sensitive'] = !empty( $options['sensitive'] );
398 $type = $options['type'];
399
400 if ( !array_key_exists( $name, $merged ) ) {
401 $merged[$name] = $options;
402 } elseif ( $merged[$name]['type'] !== $type ) {
403 throw new UnexpectedValueException( "Field type conflict for \"$name\", " .
404 "\"{$merged[$name]['type']}\" vs \"$type\""
405 );
406 } else {
407 if ( isset( $options['options'] ) ) {
408 if ( isset( $merged[$name]['options'] ) ) {
409 $merged[$name]['options'] += $options['options'];
410 } else {
411 // @codeCoverageIgnoreStart
412 $merged[$name]['options'] = $options['options'];
413 // @codeCoverageIgnoreEnd
414 }
415 }
416
417 $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
418 $merged[$name]['sensitive'] = $merged[$name]['sensitive'] || $options['sensitive'];
419
420 // No way to merge 'value', 'image', 'help', or 'label', so just use
421 // the value from the first request.
422 }
423 }
424 }
425
426 return $merged;
427 }
428
434 public static function __set_state( $data ) {
435 // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic
436 $ret = new static();
437 foreach ( $data as $k => $v ) {
438 $ret->$k = $v;
439 }
440 return $ret;
441 }
442}
This is a value object for authentication requests.
getFieldInfo()
Fetch input field info.
string null $returnToUrl
Return-to URL, in case of a REDIRECT AuthenticationResponse.
const OPTIONAL
Indicates that the request is not required for authentication to proceed.
string null $action
The AuthManager::ACTION_* constant this request was created to be used for.
static __set_state( $data)
Implementing this mainly for use from the unit tests.
getUniqueId()
Supply a unique key for deduplication.
int $required
Whether the authentication request is required (for login, continue, and link actions).
static mergeFieldInfo(array $reqs)
Merge the output of multiple AuthenticationRequest::getFieldInfo() calls.
static loadRequestsFromSubmission(array $reqs, array $data)
Update a set of requests with form submit data, discarding ones that fail.
describeCredentials()
Describe the credentials represented by this request.
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provider.
getMetadata()
Returns metadata about this request.
const REQUIRED
Indicates that the request is required for authentication to proceed.
static getUsernameFromRequests(array $reqs)
Get the username from the set of requests.
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
loadFromSubmission(array $data)
Initialize form submitted form data.
Variant of the Message class.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:155