From 69f252a90d6666d5dc55fd46fcc5698da0cec164 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Tue, 25 Jun 2024 15:47:50 -0700 Subject: [PATCH 1/6] feat: Add IPasswordHashBackend Signed-off-by: Christopher Ng --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + .../User/Backend/IPasswordHashBackend.php | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 lib/public/User/Backend/IPasswordHashBackend.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index f0addbcdaa8a1..6bee8c52568bc 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -799,6 +799,7 @@ 'OCP\\User\\Backend\\IGetHomeBackend' => $baseDir . '/lib/public/User/Backend/IGetHomeBackend.php', 'OCP\\User\\Backend\\IGetRealUIDBackend' => $baseDir . '/lib/public/User/Backend/IGetRealUIDBackend.php', 'OCP\\User\\Backend\\IPasswordConfirmationBackend' => $baseDir . '/lib/public/User/Backend/IPasswordConfirmationBackend.php', + 'OCP\\User\\Backend\\IPasswordHashBackend' => $baseDir . '/lib/public/User/Backend/IPasswordHashBackend.php', 'OCP\\User\\Backend\\IProvideAvatarBackend' => $baseDir . '/lib/public/User/Backend/IProvideAvatarBackend.php', 'OCP\\User\\Backend\\IProvideEnabledStateBackend' => $baseDir . '/lib/public/User/Backend/IProvideEnabledStateBackend.php', 'OCP\\User\\Backend\\ISearchKnownUsersBackend' => $baseDir . '/lib/public/User/Backend/ISearchKnownUsersBackend.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 51044d28a46eb..31191f982ff0f 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -832,6 +832,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\User\\Backend\\IGetHomeBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IGetHomeBackend.php', 'OCP\\User\\Backend\\IGetRealUIDBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IGetRealUIDBackend.php', 'OCP\\User\\Backend\\IPasswordConfirmationBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IPasswordConfirmationBackend.php', + 'OCP\\User\\Backend\\IPasswordHashBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IPasswordHashBackend.php', 'OCP\\User\\Backend\\IProvideAvatarBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IProvideAvatarBackend.php', 'OCP\\User\\Backend\\IProvideEnabledStateBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IProvideEnabledStateBackend.php', 'OCP\\User\\Backend\\ISearchKnownUsersBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISearchKnownUsersBackend.php', diff --git a/lib/public/User/Backend/IPasswordHashBackend.php b/lib/public/User/Backend/IPasswordHashBackend.php new file mode 100644 index 0000000000000..7bb3b33e1d0a0 --- /dev/null +++ b/lib/public/User/Backend/IPasswordHashBackend.php @@ -0,0 +1,25 @@ + Date: Tue, 25 Jun 2024 15:47:50 -0700 Subject: [PATCH 2/6] feat: Implement IPasswordHashBackend in database user backend Signed-off-by: Christopher Ng --- lib/private/User/Database.php | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index cc7050f2da821..0a0231a778421 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -21,6 +21,7 @@ use OCP\User\Backend\IGetDisplayNameBackend; use OCP\User\Backend\IGetHomeBackend; use OCP\User\Backend\IGetRealUIDBackend; +use OCP\User\Backend\IPasswordHashBackend; use OCP\User\Backend\ISearchKnownUsersBackend; use OCP\User\Backend\ISetDisplayNameBackend; use OCP\User\Backend\ISetPasswordBackend; @@ -37,7 +38,8 @@ class Database extends ABackend implements IGetHomeBackend, ICountUsersBackend, ISearchKnownUsersBackend, - IGetRealUIDBackend { + IGetRealUIDBackend, + IPasswordHashBackend { /** @var CappedMemoryCache */ private $cache; @@ -176,6 +178,34 @@ public function setPassword(string $uid, string $password): bool { return false; } + public function getPasswordHash(string $userId): ?string { + $this->fixDI(); + if (!$this->userExists($userId)) { + return null; + } + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('password') + ->from($this->table) + ->where($qb->expr()->eq('uid_lower', $qb->createNamedParameter(mb_strtolower($userId)))); + /** @var false|string $hash */ + $hash = $qb->executeQuery()->fetchOne(); + if ($hash === false) { + return null; + } + $this->cache[$userId]['password'] = $hash; + return $hash; + } + + public function setPasswordHash(string $userId, string $passwordHash): bool { + $this->fixDI(); + $result = $this->updatePassword($userId, $passwordHash); + if (!$result) { + return false; + } + $this->cache[$userId]['password'] = $passwordHash; + return true; + } + /** * Set display name * From 34d97d45cfae8a4f82bedb54582c1fc8eaf41369 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Tue, 25 Jun 2024 15:47:50 -0700 Subject: [PATCH 3/6] feat: Allow getting/setting the password hash of a user Signed-off-by: Christopher Ng --- lib/private/User/LazyUser.php | 8 ++++++++ lib/private/User/User.php | 15 +++++++++++++++ lib/public/IUser.php | 14 ++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/lib/private/User/LazyUser.php b/lib/private/User/LazyUser.php index 80b2bfe510f89..cd3e268be48bc 100644 --- a/lib/private/User/LazyUser.php +++ b/lib/private/User/LazyUser.php @@ -73,6 +73,14 @@ public function setPassword($password, $recoveryPassword = null) { return $this->getUser()->setPassword($password, $recoveryPassword); } + public function getPasswordHash(): ?string { + return $this->getUser()->getPasswordHash(); + } + + public function setPasswordHash(string $passwordHash): bool { + return $this->getUser()->setPasswordHash($passwordHash); + } + public function getHome() { return $this->getUser()->getHome(); } diff --git a/lib/private/User/User.php b/lib/private/User/User.php index 644d3e27f881e..6495b5cf2761c 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -25,6 +25,7 @@ use OCP\IUserBackend; use OCP\Notification\IManager as INotificationManager; use OCP\User\Backend\IGetHomeBackend; +use OCP\User\Backend\IPasswordHashBackend; use OCP\User\Backend\IProvideAvatarBackend; use OCP\User\Backend\IProvideEnabledStateBackend; use OCP\User\Backend\ISetDisplayNameBackend; @@ -319,6 +320,20 @@ public function setPassword($password, $recoveryPassword = null) { } } + public function getPasswordHash(): ?string { + if (!($this->backend instanceof IPasswordHashBackend)) { + return null; + } + return $this->backend->getPasswordHash($this->uid); + } + + public function setPasswordHash(string $passwordHash): bool { + if (!($this->backend instanceof IPasswordHashBackend)) { + return false; + } + return $this->backend->setPasswordHash($this->uid, $passwordHash); + } + /** * get the users home folder to mount * diff --git a/lib/public/IUser.php b/lib/public/IUser.php index 2ed2d0d87b2fa..b808e734b95fe 100644 --- a/lib/public/IUser.php +++ b/lib/public/IUser.php @@ -76,6 +76,20 @@ public function delete(); */ public function setPassword($password, $recoveryPassword = null); + /** + * Get the password hash of the user + * + * @since 30.0.0 + */ + public function getPasswordHash(): ?string; + + /** + * Set the password hash of the user + * + * @since 30.0.0 + */ + public function setPasswordHash(string $passwordHash): bool; + /** * get the users home folder to mount * From d65f53184e696565c2f955dbf523d84388ead254 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Thu, 27 Jun 2024 16:58:06 -0700 Subject: [PATCH 4/6] docs: Add info for the password hashes Signed-off-by: Christopher Ng --- lib/public/IUser.php | 3 +++ lib/public/User/Backend/IPasswordHashBackend.php | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/lib/public/IUser.php b/lib/public/IUser.php index b808e734b95fe..4ba9a89f06420 100644 --- a/lib/public/IUser.php +++ b/lib/public/IUser.php @@ -79,6 +79,7 @@ public function setPassword($password, $recoveryPassword = null); /** * Get the password hash of the user * + * @return ?string the password hash hashed by `\OCP\Security\IHasher::hash()` * @since 30.0.0 */ public function getPasswordHash(): ?string; @@ -86,6 +87,8 @@ public function getPasswordHash(): ?string; /** * Set the password hash of the user * + * @param string $passwordHash the password hash hashed by `\OCP\Security\IHasher::hash()` + * @throws InvalidArgumentException when `$passwordHash` is not a valid hash * @since 30.0.0 */ public function setPasswordHash(string $passwordHash): bool; diff --git a/lib/public/User/Backend/IPasswordHashBackend.php b/lib/public/User/Backend/IPasswordHashBackend.php index 7bb3b33e1d0a0..2525b4e45ea57 100644 --- a/lib/public/User/Backend/IPasswordHashBackend.php +++ b/lib/public/User/Backend/IPasswordHashBackend.php @@ -9,16 +9,21 @@ namespace OCP\User\Backend; +use InvalidArgumentException; + /** * @since 30.0.0 */ interface IPasswordHashBackend { /** + * @return ?string the password hash hashed by `\OCP\Security\IHasher::hash()` * @since 30.0.0 */ public function getPasswordHash(string $userId): ?string; /** + * @param string $passwordHash the password hash hashed by `\OCP\Security\IHasher::hash()` + * @throws InvalidArgumentException when `$passwordHash` is not a valid hash * @since 30.0.0 */ public function setPasswordHash(string $userId, string $passwordHash): bool; From dba00560d201755fd1bd57a0a5bf6ee6704281ed Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Thu, 27 Jun 2024 16:58:06 -0700 Subject: [PATCH 5/6] perf: Return cached password hash Signed-off-by: Christopher Ng --- lib/private/User/Database.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index 0a0231a778421..194e998ef4f47 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -183,6 +183,9 @@ public function getPasswordHash(string $userId): ?string { if (!$this->userExists($userId)) { return null; } + if (!empty($this->cache[$userId]['password'])) { + return $this->cache[$userId]['password']; + } $qb = $this->dbConn->getQueryBuilder(); $qb->select('password') ->from($this->table) From c390ae94ff21c553fadc6645ca311eb42a46195f Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Thu, 27 Jun 2024 16:58:06 -0700 Subject: [PATCH 6/6] feat: Validate password hash Signed-off-by: Christopher Ng --- lib/private/User/Database.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index 194e998ef4f47..bd6aa7ba2c274 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -8,6 +8,7 @@ */ namespace OC\User; +use InvalidArgumentException; use OCP\AppFramework\Db\TTransactional; use OCP\Cache\CappedMemoryCache; use OCP\EventDispatcher\IEventDispatcher; @@ -200,6 +201,9 @@ public function getPasswordHash(string $userId): ?string { } public function setPasswordHash(string $userId, string $passwordHash): bool { + if (!\OCP\Server::get(IHasher::class)->validate($passwordHash)) { + throw new InvalidArgumentException(); + } $this->fixDI(); $result = $this->updatePassword($userId, $passwordHash); if (!$result) {