Skip to content

Commit ce10d86

Browse files
juliusknorrsummersab
authored andcommitted
fix(authtoken): Store only one hash for authtokens with the current password per user
Signed-off-by: Julius Härtl <jus@bitgrid.net>
1 parent d71fa03 commit ce10d86

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

lib/private/Authentication/Token/PublicKeyTokenMapper.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,31 @@ public function updateActivity(IToken $token, int $now): void {
229229
);
230230
$update->executeStatement();
231231
}
232+
233+
public function updateHashesForUser(string $userId, string $passwordHash): void {
234+
$qb = $this->db->getQueryBuilder();
235+
$update = $qb->update($this->getTableName())
236+
->set('password_hash', $qb->createNamedParameter($passwordHash))
237+
->where(
238+
$qb->expr()->eq('uid', $qb->createNamedParameter($userId))
239+
);
240+
$update->executeStatement();
241+
}
242+
243+
public function getFirstTokenForUser(string $userId): ?PublicKeyToken {
244+
$qb = $this->db->getQueryBuilder();
245+
$qb->select('*')
246+
->from($this->getTableName())
247+
->where($qb->expr()->eq('uid', $qb->createNamedParameter($userId)))
248+
->setMaxResults(1)
249+
->orderBy('id');
250+
$result = $qb->executeQuery();
251+
252+
$data = $result->fetch();
253+
$result->closeCursor();
254+
if ($data === false) {
255+
return null;
256+
}
257+
return PublicKeyToken::fromRow($data);
258+
}
232259
}

lib/private/Authentication/Token/PublicKeyTokenProvider.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,23 @@ public function generateToken(string $token,
102102
$name = mb_substr($name, 0, 120) . '';
103103
}
104104

105+
// We need to check against one old token to see if there is a password
106+
// hash that we can reuse for detecting outdated passwords
107+
$randomOldToken = $this->mapper->getFirstTokenForUser($uid);
108+
$oldTokenMatches = $randomOldToken && $this->hasher->verify(sha1($password) . $password, $randomOldToken->getPasswordHash());
109+
105110
$dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
111+
112+
if ($oldTokenMatches) {
113+
$dbToken->setPasswordHash($randomOldToken->getPasswordHash());
114+
}
115+
106116
$this->mapper->insert($dbToken);
107117

118+
if (!$oldTokenMatches && $password !== null) {
119+
$this->updatePasswords($uid, $password);
120+
}
121+
108122
// Add the token to the cache
109123
$this->cache[$dbToken->getToken()] = $dbToken;
110124

@@ -289,10 +303,11 @@ public function setPassword(IToken $token, string $tokenId, string $password) {
289303

290304
// Update the password for all tokens
291305
$tokens = $this->mapper->getTokenByUser($token->getUID());
306+
$hashedPassword = $this->hashPassword($password);
292307
foreach ($tokens as $t) {
293308
$publicKey = $t->getPublicKey();
294309
$t->setPassword($this->encryptPassword($password, $publicKey));
295-
$t->setPasswordHash($this->hashPassword($password));
310+
$t->setPasswordHash($hashedPassword);
296311
$this->updateToken($t);
297312
}
298313
}
@@ -481,6 +496,13 @@ public function updatePasswords(string $uid, string $password) {
481496
$this->updateToken($t);
482497
}
483498
}
499+
500+
// If password hashes are different we update them all to be equal so
501+
// that the next execution only needs to verify once
502+
if (count($hashNeedsUpdate) > 1) {
503+
$newPasswordHash = $this->hashPassword($password);
504+
$this->mapper->updateHashesForUser($uid, $newPasswordHash);
505+
}
484506
}
485507

486508
private function logOpensslError() {

0 commit comments

Comments
 (0)