MediaWiki master
AuthenticationRequest.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Auth;
25
28use UnexpectedValueException;
29
44abstract class AuthenticationRequest {
45
47 public const OPTIONAL = 0;
48
53 public const REQUIRED = 1;
54
59 public const PRIMARY_REQUIRED = 2;
60
65 public $action = null;
66
71
73 public $returnToUrl = null;
74
78 public $username = null;
79
96 public function getUniqueId() {
97 return get_called_class();
98 }
99
138 abstract public function getFieldInfo();
139
151 public function getMetadata() {
152 return [];
153 }
154
168 public function loadFromSubmission( array $data ) {
169 $fields = array_filter( $this->getFieldInfo(), static function ( $info ) {
170 return $info['type'] !== 'null';
171 } );
172 if ( !$fields ) {
173 return false;
174 }
175
176 foreach ( $fields as $field => $info ) {
177 // Checkboxes and buttons are special. Depending on the method used
178 // to populate $data, they might be unset meaning false or they
179 // might be boolean. Further, image buttons might submit the
180 // coordinates of the click rather than the expected value.
181 if ( $info['type'] === 'checkbox' || $info['type'] === 'button' ) {
182 $this->$field = isset( $data[$field] ) && $data[$field] !== false
183 || isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false;
184 if ( !$this->$field && empty( $info['optional'] ) ) {
185 return false;
186 }
187 continue;
188 }
189
190 // Multiselect are too, slightly
191 if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
192 $data[$field] = [];
193 }
194
195 if ( !isset( $data[$field] ) ) {
196 return false;
197 }
198 if ( $data[$field] === '' || $data[$field] === [] ) {
199 if ( empty( $info['optional'] ) ) {
200 return false;
201 }
202 } else {
203 switch ( $info['type'] ) {
204 case 'select':
205 if ( !isset( $info['options'][$data[$field]] ) ) {
206 return false;
207 }
208 break;
209
210 case 'multiselect':
211 $data[$field] = (array)$data[$field];
212 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset required for multiselect
213 $allowed = array_keys( $info['options'] );
214 if ( array_diff( $data[$field], $allowed ) !== [] ) {
215 return false;
216 }
217 break;
218 }
219 }
220
221 $this->$field = $data[$field];
222 }
223
224 return true;
225 }
226
245 public function describeCredentials() {
246 return [
247 'provider' => new RawMessage( '$1', [ get_called_class() ] ),
248 'account' => new RawMessage( '$1', [ $this->getUniqueId() ] ),
249 ];
250 }
251
259 public static function loadRequestsFromSubmission( array $reqs, array $data ) {
260 $result = [];
261 foreach ( $reqs as $req ) {
262 if ( $req->loadFromSubmission( $data ) ) {
263 $result[] = $req;
264 }
265 }
266 return $result;
267 }
268
282 public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
283 $requests = array_filter( $reqs, static function ( $req ) use ( $class, $allowSubclasses ) {
284 if ( $allowSubclasses ) {
285 return is_a( $req, $class, false );
286 } else {
287 return get_class( $req ) === $class;
288 }
289 } );
290 // @phan-suppress-next-line PhanTypeMismatchReturn False positive
291 return count( $requests ) === 1 ? reset( $requests ) : null;
292 }
293
303 public static function getUsernameFromRequests( array $reqs ) {
304 $username = null;
305 $otherClass = null;
306 foreach ( $reqs as $req ) {
307 $info = $req->getFieldInfo();
308 if ( $info && array_key_exists( 'username', $info ) && $req->username !== null ) {
309 if ( $username === null ) {
310 $username = $req->username;
311 $otherClass = get_class( $req );
312 } elseif ( $username !== $req->username ) {
313 $requestClass = get_class( $req );
314 throw new UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
315 // @phan-suppress-next-line PhanTypeSuspiciousStringExpression $otherClass always set
316 . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
317 }
318 }
319 }
320 return $username;
321 }
322
329 public static function mergeFieldInfo( array $reqs ) {
330 $merged = [];
331
332 // fields that are required by some primary providers but not others are not actually required
333 $sharedRequiredPrimaryFields = null;
334 foreach ( $reqs as $req ) {
335 if ( $req->required !== self::PRIMARY_REQUIRED ) {
336 continue;
337 }
338 $required = [];
339 foreach ( $req->getFieldInfo() as $fieldName => $options ) {
340 if ( empty( $options['optional'] ) ) {
341 $required[] = $fieldName;
342 }
343 }
344 if ( $sharedRequiredPrimaryFields === null ) {
345 $sharedRequiredPrimaryFields = $required;
346 } else {
347 $sharedRequiredPrimaryFields = array_intersect( $sharedRequiredPrimaryFields, $required );
348 }
349 }
350
351 foreach ( $reqs as $req ) {
352 $info = $req->getFieldInfo();
353 if ( !$info ) {
354 continue;
355 }
356
357 foreach ( $info as $name => $options ) {
358 if (
359 // If the request isn't required, its fields aren't required either.
360 $req->required === self::OPTIONAL
361 // If there is a primary not requiring this field, no matter how many others do,
362 // authentication can proceed without it.
363 || $req->required === self::PRIMARY_REQUIRED
364 // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal False positive
365 && !in_array( $name, $sharedRequiredPrimaryFields, true )
366 ) {
367 $options['optional'] = true;
368 } else {
369 $options['optional'] = !empty( $options['optional'] );
370 }
371
372 $options['sensitive'] = !empty( $options['sensitive'] );
373 $type = $options['type'];
374
375 if ( !array_key_exists( $name, $merged ) ) {
376 $merged[$name] = $options;
377 } elseif ( $merged[$name]['type'] !== $type ) {
378 throw new UnexpectedValueException( "Field type conflict for \"$name\", " .
379 "\"{$merged[$name]['type']}\" vs \"$type\""
380 );
381 } else {
382 if ( isset( $options['options'] ) ) {
383 if ( isset( $merged[$name]['options'] ) ) {
384 $merged[$name]['options'] += $options['options'];
385 } else {
386 // @codeCoverageIgnoreStart
387 $merged[$name]['options'] = $options['options'];
388 // @codeCoverageIgnoreEnd
389 }
390 }
391
392 $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
393 $merged[$name]['sensitive'] = $merged[$name]['sensitive'] || $options['sensitive'];
394
395 // No way to merge 'value', 'image', 'help', or 'label', so just use
396 // the value from the first request.
397 }
398 }
399 }
400
401 return $merged;
402 }
403
409 public static function __set_state( $data ) {
410 // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic
411 $ret = new static();
412 foreach ( $data as $k => $v ) {
413 $ret->$k = $v;
414 }
415 return $ret;
416 }
417}
This is a value object for authentication requests.
getFieldInfo()
Fetch input field info.
string null $returnToUrl
Return-to URL, in case of redirect.
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
For login, continue, and link actions, one of self::OPTIONAL, self::REQUIRED, or self::PRIMARY_REQUIR...
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:157