* This is a value object for authentication requests.
*
* An AuthenticationRequest represents a set of form fields that are needed on
- * and provided from the login, account creation, or password change forms.
+ * and provided from a login, account creation, password change or similar form.
*
* @ingroup Auth
* @since 1.27
/** Indicates that the request is not required for authentication to proceed. */
const OPTIONAL = 0;
- /** Indicates that the request is required for authentication to proceed. */
+ /** Indicates that the request is required for authentication to proceed.
+ * This will only be used for UI purposes; it is the authentication providers'
+ * responsibility to verify that all required requests are present.
+ */
const REQUIRED = 1;
/** Indicates that the request is required by a primary authentication
- * provdier, but other primary authentication providers do not require it. */
+ * provider. Since the user can choose which primary to authenticate with,
+ * the request might or might not end up being actually required. */
const PRIMARY_REQUIRED = 2;
/** @var string|null The AuthManager::ACTION_* constant this request was
/** @var string|null Return-to URL, in case of redirect */
public $returnToUrl = null;
- /** @var string|null Username. May not be used by all subclasses. */
+ /** @var string|null Username. See AuthenticationProvider::getAuthenticationRequests()
+ * for details of what this means and how it behaves. */
public $username = null;
/**
* - select: <select>
* - checkbox: <input type="checkbox">
* - multiselect: More a grid of checkboxes than <select multi>
- * - button: <input type="image"> if 'image' is set, otherwise <input type="submit">
- * (uses 'label' as button text)
+ * - button: <input type="submit"> (uses 'label' as button text)
* - hidden: Not visible to the user, but needs to be preserved for the next request
* - null: No widget, just display the 'label' message.
* - options: (array) Maps option values to Messages for the
* 'select' and 'multiselect' types.
* - value: (string) Value (for 'null' and 'hidden') or default value (for other types).
- * - image: (string) URL of an image to use in connection with the input
* - label: (Message) Text suitable for a label in an HTML form
* - help: (Message) Text suitable as a description of what the field is
* - optional: (bool) If set and truthy, the field may be left empty
+ * - sensitive: (bool) If set and truthy, the field is considered sensitive. Code using the
+ * request should avoid exposing the value of the field.
+ *
+ * All AuthenticationRequests are populated from the same data, so most of the time you'll
+ * want to prefix fields names with something unique to the extension/provider (although
+ * in some cases sharing the field with other requests is the right thing to do, e.g. for
+ * a 'password' field).
*
* @return array As above
*/
/**
* Initialize form submitted form data.
*
- * Should always return false if self::getFieldInfo() returns an empty
- * array.
+ * The default behavior is to to check for each key of self::getFieldInfo()
+ * in the submitted data, and copy the value - after type-appropriate transformations -
+ * to $this->$key. Most subclasses won't need to override this; if you do override it,
+ * make sure to always return false if self::getFieldInfo() returns an empty array.
*
- * @param array $data Submitted data as an associative array
+ * @param array $data Submitted data as an associative array (keys will correspond
+ * to getFieldInfo())
* @return bool Whether the request data was successfully loaded
*/
public function loadFromSubmission( array $data ) {
*
* Only considers requests that have a "username" field.
*
- * @param AuthenticationRequest[] $requests
+ * @param AuthenticationRequest[] $reqs
* @return string|null
* @throws \UnexpectedValueException If multiple different usernames are present.
*/
public static function mergeFieldInfo( array $reqs ) {
$merged = [];
+ // fields that are required by some primary providers but not others are not actually required
+ $primaryRequests = array_filter( $reqs, function ( $req ) {
+ return $req->required === AuthenticationRequest::PRIMARY_REQUIRED;
+ } );
+ $sharedRequiredPrimaryFields = array_reduce( $primaryRequests, function ( $shared, $req ) {
+ $required = array_keys( array_filter( $req->getFieldInfo(), function ( $options ) {
+ return empty( $options['optional'] );
+ } ) );
+ if ( $shared === null ) {
+ return $required;
+ } else {
+ return array_intersect( $shared, $required );
+ }
+ }, null );
+
foreach ( $reqs as $req ) {
$info = $req->getFieldInfo();
if ( !$info ) {
}
foreach ( $info as $name => $options ) {
- if ( $req->required !== self::REQUIRED ) {
+ if (
// If the request isn't required, its fields aren't required either.
+ $req->required === self::OPTIONAL
+ // If there is a primary not requiring this field, no matter how many others do,
+ // authentication can proceed without it.
+ || $req->required === self::PRIMARY_REQUIRED
+ && !in_array( $name, $sharedRequiredPrimaryFields, true )
+ ) {
$options['optional'] = true;
} else {
$options['optional'] = !empty( $options['optional'] );
}
+ $options['sensitive'] = !empty( $options['sensitive'] );
+
if ( !array_key_exists( $name, $merged ) ) {
$merged[$name] = $options;
} elseif ( $merged[$name]['type'] !== $options['type'] ) {
}
$merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
+ $merged[$name]['sensitive'] = $merged[$name]['sensitive'] || $options['sensitive'];
// No way to merge 'value', 'image', 'help', or 'label', so just use
// the value from the first request.