MediaWiki master
AuthenticationRequest.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Auth;
8
11use StatusValue;
12use UnexpectedValueException;
13
29abstract class AuthenticationRequest {
30
32 public const OPTIONAL = 0;
33
39 public const REQUIRED = 1;
40
46 public const PRIMARY_REQUIRED = 2;
47
55 public $action = null;
56
68
73 public $returnToUrl = null;
74
83 public $username = null;
84
101 public function getUniqueId() {
102 return get_called_class();
103 }
104
146 abstract public function getFieldInfo();
147
159 public function getMetadata() {
160 return [];
161 }
162
176 public function loadFromSubmission( array $data ) {
177 $fields = array_filter( $this->getFieldInfo(), static function ( $info ) {
178 return $info['type'] !== 'null';
179 } );
180 if ( !$fields ) {
181 return false;
182 }
183
184 foreach ( $fields as $field => $info ) {
185 // Checkboxes and buttons are special. Depending on the method used
186 // to populate $data, they might be unset meaning false or they
187 // might be boolean. Further, image buttons might submit the
188 // coordinates of the click rather than the expected value.
189 if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
190 $this->$field = ( isset( $data[$field] ) && $data[$field] !== false )
191 || ( isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false );
192 if ( !$this->$field && empty( $info['optional'] ) ) {
193 return false;
194 }
195 continue;
196 }
197
198 // Multiselect are too, slightly
199 if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
200 $data[$field] = [];
201 }
202
203 if ( !isset( $data[$field] ) ) {
204 return false;
205 }
206 if ( $data[$field] === '' || $data[$field] === [] ) {
207 if ( empty( $info['optional'] ) ) {
208 return false;
209 }
210 } else {
211 switch ( $info['type'] ) {
212 case 'select':
213 if ( !isset( $info['options'][$data[$field]] ) ) {
214 return false;
215 }
216 break;
217
218 case 'multiselect':
219 $data[$field] = (array)$data[$field];
220 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset required for multiselect
221 $allowed = array_keys( $info['options'] );
222 if ( array_diff( $data[$field], $allowed ) !== [] ) {
223 return false;
224 }
225 break;
226 }
227 }
228
229 $this->$field = $data[$field];
230 }
231
232 return true;
233 }
234
255 public function validate() {
256 return StatusValue::newGood();
257 }
258
277 public function describeCredentials() {
278 return [
279 'provider' => new RawMessage( '$1', [ get_called_class() ] ),
280 'account' => new RawMessage( '$1', [ $this->getUniqueId() ] ),
281 ];
282 }
283
291 public static function loadRequestsFromSubmission( array $reqs, array $data ) {
292 $result = [];
293 foreach ( $reqs as $req ) {
294 if ( $req->loadFromSubmission( $data ) ) {
295 $result[] = $req;
296 }
297 }
298 return $result;
299 }
300
312 public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
313 $requests = array_filter( $reqs, static function ( $req ) use ( $class, $allowSubclasses ) {
314 if ( $allowSubclasses ) {
315 return is_a( $req, $class, false );
316 } else {
317 return get_class( $req ) === $class;
318 }
319 } );
320 // @phan-suppress-next-line PhanTypeMismatchReturn False positive
321 return count( $requests ) === 1 ? reset( $requests ) : null;
322 }
323
333 public static function getUsernameFromRequests( array $reqs ) {
334 $username = null;
335 $otherClass = null;
336 foreach ( $reqs as $req ) {
337 $info = $req->getFieldInfo();
338 if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
339 if ( $username === null ) {
340 $username = $req->username;
341 $otherClass = get_class( $req );
342 } elseif ( $username !== $req->username ) {
343 $requestClass = get_class( $req );
344 throw new UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
345 // @phan-suppress-next-line PhanTypeSuspiciousStringExpression $otherClass always set
346 . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
347 }
348 }
349 }
350 return $username;
351 }
352
360 public static function mergeFieldInfo( array $reqs ) {
361 $merged = [];
362
363 // fields that are required by some primary providers but not others are not actually required
364 $sharedRequiredPrimaryFields = null;
365 foreach ( $reqs as $req ) {
366 if ( $req->required !== self::PRIMARY_REQUIRED ) {
367 continue;
368 }
369 $required = [];
370 foreach ( $req->getFieldInfo() as $fieldName => $options ) {
371 if ( empty( $options['optional'] ) ) {
372 $required[] = $fieldName;
373 }
374 }
375 if ( $sharedRequiredPrimaryFields === null ) {
376 $sharedRequiredPrimaryFields = $required;
377 } else {
378 $sharedRequiredPrimaryFields = array_intersect( $sharedRequiredPrimaryFields, $required );
379 }
380 }
381
382 foreach ( $reqs as $req ) {
383 $info = $req->getFieldInfo();
384 if ( !$info ) {
385 continue;
386 }
387
388 foreach ( $info as $name => $options ) {
389 if (
390 // If the request isn't required, its fields aren't required either.
391 $req->required === self::OPTIONAL
392 // If there is a primary not requiring this field, no matter how many others do,
393 // authentication can proceed without it.
394 || ( $req->required === self::PRIMARY_REQUIRED
395 // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal False positive
396 && !in_array( $name, $sharedRequiredPrimaryFields, true ) )
397 ) {
398 $options['optional'] = true;
399 } else {
400 $options['optional'] = !empty( $options['optional'] );
401 }
402
403 $options['sensitive'] = !empty( $options['sensitive'] );
404 $type = $options['type'];
405
406 if ( !array_key_exists( $name, $merged ) ) {
407 $merged[$name] = $options;
408 } elseif ( $merged[$name]['type'] !== $type ) {
409 throw new UnexpectedValueException( "Field type conflict for \"$name\", " .
410 "\"{$merged[$name]['type']}\" vs \"$type\""
411 );
412 } else {
413 if ( isset( $options['options'] ) ) {
414 if ( isset( $merged[$name]['options'] ) ) {
415 $merged[$name]['options'] += $options['options'];
416 } else {
417 // @codeCoverageIgnoreStart
418 $merged[$name]['options'] = $options['options'];
419 // @codeCoverageIgnoreEnd
420 }
421 }
422
423 $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
424 $merged[$name]['sensitive'] = $merged[$name]['sensitive'] || $options['sensitive'];
425
426 // No way to merge 'value', 'image', 'help', or 'label', so just use
427 // the value from the first request.
428 }
429 }
430 }
431
432 return $merged;
433 }
434
440 public static function __set_state( $data ) {
441 // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic
442 $ret = new static();
443 foreach ( $data as $k => $v ) {
444 $ret->$k = $v;
445 }
446 return $ret;
447 }
448}
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:144
Generic operation result class Has warning/error list, boolean status and arbitrary value.