* This can optionally create the user if it doesn't exist, and "steal" the
* account if it does exist.
*
+ * "Stealing" an existing user is intended to make it impossible for normal
+ * authentication processes to use the account, effectively disabling the
+ * account for normal use:
+ * - Email is invalidated, to prevent account recovery by emailing a
+ * temporary password and to disassociate the account from the existing
+ * human.
+ * - The token is set to a magic invalid value, to kill existing sessions
+ * and to prevent $this->setToken() calls from resetting the token to a
+ * valid value.
+ * - SessionManager is instructed to prevent new sessions for the user, to
+ * do things like deauthorizing OAuth consumers.
+ * - AuthManager is instructed to revoke access, to invalidate or remove
+ * passwords and other credentials.
+ *
* @param string $name Username
* @param array $options Options are:
* - validate: As for User::getCanonicalName(), default 'valid'
* - create: Whether to create the user if it doesn't already exist, default true
- * - steal: Whether to reset the account's password and email if it
- * already exists, default false
+ * - steal: Whether to "disable" the account for normal use if it already
+ * exists, default false
* @return User|null
* @since 1.27
*/
}
$user = self::newFromRow( $row );
- // A user is considered to exist as a non-system user if it has a
- // password set, or a temporary password set, or an email set, or a
- // non-invalid token.
+ // A user is considered to exist as a non-system user if it can
+ // authenticate, or has an email set, or has a non-invalid token.
if ( !$user->mEmail && $user->mToken === self::INVALID_TOKEN ) {
if ( $wgDisableAuthManager ) {
$passwordFactory = new PasswordFactory();
return $toPromote;
}
+ /**
+ * Builds update conditions. Additional conditions may be added to $conditions to
+ * protected against race conditions using a compare-and-set (CAS) mechanism
+ * based on comparing $this->mTouched with the user_touched field.
+ *
+ * @param DatabaseBase $db
+ * @param array $conditions WHERE conditions for use with DatabaseBase::update
+ * @return array WHERE conditions for use with DatabaseBase::update
+ */
+ protected function makeUpdateConditions( DatabaseBase $db, array $conditions ) {
+ if ( $this->mTouched ) {
+ // CAS check: only update if the row wasn't changed sicne it was loaded.
+ $conditions['user_touched'] = $db->timestamp( $this->mTouched );
+ }
+
+ return $conditions;
+ }
+
/**
* Bump user_touched if it didn't change since this object was loaded
*
}
// Get a new user_touched that is higher than the old one
- $oldTouched = $this->mTouched;
$newTouched = $this->newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'user',
[ 'user_touched' => $dbw->timestamp( $newTouched ) ],
- [
+ $this->makeUpdateConditions( $dbw, [
'user_id' => $this->mId,
- 'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
- ],
+ ] ),
__METHOD__
);
$success = ( $dbw->affectedRows() > 0 );
if ( !$session->canSetUser() ) {
\MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
->warning( __METHOD__ . ": Cannot log out of an immutable session" );
+ $error = 'immutable';
} elseif ( !$session->getUser()->equals( $this ) ) {
\MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
->warning( __METHOD__ .
);
// But we still may as well make this user object anon
$this->clearInstanceCache( 'defaults' );
+ $error = 'wronguser';
} else {
$this->clearInstanceCache( 'defaults' );
$delay = $session->delaySave();
$session->setLoggedOutTimestamp( time() );
$session->setUser( new User );
$session->set( 'wsUserID', 0 ); // Other code expects this
+ $session->resetAllTokens();
ScopedCallback::consume( $delay );
+ $error = false;
}
+ \MediaWiki\Logger\LoggerFactory::getInstance( 'authmanager' )->info( 'Logout', [
+ 'event' => 'logout',
+ 'successful' => $error === false,
+ 'status' => $error ?: 'success',
+ ] );
}
/**
// Get a new user_touched that is higher than the old one.
// This will be used for a CAS check as a last-resort safety
// check against race conditions and slave lag.
- $oldTouched = $this->mTouched;
$newTouched = $this->newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
'user_token' => strval( $this->mToken ),
'user_email_token' => $this->mEmailToken,
'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
- ], [ /* WHERE */
+ ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
'user_id' => $this->mId,
- 'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
- ], __METHOD__
+ ] ), __METHOD__
);
if ( !$dbw->affectedRows() ) {