-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(session): Replace remember-me tokens in transaction #40628
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,13 +48,15 @@ | |
use OC_User; | ||
use OC_Util; | ||
use OCA\DAV\Connector\Sabre\Auth; | ||
use OCP\AppFramework\Db\TTransactional; | ||
use OCP\AppFramework\Utility\ITimeFactory; | ||
use OCP\Authentication\Exceptions\ExpiredTokenException; | ||
use OCP\Authentication\Exceptions\InvalidTokenException; | ||
use OCP\EventDispatcher\GenericEvent; | ||
use OCP\EventDispatcher\IEventDispatcher; | ||
use OCP\Files\NotPermittedException; | ||
use OCP\IConfig; | ||
use OCP\IDBConnection; | ||
use OCP\IRequest; | ||
use OCP\ISession; | ||
use OCP\IUser; | ||
|
@@ -67,6 +69,7 @@ | |
use OCP\User\Events\UserFirstTimeLoggedInEvent; | ||
use OCP\Util; | ||
use Psr\Log\LoggerInterface; | ||
use function in_array; | ||
|
||
/** | ||
* Class Session | ||
|
@@ -91,6 +94,9 @@ | |
* @package OC\User | ||
*/ | ||
class Session implements IUserSession, Emitter { | ||
|
||
use TTransactional; | ||
|
||
/** @var Manager $manager */ | ||
private $manager; | ||
|
||
|
@@ -116,6 +122,7 @@ | |
private $lockdownManager; | ||
|
||
private LoggerInterface $logger; | ||
private IDBConnection $dbConnection; | ||
/** @var IEventDispatcher */ | ||
private $dispatcher; | ||
|
||
|
@@ -127,6 +134,7 @@ | |
ISecureRandom $random, | ||
ILockdownManager $lockdownManager, | ||
LoggerInterface $logger, | ||
IDBConnection $dbConnection, | ||
IEventDispatcher $dispatcher | ||
) { | ||
$this->manager = $manager; | ||
|
@@ -137,6 +145,7 @@ | |
$this->random = $random; | ||
$this->lockdownManager = $lockdownManager; | ||
$this->logger = $logger; | ||
$this->dbConnection = $dbConnection; | ||
$this->dispatcher = $dispatcher; | ||
} | ||
|
||
|
@@ -905,24 +914,49 @@ | |
return false; | ||
} | ||
|
||
// get stored tokens | ||
$tokens = $this->config->getUserKeys($uid, 'login_token'); | ||
// test cookies token against stored tokens | ||
if (!in_array($currentToken, $tokens, true)) { | ||
$this->logger->info('Tried to log in but could not verify token', [ | ||
/* | ||
* Run token lookup and replacement in a transaction | ||
* | ||
* The READ COMMITTED isolation level causes the database to serialize | ||
* the DELETE query, making it possible to detect if two concurrent | ||
* processes try to replace the same login token. | ||
* Replacing more than once doesn't work because the app token behind | ||
* the session can only be replaced once. | ||
*/ | ||
$newToken = $this->atomic(function () use ($uid, $currentToken): ?string { | ||
// get stored tokens | ||
$tokens = $this->config->getUserKeys($uid, 'login_token'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. food for thought: we might be able to skip the SELECT and go for the DELETE directly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you doing the select so you have that explict? As to ensure no database funny buisness goes on when you do a delete+insert in the transaction? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the DELETE and INSERT should not trigger a conflict, even in concurrent situations, because the unique contraint is on appid and configkey. configkey is the new, random token. only in theory there might be two processes with the same random token ;-) |
||
// test cookies token against stored tokens | ||
if (!in_array($currentToken, $tokens, true)) { | ||
$this->logger->error('Tried to log in {uid} but could not find token {token} in database', [ | ||
'app' => 'core', | ||
'token' => $currentToken, | ||
'uid' => $uid, | ||
'user' => $uid, | ||
]); | ||
return false; | ||
Check failure on line 937 in lib/private/User/Session.php GitHub Actions / static-code-analysisFalsableReturnStatement
|
||
Check failure Code scanning / Psalm FalsableReturnStatement Error
The declared return type 'null|string' for /home/runner/actions-runner/_work/server/server/lib/private/user/session.php:926:26323:-:closure does not allow false, but the function returns 'false'
|
||
} | ||
// replace successfully used token with a new one | ||
if (!$this->config->deleteUserValue($uid, 'login_token', $currentToken)) { | ||
$this->logger->error('Tried to log in {uid} but ran into concurrent session revival', [ | ||
'app' => 'core', | ||
'token' => $currentToken, | ||
'uid' => $uid, | ||
'user' => $uid, | ||
]); | ||
return null; | ||
} | ||
$newToken = $this->random->generate(32); | ||
$this->config->setUserValue($uid, 'login_token', $newToken, (string)$this->timeFactory->getTime()); | ||
$this->logger->debug('Remember-me token {token} for {uid} replaced by {newToken}', [ | ||
'app' => 'core', | ||
'token' => $currentToken, | ||
'newToken' => $newToken, | ||
'uid' => $uid, | ||
'user' => $uid, | ||
]); | ||
return false; | ||
} | ||
// replace successfully used token with a new one | ||
$this->config->deleteUserValue($uid, 'login_token', $currentToken); | ||
$newToken = $this->random->generate(32); | ||
$this->config->setUserValue($uid, 'login_token', $newToken, (string)$this->timeFactory->getTime()); | ||
$this->logger->debug('Remember-me token replaced', [ | ||
'app' => 'core', | ||
'user' => $uid, | ||
]); | ||
return $newToken; | ||
}, $this->dbConnection); | ||
|
||
try { | ||
$sessionId = $this->session->getId(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this in general 👍