wiki.techinc.nl/includes/auth/AuthenticationResponse.php
dreamyjazz 212b9b90cd Allow AuthenticationResponse to store private failure reasons
Allows AuthenticationResponse to store, when the status is FAIL,
an array of strings that describe the reasons for the failure.
These are stored in $failReasons and are not intended for the
client. On any other status $failReasons is null. These are
optionally provided when calling AuthenticationResponse::newFail
in the parameter $failReasons.

This is implemented to allow the CentralAuth extension to store
whether the password was correct if the account is locked inside
the AuthenticationResponse. The extension CheckUser which hooks
into authentication requests then can read the failure reasons
from the AuthenticationResponse, and can then note in the CU
entry that the login attempt had the correct password.

If whether the correct password was used is stored in the I18n
message, the client would then know if the password they tried
on the locked account was correct. For comprimised accounts this
could be used by mailicious actors to verify that the password
was correct and then try it elsewhere if the account has the same
password as on other sites. This means, unless I have missed
another method, a new array is needed to store these failure reasons.

This, along with some other patches to CheckUser and CentralAuth,
will then allow Checkusers to see if a login attempt for a locked
account had the correct password. Checkusers can then use this,
with the knowledge that the account isn't comprimised, to say that
the login attempt was made by the owner of the account so in cases
of socking the creation of a new account can be more conclusively
said to be by the person who created the now locked sock account.

Bug: T303192
Change-Id: I7b2d9579a518a6c02f05281b1016e31e0d086fe7
2022-05-14 20:41:51 +01:00

231 lines
7.8 KiB
PHP

<?php
/**
* Authentication response value object
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Auth
*/
namespace MediaWiki\Auth;
use Message;
/**
* This is a value object to hold authentication response data
*
* An AuthenticationResponse represents both the status of the authentication
* (success, failure, in progress) and it its state (what data is needed to continue).
*
* @ingroup Auth
* @since 1.27
*/
class AuthenticationResponse {
/** Indicates that the authentication succeeded. */
public const PASS = 'PASS';
/** Indicates that the authentication failed. */
public const FAIL = 'FAIL';
/** Indicates that third-party authentication succeeded but no user exists.
* Either treat this like a UI response or pass $this->createRequest to
* AuthManager::beginCreateAccount(). For use by AuthManager only (providers
* should just return a PASS with no username).
*/
public const RESTART = 'RESTART';
/** Indicates that the authentication provider does not handle this request. */
public const ABSTAIN = 'ABSTAIN';
/** Indicates that the authentication needs further user input of some sort. */
public const UI = 'UI';
/** Indicates that the authentication needs to be redirected to a third party to proceed. */
public const REDIRECT = 'REDIRECT';
/** @var string One of the constants above */
public $status;
/** @var string|null URL to redirect to for a REDIRECT response */
public $redirectTarget = null;
/**
* @var mixed Data for a REDIRECT response that a client might use to
* query the remote site via its API rather than by following $redirectTarget.
* Value must be something acceptable to ApiResult::addValue().
*/
public $redirectApiData = null;
/**
* @var AuthenticationRequest[] Needed AuthenticationRequests to continue
* after a UI or REDIRECT response. This plays the same role when continuing
* authentication as AuthManager::getAuthenticationRequests() does when
* beginning it.
*/
public $neededRequests = [];
/** @var Message|null I18n message to display in case of UI or FAIL */
public $message = null;
/** @var string Whether the $message is an error or warning message, for styling reasons */
public $messageType = 'warning';
/**
* @var string|null Local user name from authentication.
* May be null if the authentication passed but no local user is known.
*/
public $username = null;
/**
* @var AuthenticationRequest|null
*
* Returned with a PrimaryAuthenticationProvider login FAIL or a PASS with
* no username, this can be set to a request that should result in a PASS when
* passed to that provider's PrimaryAuthenticationProvider::beginPrimaryAccountCreation().
* The client will be able to send that back for expedited account creation where only
* the username needs to be filled.
*
* Returned with an AuthManager login FAIL or RESTART, this holds a
* CreateFromLoginAuthenticationRequest that may be passed to
* AuthManager::beginCreateAccount(), possibly in place of any
* "primary-required" requests. It may also be passed to
* AuthManager::beginAuthentication() to preserve the list of
* accounts which can be linked after success (see $linkRequest).
*/
public $createRequest = null;
/**
* @var AuthenticationRequest|null When returned with a PrimaryAuthenticationProvider
* login PASS with no username, the request this holds will be passed to
* AuthManager::changeAuthenticationData() once the local user has been determined and the
* user has confirmed the account ownership (by reviewing the information given by
* $linkRequest->describeCredentials()). The provider should handle that
* changeAuthenticationData() call by doing the actual linking.
*/
public $linkRequest = null;
/**
* @var AuthenticationRequest|null Returned with an AuthManager account
* creation PASS, this holds a request to pass to AuthManager::beginAuthentication()
* to immediately log into the created account. All provider methods except
* postAuthentication will be skipped.
*/
public $loginRequest = null;
/**
* @var string[]|null String data that is optionally provided on a FAIL. It describes information about the
* failed AuthenticationResponse that shouldn't be shared with the client.
*
* The CheckUser extension uses this so that it can receive whether a login request for a locked
* account had the correct password. Using the I18n message would allow the client to see if the
* password they tried on the locked account was correct while this method does not show the client this info.
*/
public $failReasons = null;
/**
* @param string|null $username Local username
* @return AuthenticationResponse
* @see AuthenticationResponse::PASS
*/
public static function newPass( $username = null ) {
$ret = new AuthenticationResponse;
$ret->status = self::PASS;
$ret->username = $username;
return $ret;
}
/**
* @param Message $msg
* @param string[] $failReasons An array of strings that describes the reason(s) for a login failure
* @return AuthenticationResponse
* @see AuthenticationResponse::FAIL
*/
public static function newFail( Message $msg, array $failReasons = [] ) {
$ret = new AuthenticationResponse;
$ret->status = self::FAIL;
$ret->message = $msg;
$ret->messageType = 'error';
$ret->failReasons = $failReasons;
return $ret;
}
/**
* @param Message $msg
* @return AuthenticationResponse
* @see AuthenticationResponse::RESTART
*/
public static function newRestart( Message $msg ) {
$ret = new AuthenticationResponse;
$ret->status = self::RESTART;
$ret->message = $msg;
return $ret;
}
/**
* @return AuthenticationResponse
* @see AuthenticationResponse::ABSTAIN
*/
public static function newAbstain() {
$ret = new AuthenticationResponse;
$ret->status = self::ABSTAIN;
return $ret;
}
/**
* @param AuthenticationRequest[] $reqs AuthenticationRequests needed to continue
* @param Message $msg
* @param string $msgtype
* @return AuthenticationResponse
* @see AuthenticationResponse::UI
*/
public static function newUI( array $reqs, Message $msg, $msgtype = 'warning' ) {
if ( !$reqs ) {
throw new \InvalidArgumentException( '$reqs may not be empty' );
}
if ( $msgtype !== 'warning' && $msgtype !== 'error' ) {
throw new \InvalidArgumentException( $msgtype . ' is not a valid message type.' );
}
$ret = new AuthenticationResponse;
$ret->status = self::UI;
$ret->neededRequests = $reqs;
$ret->message = $msg;
$ret->messageType = $msgtype;
return $ret;
}
/**
* @param AuthenticationRequest[] $reqs AuthenticationRequests needed to continue
* @param string $redirectTarget URL
* @param mixed|null $redirectApiData Data suitable for adding to an ApiResult
* @return AuthenticationResponse
* @see AuthenticationResponse::REDIRECT
*/
public static function newRedirect( array $reqs, $redirectTarget, $redirectApiData = null ) {
if ( !$reqs ) {
throw new \InvalidArgumentException( '$reqs may not be empty' );
}
$ret = new AuthenticationResponse;
$ret->status = self::REDIRECT;
$ret->neededRequests = $reqs;
$ret->redirectTarget = $redirectTarget;
$ret->redirectApiData = $redirectApiData;
return $ret;
}
}