X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fauth%2FAuthManager.php;h=51efe5696ad9f573066fdd675b05c5df0ac7dc7c;hb=5afced65643c322ccd5ade1312e75013bce6c71f;hp=acdc01bfea325566716b419e9c75705503a30779;hpb=d7c4e65fddc7a737395cb1dbaedb4d51eff3cc43;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/auth/AuthManager.php b/includes/auth/AuthManager.php index acdc01bfea..51efe5696a 100644 --- a/includes/auth/AuthManager.php +++ b/includes/auth/AuthManager.php @@ -37,8 +37,46 @@ use WebRequest; * In the future, it may also serve as the entry point to the authorization * system. * + * If you are looking at this because you are working on an extension that creates its own + * login or signup page, then 1) you really shouldn't do that, 2) if you feel you absolutely + * have to, subclass AuthManagerSpecialPage or build it on the client side using the clientlogin + * or the createaccount API. Trying to call this class directly will very likely end up in + * security vulnerabilities or broken UX in edge cases. + * + * If you are working on an extension that needs to integrate with the authentication system + * (e.g. by providing a new login method, or doing extra permission checks), you'll probably + * need to write an AuthenticationProvider. + * + * If you want to create a "reserved" user programmatically, User::newSystemUser() might be what + * you are looking for. If you want to change user data, use User::changeAuthenticationData(). + * Code that is related to some SessionProvider or PrimaryAuthenticationProvider can + * create a (non-reserved) user by calling AuthManager::autoCreateUser(); it is then the provider's + * responsibility to ensure that the user can authenticate somehow (see especially + * PrimaryAuthenticationProvider::autoCreatedAccount()). + * If you are writing code that is not associated with such a provider and needs to create accounts + * programmatically for real users, you should rethink your architecture. There is no good way to + * do that as such code has no knowledge of what authentication methods are enabled on the wiki and + * cannot provide any means for users to access the accounts it would create. + * + * The two main control flows when using this class are as follows: + * * Login, user creation or account linking code will call getAuthenticationRequests(), populate + * the requests with data (by using them to build a HTMLForm and have the user fill it, or by + * exposing a form specification via the API, so that the client can build it), and pass them to + * the appropriate begin* method. That will return either a success/failure response, or more + * requests to fill (either by building a form or by redirecting the user to some external + * provider which will send the data back), in which case they need to be submitted to the + * appropriate continue* method and that step has to be repeated until the response is a success + * or failure response. AuthManager will use the session to maintain internal state during the + * process. + * * Code doing an authentication data change will call getAuthenticationRequests(), select + * a single request, populate it, and pass it to allowsAuthenticationDataChange() and then + * changeAuthenticationData(). If the data change is user-initiated, the whole process needs + * to be preceded by a call to securitySensitiveOperationStatus() and aborted if that returns + * a non-OK status. + * * @ingroup Auth * @since 1.27 + * @see https://www.mediawiki.org/wiki/Manual:SessionManager_and_AuthManager */ class AuthManager implements LoggerAwareInterface { /** Log in with an existing (not necessarily local) user */ @@ -568,6 +606,7 @@ class AuthManager implements LoggerAwareInterface { $user = User::newFromName( $res->username, 'usable' ); if ( !$user ) { + $provider = $this->getAuthenticationProvider( $state['primary'] ); throw new \DomainException( get_class( $provider ) . " returned an invalid username: {$res->username}" ); @@ -643,6 +682,7 @@ class AuthManager implements LoggerAwareInterface { $this->logger->info( 'Login for {user} succeeded', [ 'user' => $user->getName(), ] ); + /** @var RememberMeAuthenticationRequest $req */ $req = AuthenticationRequest::getRequestByClass( $beginReqs, RememberMeAuthenticationRequest::class ); @@ -737,7 +777,10 @@ class AuthManager implements LoggerAwareInterface { /** * Determine whether a username can authenticate * - * @param string $username + * This is mainly for internal purposes and only takes authentication data into account, + * not things like blocks that can change without the authentication system being aware. + * + * @param string $username MediaWiki username * @return bool */ public function userCanAuthenticate( $username ) { @@ -832,6 +875,9 @@ class AuthManager implements LoggerAwareInterface { * If $req was returned for AuthManager::ACTION_REMOVE, using $req should * no longer result in a successful login. * + * This method should only be called if allowsAuthenticationDataChange( $req, true ) + * returned success. + * * @param AuthenticationRequest $req */ public function changeAuthenticationData( AuthenticationRequest $req ) { @@ -871,7 +917,7 @@ class AuthManager implements LoggerAwareInterface { /** * Determine whether a particular account can be created - * @param string $username + * @param string $username MediaWiki username * @param array $options * - flags: (int) Bitfield of User:READ_* constants, default User::READ_NORMAL * - creating: (bool) For internal use only. Never specify this. @@ -1354,7 +1400,7 @@ class AuthManager implements LoggerAwareInterface { 'creator' => $creator->getName(), ] ); $status = $user->addToDatabase(); - if ( !$status->isOk() ) { + if ( !$status->isOK() ) { // @codeCoverageIgnoreStart $ret = AuthenticationResponse::newFail( $status->getMessage() ); $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] ); @@ -1385,6 +1431,7 @@ class AuthManager implements LoggerAwareInterface { ); $logEntry->setPerformer( $isAnon ? $user : $creator ); $logEntry->setTarget( $user->getUserPage() ); + /** @var CreationReasonAuthenticationRequest $req */ $req = AuthenticationRequest::getRequestByClass( $state['reqs'], CreationReasonAuthenticationRequest::class ); @@ -1474,6 +1521,13 @@ class AuthManager implements LoggerAwareInterface { /** * Auto-create an account, and log into that account + * + * PrimaryAuthenticationProviders can invoke this method by returning a PASS from + * beginPrimaryAuthentication/continuePrimaryAuthentication with the username of a + * non-existing user. SessionProviders can invoke it by returning a SessionInfo with + * the username of a non-existing user from provideSessionInfo(). Calling this method + * explicitly (e.g. from a maintenance script) is also fine. + * * @param User $user User to auto-create * @param string $source What caused the auto-creation? This must be the ID * of a PrimaryAuthenticationProvider or the constant self::AUTOCREATE_SOURCE_SESSION. @@ -1489,7 +1543,7 @@ class AuthManager implements LoggerAwareInterface { $username = $user->getName(); - // Try the local user from the slave DB + // Try the local user from the replica DB $localId = User::idFromName( $username ); $flags = User::READ_NORMAL; @@ -1550,7 +1604,7 @@ class AuthManager implements LoggerAwareInterface { $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [ 'username' => $username, ] ); - $session->set( 'AuthManager::AutoCreateBlacklist', 'noname', 600 ); + $session->set( 'AuthManager::AutoCreateBlacklist', 'noname' ); $user->setId( 0 ); $user->loadFromId(); return Status::newFatal( 'noname' ); @@ -1563,7 +1617,7 @@ class AuthManager implements LoggerAwareInterface { 'username' => $username, 'ip' => $anon->getName(), ] ); - $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm', 600 ); + $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm' ); $session->persist(); $user->setId( 0 ); $user->loadFromId(); @@ -1598,7 +1652,7 @@ class AuthManager implements LoggerAwareInterface { 'username' => $username, 'reason' => $ret->getWikiText( null, null, 'en' ), ] ); - $session->set( 'AuthManager::AutoCreateBlacklist', $status, 600 ); + $session->set( 'AuthManager::AutoCreateBlacklist', $status ); $user->setId( 0 ); $user->loadFromId(); return $ret; @@ -1627,15 +1681,13 @@ class AuthManager implements LoggerAwareInterface { $trxProfiler->setSilenced( true ); try { $status = $user->addToDatabase(); - if ( !$status->isOk() ) { - // double-check for a race condition (T70012) - $localId = User::idFromName( $username, User::READ_LATEST ); - if ( $localId ) { + if ( !$status->isOK() ) { + // Double-check for a race condition (T70012). We make use of the fact that when + // addToDatabase fails due to the user already existing, the user object gets loaded. + if ( $user->getId() ) { $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [ 'username' => $username, ] ); - $user->setId( $localId ); - $user->loadFromId( User::READ_LATEST ); if ( $login ) { $this->setSessionDataForUser( $user ); } @@ -2310,6 +2362,7 @@ class AuthManager implements LoggerAwareInterface { } /** + * Log the user in * @param User $user * @param bool|null $remember */ @@ -2374,6 +2427,7 @@ class AuthManager implements LoggerAwareInterface { /** * Reset the internal caching for unit testing + * @protected Unit tests only */ public static function resetCache() { if ( !defined( 'MW_PHPUNIT_TEST' ) ) {