From 55572b08a6845e892870abcac4b8b0718b7c9075 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gerg=C5=91=20Tisza?= Date: Thu, 8 Sep 2016 22:40:20 +0000 Subject: [PATCH] Avoid user autocreation race condition caused by repeatable read AuthManager tries to check whether the user already exists if User::addToDatabase fails in autocreation, but since the same DB row was already checked a few lines earlier and this method is typically wrapped in an implicit transaction, it will just re-read the same snapshot and not do anything useful. addToDatabase already has a check for that so let's rely on that instead. Bug: T145131 Change-Id: I94a5e8b851dcf994f5f9e773edf4e9153a4a3535 --- includes/auth/AuthManager.php | 8 +++----- tests/phpunit/includes/auth/AuthManagerTest.php | 6 ++++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/includes/auth/AuthManager.php b/includes/auth/AuthManager.php index 992e70f118..89a22f8402 100644 --- a/includes/auth/AuthManager.php +++ b/includes/auth/AuthManager.php @@ -1679,14 +1679,12 @@ class AuthManager implements LoggerAwareInterface { try { $status = $user->addToDatabase(); if ( !$status->isOk() ) { - // double-check for a race condition (T70012) - $localId = User::idFromName( $username, User::READ_LATEST ); - if ( $localId ) { + // 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 ); } diff --git a/tests/phpunit/includes/auth/AuthManagerTest.php b/tests/phpunit/includes/auth/AuthManagerTest.php index 788d304295..f679f63345 100644 --- a/tests/phpunit/includes/auth/AuthManagerTest.php +++ b/tests/phpunit/includes/auth/AuthManagerTest.php @@ -2709,9 +2709,11 @@ class AuthManagerTest extends \MediaWikiTestCase { $session->clear(); $user = $this->getMock( 'User', [ 'addToDatabase' ] ); $user->expects( $this->once() )->method( 'addToDatabase' ) - ->will( $this->returnCallback( function () use ( $username ) { - $status = \User::newFromName( $username )->addToDatabase(); + ->will( $this->returnCallback( function () use ( $username, &$user ) { + $oldUser = \User::newFromName( $username ); + $status = $oldUser->addToDatabase(); $this->assertTrue( $status->isOK(), 'sanity check' ); + $user->setId( $oldUser->getId() ); return \Status::newFatal( 'userexists' ); } ) ); $user->setName( $username ); -- 2.20.1