MediaWiki master
AuthenticationRequest.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Auth;
8
11use UnexpectedValueException;
12
28abstract class AuthenticationRequest {
29
31 public const OPTIONAL = 0;
32
38 public const REQUIRED = 1;
39
45 public const PRIMARY_REQUIRED = 2;
46
54 public $action = null;
55
67
72 public $returnToUrl = null;
73
82 public $username = null;
83
100 public function getUniqueId() {
101 return get_called_class();
102 }
103
145 abstract public function getFieldInfo();
146
158 public function getMetadata() {
159 return [];
160 }
161
175 public function loadFromSubmission( array $data ) {
176 $fields = array_filter( $this->getFieldInfo(), static function ( $info ) {
177 return $info['type'] !== 'null';
178 } );
179 if ( !$fields ) {
180 return false;
181 }
182
183 foreach ( $fields as $field => $info ) {
184 // Checkboxes and buttons are special. Depending on the method used
185 // to populate $data, they might be unset meaning false or they
186 // might be boolean. Further, image buttons might submit the
187 // coordinates of the click rather than the expected value.
188 if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
189 $this->$field = ( isset( $data[$field] ) && $data[$field] !== false )
190 || ( isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false );
191 if ( !$this->$field && empty( $info['optional'] ) ) {
192 return false;
193 }
194 continue;
195 }
196
197 // Multiselect are too, slightly
198 if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
199 $data[$field] = [];
200 }
201
202 if ( !isset( $data[$field] ) ) {
203 return false;
204 }
205 if ( $data[$field] === '' || $data[$field] === [] ) {
206 if ( empty( $info['optional'] ) ) {
207 return false;
208 }
209 } else {
210 switch ( $info['type'] ) {
211 case 'select':
212 if ( !isset( $info['options'][$data[$field]] ) ) {
213 return false;
214 }
215 break;
216
217 case 'multiselect':
218 $data[$field] = (array)$data[$field];
219 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset required for multiselect
220 $allowed = array_keys( $info['options'] );
221 if ( array_diff( $data[$field], $allowed ) !== [] ) {
222 return false;
223 }
224 break;
225 }
226 }
227
228 $this->$field = $data[$field];
229 }
230
231 return true;
232 }
233
252 public function describeCredentials() {
253 return [
254 'provider' => new RawMessage( '$1', [ get_called_class() ] ),
255 'account' => new RawMessage( '$1', [ $this->getUniqueId() ] ),
256 ];
257 }
258
266 public static function loadRequestsFromSubmission( array $reqs, array $data ) {
267 $result = [];
268 foreach ( $reqs as $req ) {
269 if ( $req->loadFromSubmission( $data ) ) {
270 $result[] = $req;
271 }
272 }
273 return $result;
274 }
275
287 public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
288 $requests = array_filter( $reqs, static function ( $req ) use ( $class, $allowSubclasses ) {
289 if ( $allowSubclasses ) {
290 return is_a( $req, $class, false );
291 } else {
292 return get_class( $req ) === $class;
293 }
294 } );
295 // @phan-suppress-next-line PhanTypeMismatchReturn False positive
296 return count( $requests ) === 1 ? reset( $requests ) : null;
297 }
298
308 public static function getUsernameFromRequests( array $reqs ) {
309 $username = null;
310 $otherClass = null;
311 foreach ( $reqs as $req ) {
312 $info = $req->getFieldInfo();
313 if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
314 if ( $username === null ) {
315 $username = $req->username;
316 $otherClass = get_class( $req );
317 } elseif ( $username !== $req->username ) {
318 $requestClass = get_class( $req );
319 throw new UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
320 // @phan-suppress-next-line PhanTypeSuspiciousStringExpression $otherClass always set
321 . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
322 }
323 }
324 }
325 return $username;
326 }
327
335 public static function mergeFieldInfo( array $reqs ) {
336 $merged = [];
337
338 // fields that are required by some primary providers but not others are not actually required
339 $sharedRequiredPrimaryFields = null;
340 foreach ( $reqs as $req ) {
341 if ( $req->required !== self::PRIMARY_REQUIRED ) {
342 continue;
343 }
344 $required = [];
345 foreach ( $req->getFieldInfo() as $fieldName => $options ) {
346 if ( empty( $options['optional'] ) ) {
347 $required[] = $fieldName;
348 }
349 }
350 if ( $sharedRequiredPrimaryFields === null ) {
351 $sharedRequiredPrimaryFields = $required;
352 } else {
353 $sharedRequiredPrimaryFields = array_intersect( $sharedRequiredPrimaryFields, $required );
354 }
355 }
356
357 foreach ( $reqs as $req ) {
358 $info = $req->getFieldInfo();
359 if ( !$info ) {
360 continue;
361 }
362
363 foreach ( $info as $name => $options ) {
364 if (
365 // If the request isn't required, its fields aren't required either.
366 $req->required === self::OPTIONAL
367 // If there is a primary not requiring this field, no matter how many others do,
368 // authentication can proceed without it.
369 || ( $req->required === self::PRIMARY_REQUIRED
370 // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal False positive
371 && !in_array( $name, $sharedRequiredPrimaryFields, true ) )
372 ) {
373 $options['optional'] = true;
374 } else {
375 $options['optional'] = !empty( $options['optional'] );
376 }
377
378 $options['sensitive'] = !empty( $options['sensitive'] );
379 $type = $options['type'];
380
381 if ( !array_key_exists( $name, $merged ) ) {
382 $merged[$name] = $options;
383 } elseif ( $merged[$name]['type'] !== $type ) {
384 throw new UnexpectedValueException( "Field type conflict for \"$name\", " .
385 "\"{$merged[$name]['type']}\" vs \"$type\""
386 );
387 } else {
388 if ( isset( $options['options'] ) ) {
389 if ( isset( $merged[$name]['options'] ) ) {
390 $merged[$name]['options'] += $options['options'];
391 } else {
392 // @codeCoverageIgnoreStart
393 $merged[$name]['options'] = $options['options'];
394 // @codeCoverageIgnoreEnd
395 }
396 }
397
398 $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
399 $merged[$name]['sensitive'] = $merged[$name]['sensitive'] || $options['sensitive'];
400
401 // No way to merge 'value', 'image', 'help', or 'label', so just use
402 // the value from the first request.
403 }
404 }
405 }
406
407 return $merged;
408 }
409
415 public static function __set_state( $data ) {
416 // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic
417 $ret = new static();
418 foreach ( $data as $k => $v ) {
419 $ret->$k = $v;
420 }
421 return $ret;
422 }
423}
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