diff --git a/apps/encryption/composer/composer/autoload_classmap.php b/apps/encryption/composer/composer/autoload_classmap.php index 059296338b49a..814f39653e990 100644 --- a/apps/encryption/composer/composer/autoload_classmap.php +++ b/apps/encryption/composer/composer/autoload_classmap.php @@ -26,12 +26,11 @@ 'OCA\\Encryption\\Exceptions\\MultiKeyEncryptException' => $baseDir . '/../lib/Exceptions/MultiKeyEncryptException.php', 'OCA\\Encryption\\Exceptions\\PrivateKeyMissingException' => $baseDir . '/../lib/Exceptions/PrivateKeyMissingException.php', 'OCA\\Encryption\\Exceptions\\PublicKeyMissingException' => $baseDir . '/../lib/Exceptions/PublicKeyMissingException.php', - 'OCA\\Encryption\\HookManager' => $baseDir . '/../lib/HookManager.php', - 'OCA\\Encryption\\Hooks\\Contracts\\IHook' => $baseDir . '/../lib/Hooks/Contracts/IHook.php', - 'OCA\\Encryption\\Hooks\\UserHooks' => $baseDir . '/../lib/Hooks/UserHooks.php', 'OCA\\Encryption\\KeyManager' => $baseDir . '/../lib/KeyManager.php', + 'OCA\\Encryption\\Listeners\\UserEventsListener' => $baseDir . '/../lib/Listeners/UserEventsListener.php', 'OCA\\Encryption\\Migration\\SetMasterKeyStatus' => $baseDir . '/../lib/Migration/SetMasterKeyStatus.php', 'OCA\\Encryption\\Recovery' => $baseDir . '/../lib/Recovery.php', + 'OCA\\Encryption\\Services\\PassphraseService' => $baseDir . '/../lib/Services/PassphraseService.php', 'OCA\\Encryption\\Session' => $baseDir . '/../lib/Session.php', 'OCA\\Encryption\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php', 'OCA\\Encryption\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php', diff --git a/apps/encryption/composer/composer/autoload_static.php b/apps/encryption/composer/composer/autoload_static.php index 6c458eabddd1a..af5e51925205f 100644 --- a/apps/encryption/composer/composer/autoload_static.php +++ b/apps/encryption/composer/composer/autoload_static.php @@ -41,12 +41,11 @@ class ComposerStaticInitEncryption 'OCA\\Encryption\\Exceptions\\MultiKeyEncryptException' => __DIR__ . '/..' . '/../lib/Exceptions/MultiKeyEncryptException.php', 'OCA\\Encryption\\Exceptions\\PrivateKeyMissingException' => __DIR__ . '/..' . '/../lib/Exceptions/PrivateKeyMissingException.php', 'OCA\\Encryption\\Exceptions\\PublicKeyMissingException' => __DIR__ . '/..' . '/../lib/Exceptions/PublicKeyMissingException.php', - 'OCA\\Encryption\\HookManager' => __DIR__ . '/..' . '/../lib/HookManager.php', - 'OCA\\Encryption\\Hooks\\Contracts\\IHook' => __DIR__ . '/..' . '/../lib/Hooks/Contracts/IHook.php', - 'OCA\\Encryption\\Hooks\\UserHooks' => __DIR__ . '/..' . '/../lib/Hooks/UserHooks.php', 'OCA\\Encryption\\KeyManager' => __DIR__ . '/..' . '/../lib/KeyManager.php', + 'OCA\\Encryption\\Listeners\\UserEventsListener' => __DIR__ . '/..' . '/../lib/Listeners/UserEventsListener.php', 'OCA\\Encryption\\Migration\\SetMasterKeyStatus' => __DIR__ . '/..' . '/../lib/Migration/SetMasterKeyStatus.php', 'OCA\\Encryption\\Recovery' => __DIR__ . '/..' . '/../lib/Recovery.php', + 'OCA\\Encryption\\Services\\PassphraseService' => __DIR__ . '/..' . '/../lib/Services/PassphraseService.php', 'OCA\\Encryption\\Session' => __DIR__ . '/..' . '/../lib/Session.php', 'OCA\\Encryption\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php', 'OCA\\Encryption\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php', diff --git a/apps/encryption/lib/AppInfo/Application.php b/apps/encryption/lib/AppInfo/Application.php index d683c82286ac7..600bc30079714 100644 --- a/apps/encryption/lib/AppInfo/Application.php +++ b/apps/encryption/lib/AppInfo/Application.php @@ -7,14 +7,14 @@ */ namespace OCA\Encryption\AppInfo; +use OC\Core\Events\BeforePasswordResetEvent; +use OC\Core\Events\PasswordResetEvent; use OCA\Encryption\Crypto\Crypt; use OCA\Encryption\Crypto\DecryptAll; use OCA\Encryption\Crypto\EncryptAll; use OCA\Encryption\Crypto\Encryption; -use OCA\Encryption\HookManager; -use OCA\Encryption\Hooks\UserHooks; use OCA\Encryption\KeyManager; -use OCA\Encryption\Recovery; +use OCA\Encryption\Listeners\UserEventsListener; use OCA\Encryption\Session; use OCA\Encryption\Users\Setup; use OCA\Encryption\Util; @@ -23,7 +23,14 @@ use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\Encryption\IManager; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; +use OCP\IL10N; +use OCP\IUserSession; +use OCP\User\Events\BeforePasswordUpdatedEvent; +use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\UserCreatedEvent; +use OCP\User\Events\UserDeletedEvent; use Psr\Log\LoggerInterface; class Application extends App implements IBootstrap { @@ -49,7 +56,7 @@ public function boot(IBootContext $context): void { } $context->injectFn($this->registerEncryptionModule(...)); - $context->injectFn($this->registerHooks(...)); + $context->injectFn($this->registerEventListeners(...)); $context->injectFn($this->setUp(...)); }); } @@ -57,38 +64,29 @@ public function boot(IBootContext $context): void { public function setUp(IManager $encryptionManager) { if ($encryptionManager->isEnabled()) { /** @var Setup $setup */ - $setup = $this->getContainer()->query(Setup::class); + $setup = $this->getContainer()->get(Setup::class); $setup->setupSystem(); } } - /** - * register hooks - */ - public function registerHooks(IConfig $config) { - if (!$config->getSystemValueBool('maintenance')) { - $container = $this->getContainer(); - $server = $container->getServer(); - // Register our hooks and fire them. - $hookManager = new HookManager(); - - $hookManager->registerHook([ - new UserHooks($container->query(KeyManager::class), - $server->getUserManager(), - $server->get(LoggerInterface::class), - $container->query(Setup::class), - $server->getUserSession(), - $container->query(Util::class), - $container->query(Session::class), - $container->query(Crypt::class), - $container->query(Recovery::class)) - ]); + public function registerEventListeners(IConfig $config, IEventDispatcher $eventDispatcher, IManager $encryptionManager): void { + if (!$encryptionManager->isEnabled()) { + return; + } - $hookManager->fireHooks(); - } else { + if ($config->getSystemValueBool('maintenance')) { // Logout user if we are in maintenance to force re-login - $this->getContainer()->getServer()->getUserSession()->logout(); + $this->getContainer()->get(IUserSession::class)->logout(); + return; } + + // No maintenance so register all events + $eventDispatcher->addServiceListener(UserCreatedEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(UserDeletedEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(BeforePasswordUpdatedEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(PasswordUpdatedEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(BeforePasswordResetEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(PasswordResetEvent::class, UserEventsListener::class); } public function registerEncryptionModule(IManager $encryptionManager) { @@ -99,14 +97,14 @@ public function registerEncryptionModule(IManager $encryptionManager) { Encryption::DISPLAY_NAME, function () use ($container) { return new Encryption( - $container->query(Crypt::class), - $container->query(KeyManager::class), - $container->query(Util::class), - $container->query(Session::class), - $container->query(EncryptAll::class), - $container->query(DecryptAll::class), - $container->getServer()->get(LoggerInterface::class), - $container->getServer()->getL10N($container->getAppName()) + $container->get(Crypt::class), + $container->get(KeyManager::class), + $container->get(Util::class), + $container->get(Session::class), + $container->get(EncryptAll::class), + $container->get(DecryptAll::class), + $container->get(LoggerInterface::class), + $container->get(IL10N::class), ); }); } diff --git a/apps/encryption/lib/Crypto/Crypt.php b/apps/encryption/lib/Crypto/Crypt.php index b38734dd06139..463ca4e22bb21 100644 --- a/apps/encryption/lib/Crypto/Crypt.php +++ b/apps/encryption/lib/Crypto/Crypt.php @@ -83,7 +83,7 @@ public function __construct( /** * create new private/public key-pair for user * - * @return array|bool + * @return array{publicKey: string, privateKey: string}|false */ public function createKeyPair() { $res = $this->getOpenSSLPKey(); diff --git a/apps/encryption/lib/HookManager.php b/apps/encryption/lib/HookManager.php deleted file mode 100644 index 6ad56ebad782c..0000000000000 --- a/apps/encryption/lib/HookManager.php +++ /dev/null @@ -1,43 +0,0 @@ -hookInstances[] = $instance; - } - } elseif ($instances instanceof IHook) { - $this->hookInstances[] = $instances; - } - return true; - } - - public function fireHooks() { - foreach ($this->hookInstances as $instance) { - /** - * Fire off the add hooks method of each instance stored in cache - */ - $instance->addHooks(); - } - } -} diff --git a/apps/encryption/lib/Hooks/Contracts/IHook.php b/apps/encryption/lib/Hooks/Contracts/IHook.php deleted file mode 100644 index 5bb8046e2305c..0000000000000 --- a/apps/encryption/lib/Hooks/Contracts/IHook.php +++ /dev/null @@ -1,17 +0,0 @@ - - */ - protected static array $passwordResetUsers = []; - - public function __construct( - private KeyManager $keyManager, - private IUserManager $userManager, - private LoggerInterface $logger, - private Setup $userSetup, - private IUserSession $userSession, - private Util $util, - private Session $session, - private Crypt $crypt, - private Recovery $recovery, - ) { - } - - /** - * Connects Hooks - * - * @return null - */ - public function addHooks() { - OCUtil::connectHook('OC_User', 'post_login', $this, 'login'); - OCUtil::connectHook('OC_User', 'logout', $this, 'logout'); - - // this hooks only make sense if no master key is used - if ($this->util->isMasterKeyEnabled() === false) { - OCUtil::connectHook('OC_User', - 'post_setPassword', - $this, - 'setPassphrase'); - - OCUtil::connectHook('OC_User', - 'pre_setPassword', - $this, - 'preSetPassphrase'); - - OCUtil::connectHook('\OC\Core\LostPassword\Controller\LostController', - 'post_passwordReset', - $this, - 'postPasswordReset'); - - OCUtil::connectHook('\OC\Core\LostPassword\Controller\LostController', - 'pre_passwordReset', - $this, - 'prePasswordReset'); - - OCUtil::connectHook('OC_User', - 'post_createUser', - $this, - 'postCreateUser'); - - OCUtil::connectHook('OC_User', - 'post_deleteUser', - $this, - 'postDeleteUser'); - } - } - - - /** - * Startup encryption backend upon user login - * - * @note This method should never be called for users using client side encryption - * @param array $params - * @return boolean|null - */ - public function login($params) { - // ensure filesystem is loaded - if (!Filesystem::$loaded) { - $this->setupFS($params['uid']); - } - if ($this->util->isMasterKeyEnabled() === false) { - $this->userSetup->setupUser($params['uid'], $params['password']); - } - - $this->keyManager->init($params['uid'], $params['password']); - } - - /** - * remove keys from session during logout - */ - public function logout() { - $this->session->clear(); - } - - /** - * setup encryption backend upon user created - * - * @note This method should never be called for users using client side encryption - * @param array $params - */ - public function postCreateUser($params) { - $this->userSetup->setupUser($params['uid'], $params['password']); - } - - /** - * cleanup encryption backend upon user deleted - * - * @param array $params : uid, password - * @note This method should never be called for users using client side encryption - */ - public function postDeleteUser($params) { - $this->keyManager->deletePublicKey($params['uid']); - } - - public function prePasswordReset($params) { - $user = $params['uid']; - self::$passwordResetUsers[$user] = true; - } - - public function postPasswordReset($params) { - $uid = $params['uid']; - $password = $params['password']; - $this->keyManager->backupUserKeys('passwordReset', $uid); - $this->keyManager->deleteUserKeys($uid); - $this->userSetup->setupUser($uid, $password); - unset(self::$passwordResetUsers[$uid]); - } - - /** - * If the password can't be changed within Nextcloud, than update the key password in advance. - * - * @param array $params : uid, password - * @return boolean|null - */ - public function preSetPassphrase($params) { - $user = $this->userManager->get($params['uid']); - - if ($user && !$user->canChangePassword()) { - $this->setPassphrase($params); - } - } - - /** - * Change a user's encryption passphrase - * - * @param array $params keys: uid, password - * @return boolean|null - */ - public function setPassphrase($params) { - // if we are in the process to resetting a user password, we have nothing - // to do here - if (isset(self::$passwordResetUsers[$params['uid']])) { - return true; - } - - // Get existing decrypted private key - $user = $this->userSession->getUser(); - - // current logged in user changes their own password - if ($user && $params['uid'] === $user->getUID()) { - $privateKey = $this->session->getPrivateKey(); - - // Encrypt private key with new user pwd as passphrase - $encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $params['password'], $params['uid']); - - // Save private key - if ($encryptedPrivateKey) { - $this->keyManager->setPrivateKey($user->getUID(), - $this->crypt->generateHeader() . $encryptedPrivateKey); - } else { - $this->logger->error('Encryption could not update users encryption password'); - } - - // NOTE: Session does not need to be updated as the - // private key has not changed, only the passphrase - // used to decrypt it has changed - } else { // admin changed the password for a different user, create new keys and re-encrypt file keys - $userId = $params['uid']; - $this->initMountPoints($userId); - $recoveryPassword = $params['recoveryPassword'] ?? null; - - $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); - $recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId); - try { - $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $recoveryPassword); - } catch (\Exception $e) { - $decryptedRecoveryKey = false; - } - if ($decryptedRecoveryKey === false) { - $message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.'; - throw new GenericEncryptionException($message, $message); - } - - // we generate new keys if... - // ...we have a recovery password and the user enabled the recovery key - // ...encryption was activated for the first time (no keys exists) - // ...the user doesn't have any files - if ( - ($this->recovery->isRecoveryEnabledForUser($userId) && $recoveryPassword) - || !$this->keyManager->userHasKeys($userId) - || !$this->util->userHasFiles($userId) - ) { - // backup old keys - //$this->backupAllKeys('recovery'); - - $newUserPassword = $params['password']; - - $keyPair = $this->crypt->createKeyPair(); - - // Save public key - $this->keyManager->setPublicKey($userId, $keyPair['publicKey']); - - // Encrypt private key with new password - $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $newUserPassword, $userId); - - if ($encryptedKey) { - $this->keyManager->setPrivateKey($userId, $this->crypt->generateHeader() . $encryptedKey); - - if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files - $this->recovery->recoverUsersFiles($recoveryPassword, $userId); - } - } else { - $this->logger->error('Encryption Could not update users encryption password'); - } - } - } - } - - /** - * init mount points for given user - * - * @param string $user - * @throws \OC\User\NoUserException - */ - protected function initMountPoints($user) { - Filesystem::initMountPoints($user); - } - - /** - * setup file system for user - * - * @param string $uid user id - */ - protected function setupFS($uid) { - \OC_Util::setupFS($uid); - } -} diff --git a/apps/encryption/lib/KeyManager.php b/apps/encryption/lib/KeyManager.php index 0c9c02760a897..f694e6550f1f7 100644 --- a/apps/encryption/lib/KeyManager.php +++ b/apps/encryption/lib/KeyManager.php @@ -287,11 +287,9 @@ public function setShareKey($path, $uid, $key) { /** * Decrypt private key and store it * - * @param string $uid user id - * @param string $passPhrase users password * @return boolean */ - public function init($uid, $passPhrase) { + public function init(string $uid, ?string $passPhrase) { $this->session->setStatus(Session::INIT_EXECUTED); try { @@ -300,6 +298,10 @@ public function init($uid, $passPhrase) { $passPhrase = $this->getMasterKeyPassword(); $privateKey = $this->getSystemPrivateKey($uid); } else { + if ($passPhrase === null) { + $this->logger->warning('Master key is disabled but not passphrase provided.'); + return false; + } $privateKey = $this->getPrivateKey($uid); } $privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid); diff --git a/apps/encryption/lib/Listeners/UserEventsListener.php b/apps/encryption/lib/Listeners/UserEventsListener.php new file mode 100644 index 0000000000000..694640a0103ad --- /dev/null +++ b/apps/encryption/lib/Listeners/UserEventsListener.php @@ -0,0 +1,143 @@ + + */ +class UserEventsListener implements IEventListener { + + public function __construct( + private Util $util, + private Setup $userSetup, + private Session $session, + private KeyManager $keyManager, + private IUserManager $userManager, + private IUserSession $userSession, + private SetupManager $setupManager, + private PassphraseService $passphraseService, + ) { + } + + public function handle(Event $event): void { + if ($event instanceof UserCreatedEvent) { + $this->onUserCreated($event->getUid(), $event->getPassword()); + } elseif ($event instanceof UserDeletedEvent) { + $this->onUserDeleted($event->getUid()); + } elseif ($event instanceof UserLoggedInEvent) { + $this->onUserLogin($event->getUser(), $event->getPassword()); + } elseif ($event instanceof UserLoggedOutEvent) { + $this->onUserLogout(); + } elseif ($event instanceof BeforePasswordUpdatedEvent) { + $this->onBeforePasswordUpdated($event->getUser(), $event->getPassword(), $event->getRecoveryPassword()); + } elseif ($event instanceof PasswordUpdatedEvent) { + $this->onPasswordUpdated($event->getUid(), $event->getPassword(), $event->getRecoveryPassword()); + } elseif ($event instanceof BeforePasswordResetEvent) { + $this->onBeforePasswordReset($event->getUid()); + } elseif ($event instanceof PasswordResetEvent) { + $this->onPasswordReset($event->getUid(), $event->getPassword()); + } + } + + /** + * Startup encryption backend upon user login + */ + private function onUserLogin(IUser $user, ?string $password): void { + // ensure filesystem is loaded + $this->setupManager->setupForUser($user); + if ($this->util->isMasterKeyEnabled() === false) { + // Skip if no master key and the password is not provided + if ($password === null) { + return; + } + + $this->userSetup->setupUser($user->getUID(), $password); + } + + $this->keyManager->init($user->getUID(), $password); + } + + /** + * Remove keys from session during logout + */ + private function onUserLogout(): void { + $this->session->clear(); + } + + /** + * Setup encryption backend upon user created + * + * This method should never be called for users using client side encryption + */ + protected function onUserCreated(string $userId, string $password): void { + $this->userSetup->setupUser($userId, $password); + } + + /** + * Cleanup encryption backend upon user deleted + * + * This method should never be called for users using client side encryption + */ + protected function onUserDeleted(string $userId): void { + $this->keyManager->deletePublicKey($userId); + } + + /** + * If the password can't be changed within Nextcloud, than update the key password in advance. + */ + public function onBeforePasswordUpdated(IUser $user, string $password, ?string $recoveryPassword = null): void { + if (!$user->canChangePassword()) { + $this->passphraseService->setPassphraseForUser($user->getUID(), $password, $recoveryPassword); + } + } + + /** + * Change a user's encryption passphrase + */ + public function onPasswordUpdated(string $userId, string $password, ?string $recoveryPassword): void { + $this->passphraseService->setPassphraseForUser($userId, $password, $recoveryPassword); + } + + /** + * Set user password resetting state to allow ignoring "reset"-requests on password update + */ + public function onBeforePasswordReset(string $userId): void { + $this->passphraseService->setProcessingReset($userId); + } + + /** + * Create new encryption keys on password reset and backup the old one + */ + public function onPasswordReset(string $userId, string $password): void { + $this->keyManager->backupUserKeys('passwordReset', $userId); + $this->keyManager->deleteUserKeys($userId); + $this->userSetup->setupUser($userId, $password); + $this->passphraseService->setProcessingReset($userId, false); + } +} diff --git a/apps/encryption/lib/Services/PassphraseService.php b/apps/encryption/lib/Services/PassphraseService.php new file mode 100644 index 0000000000000..4b3e83375f5be --- /dev/null +++ b/apps/encryption/lib/Services/PassphraseService.php @@ -0,0 +1,142 @@ + */ + private static array $passwordResetUsers = []; + + public function __construct( + private Util $util, + private Crypt $crypt, + private Session $session, + private Recovery $recovery, + private KeyManager $keyManager, + private LoggerInterface $logger, + private IUserManager $userManager, + private IUserSession $userSession, + ) { + } + + public function setProcessingReset(string $uid, bool $processing = true): void { + if ($processing) { + self::$passwordResetUsers[$uid] = true; + } else { + unset(self::$passwordResetUsers[$uid]); + } + } + + /** + * Change a user's encryption passphrase + */ + public function setPassphraseForUser(string $userId, string $password, ?string $recoveryPassword = null): bool { + // if we are in the process to resetting a user password, we have nothing + // to do here + if (isset(self::$passwordResetUsers[$userId])) { + return true; + } + + // Check user exists on backend + $user = $this->userManager->get($userId); + if ($user === null) { + return false; + } + + // Get existing decrypted private key + $currentUser = $this->userSession->getUser(); + + // current logged in user changes his own password + if ($currentUser !== null && $userId === $currentUser->getUID()) { + $privateKey = $this->session->getPrivateKey(); + + // Encrypt private key with new user pwd as passphrase + $encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $password, $userId); + + // Save private key + if ($encryptedPrivateKey !== false) { + $key = $this->crypt->generateHeader() . $encryptedPrivateKey; + $this->keyManager->setPrivateKey($userId, $key); + return true; + } + + $this->logger->error('Encryption could not update users encryption password'); + + // NOTE: Session does not need to be updated as the + // private key has not changed, only the passphrase + // used to decrypt it has changed + } else { + // admin changed the password for a different user, create new keys and re-encrypt file keys + $recoveryPassword = $recoveryPassword ?? ''; + $this->initMountPoints($user); + + $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); + $recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId); + try { + $this->crypt->decryptPrivateKey($recoveryKey, $recoveryPassword); + } catch (\Exception) { + $message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.'; + throw new GenericEncryptionException($message, $message); + } + + // we generate new keys if... + // ...we have a recovery password and the user enabled the recovery key + // ...encryption was activated for the first time (no keys exists) + // ...the user doesn't have any files + if ( + ($this->recovery->isRecoveryEnabledForUser($userId) && $recoveryPassword !== '') + || !$this->keyManager->userHasKeys($userId) + || !$this->util->userHasFiles($userId) + ) { + $keyPair = $this->crypt->createKeyPair(); + if ($keyPair === false) { + $this->logger->error('Could not create new private key-pair for user.'); + return false; + } + + // Save public key + $this->keyManager->setPublicKey($userId, $keyPair['publicKey']); + + // Encrypt private key with new password + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $userId); + if ($encryptedKey === false) { + $this->logger->error('Encryption could not update users encryption password'); + return false; + } + + $this->keyManager->setPrivateKey($userId, $this->crypt->generateHeader() . $encryptedKey); + + if ($recoveryPassword !== '') { + // if recovery key is set we can re-encrypt the key files + $this->recovery->recoverUsersFiles($recoveryPassword, $userId); + } + return true; + } + } + return false; + } + + /** + * Init mount points for given user + */ + private function initMountPoints(IUser $user): void { + \OC\Files\Filesystem::initMountPoints($user); + } +} diff --git a/apps/encryption/lib/Users/Setup.php b/apps/encryption/lib/Users/Setup.php index 30e7c5461ccb6..f2189d6dab25a 100644 --- a/apps/encryption/lib/Users/Setup.php +++ b/apps/encryption/lib/Users/Setup.php @@ -11,15 +11,11 @@ use OCA\Encryption\KeyManager; class Setup { - /** @var Crypt */ - private $crypt; - /** @var KeyManager */ - private $keyManager; - public function __construct(Crypt $crypt, - KeyManager $keyManager) { - $this->crypt = $crypt; - $this->keyManager = $keyManager; + public function __construct( + private Crypt $crypt, + private KeyManager $keyManager, + ) { } /** diff --git a/apps/encryption/lib/Util.php b/apps/encryption/lib/Util.php index 6ca4d2c1e1e8d..61656ddcda352 100644 --- a/apps/encryption/lib/Util.php +++ b/apps/encryption/lib/Util.php @@ -25,11 +25,7 @@ public function __construct( private IConfig $config, private IUserManager $userManager, ) { - $this->files = $files; - $this->crypt = $crypt; $this->user = $userSession->isLoggedIn() ? $userSession->getUser() : false; - $this->config = $config; - $this->userManager = $userManager; } /** @@ -140,4 +136,5 @@ public function getOwner($path) { public function getStorage($path) { return $this->files->getMount($path)->getStorage(); } + } diff --git a/apps/encryption/tests/HookManagerTest.php b/apps/encryption/tests/HookManagerTest.php deleted file mode 100644 index f831690580f14..0000000000000 --- a/apps/encryption/tests/HookManagerTest.php +++ /dev/null @@ -1,52 +0,0 @@ -registerHook([ - $this->getMockBuilder(IHook::class)->disableOriginalConstructor()->getMock(), - $this->getMockBuilder(IHook::class)->disableOriginalConstructor()->getMock(), - $this->createMock(IConfig::class) - ]); - - $hookInstances = self::invokePrivate(self::$instance, 'hookInstances'); - // Make sure our type checking works - $this->assertCount(2, $hookInstances); - } - - - - public static function setUpBeforeClass(): void { - parent::setUpBeforeClass(); - // have to make instance static to preserve data between tests - self::$instance = new HookManager(); - } - - - public function testRegisterHooksWithInstance(): void { - $mock = $this->getMockBuilder(IHook::class)->disableOriginalConstructor()->getMock(); - /** @var IHook $mock */ - self::$instance->registerHook($mock); - - $hookInstances = self::invokePrivate(self::$instance, 'hookInstances'); - $this->assertCount(3, $hookInstances); - } -} diff --git a/apps/encryption/tests/Hooks/UserHooksTest.php b/apps/encryption/tests/Hooks/UserHooksTest.php deleted file mode 100644 index 072a20de846d4..0000000000000 --- a/apps/encryption/tests/Hooks/UserHooksTest.php +++ /dev/null @@ -1,370 +0,0 @@ - 'testUser', 'password' => 'password']; - - public function testLogin(): void { - $this->userSetupMock->expects($this->once()) - ->method('setupUser') - ->willReturnOnConsecutiveCalls(true, false); - - $this->keyManagerMock->expects($this->once()) - ->method('init') - ->with('testUser', 'password'); - - $this->assertNull($this->instance->login($this->params)); - } - - public function testLogout(): void { - $this->sessionMock->expects($this->once()) - ->method('clear'); - $this->instance->logout(); - $this->addToAssertionCount(1); - } - - public function testPostCreateUser(): void { - $this->userSetupMock->expects($this->once()) - ->method('setupUser'); - - $this->instance->postCreateUser($this->params); - $this->addToAssertionCount(1); - } - - public function testPostDeleteUser(): void { - $this->keyManagerMock->expects($this->once()) - ->method('deletePublicKey') - ->with('testUser'); - - $this->instance->postDeleteUser($this->params); - $this->addToAssertionCount(1); - } - - public function testPrePasswordReset(): void { - $params = ['uid' => 'user1']; - $expected = ['user1' => true]; - $this->instance->prePasswordReset($params); - $passwordResetUsers = $this->invokePrivate($this->instance, 'passwordResetUsers'); - - $this->assertSame($expected, $passwordResetUsers); - } - - public function testPostPasswordReset(): void { - $params = ['uid' => 'user1', 'password' => 'password']; - $this->invokePrivate($this->instance, 'passwordResetUsers', [['user1' => true]]); - $this->keyManagerMock->expects($this->once())->method('backupUserKeys') - ->with('passwordReset', 'user1'); - $this->keyManagerMock->expects($this->once())->method('deleteUserKeys') - ->with('user1'); - $this->userSetupMock->expects($this->once())->method('setupUser') - ->with('user1', 'password'); - - $this->instance->postPasswordReset($params); - $passwordResetUsers = $this->invokePrivate($this->instance, 'passwordResetUsers'); - $this->assertEmpty($passwordResetUsers); - } - - /** - * @dataProvider dataTestPreSetPassphrase - */ - public function testPreSetPassphrase($canChange): void { - /** @var UserHooks | \PHPUnit\Framework\MockObject\MockObject $instance */ - $instance = $this->getMockBuilder(UserHooks::class) - ->setConstructorArgs( - [ - $this->keyManagerMock, - $this->userManagerMock, - $this->loggerMock, - $this->userSetupMock, - $this->userSessionMock, - $this->utilMock, - $this->sessionMock, - $this->cryptMock, - $this->recoveryMock - ] - ) - ->setMethods(['setPassphrase']) - ->getMock(); - - $userMock = $this->createMock(IUser::class); - - $this->userManagerMock->expects($this->once()) - ->method('get') - ->with($this->params['uid']) - ->willReturn($userMock); - $userMock->expects($this->once()) - ->method('canChangePassword') - ->willReturn($canChange); - - if ($canChange) { - // in this case the password will be changed in the post hook - $instance->expects($this->never())->method('setPassphrase'); - } else { - // if user can't change the password we update the encryption - // key password already in the pre hook - $instance->expects($this->once()) - ->method('setPassphrase') - ->with($this->params); - } - - $instance->preSetPassphrase($this->params); - } - - public function dataTestPreSetPassphrase() { - return [ - [true], - [false] - ]; - } - - public function XtestSetPassphrase() { - $this->sessionMock->expects($this->once()) - ->method('getPrivateKey') - ->willReturn(true); - - $this->cryptMock->expects($this->exactly(4)) - ->method('encryptPrivateKey') - ->willReturn(true); - - $this->cryptMock->expects($this->any()) - ->method('generateHeader') - ->willReturn(Crypt::HEADER_START . ':Cipher:test:' . Crypt::HEADER_END); - - $this->keyManagerMock->expects($this->exactly(4)) - ->method('setPrivateKey') - ->willReturnCallback(function ($user, $key): void { - $header = substr($key, 0, strlen(Crypt::HEADER_START)); - $this->assertSame( - Crypt::HEADER_START, - $header, 'every encrypted file should start with a header'); - }); - - $this->assertNull($this->instance->setPassphrase($this->params)); - $this->params['recoveryPassword'] = 'password'; - - $this->recoveryMock->expects($this->exactly(3)) - ->method('isRecoveryEnabledForUser') - ->with('testUser1') - ->willReturnOnConsecutiveCalls(true, false); - - - $this->instance = $this->getMockBuilder(UserHooks::class) - ->setConstructorArgs( - [ - $this->keyManagerMock, - $this->userManagerMock, - $this->loggerMock, - $this->userSetupMock, - $this->userSessionMock, - $this->utilMock, - $this->sessionMock, - $this->cryptMock, - $this->recoveryMock - ] - )->setMethods(['initMountPoints'])->getMock(); - - $this->instance->expects($this->exactly(3))->method('initMountPoints'); - - $this->params['uid'] = 'testUser1'; - - // Test first if statement - $this->assertNull($this->instance->setPassphrase($this->params)); - - // Test Second if conditional - $this->keyManagerMock->expects($this->exactly(2)) - ->method('userHasKeys') - ->with('testUser1') - ->willReturn(true); - - $this->assertNull($this->instance->setPassphrase($this->params)); - - // Test third and final if condition - $this->utilMock->expects($this->once()) - ->method('userHasFiles') - ->with('testUser1') - ->willReturn(false); - - $this->cryptMock->expects($this->once()) - ->method('createKeyPair'); - - $this->keyManagerMock->expects($this->once()) - ->method('setPrivateKey'); - - $this->recoveryMock->expects($this->once()) - ->method('recoverUsersFiles') - ->with('password', 'testUser1'); - - $this->assertNull($this->instance->setPassphrase($this->params)); - } - - public function testSetPassphraseResetUserMode(): void { - $params = ['uid' => 'user1', 'password' => 'password']; - $this->invokePrivate($this->instance, 'passwordResetUsers', [[$params['uid'] => true]]); - $this->sessionMock->expects($this->never())->method('getPrivateKey'); - $this->keyManagerMock->expects($this->never())->method('setPrivateKey'); - $this->assertTrue($this->instance->setPassphrase($params)); - $this->invokePrivate($this->instance, 'passwordResetUsers', [[]]); - } - - public function XtestSetPasswordNoUser() { - $userSessionMock = $this->getMockBuilder(IUserSession::class) - ->disableOriginalConstructor() - ->getMock(); - - $userSessionMock->expects($this->any())->method('getUser')->willReturn(null); - - $this->recoveryMock->expects($this->once()) - ->method('isRecoveryEnabledForUser') - ->with('testUser') - ->willReturn(false); - - $userHooks = $this->getMockBuilder(UserHooks::class) - ->setConstructorArgs( - [ - $this->keyManagerMock, - $this->userManagerMock, - $this->loggerMock, - $this->userSetupMock, - $userSessionMock, - $this->utilMock, - $this->sessionMock, - $this->cryptMock, - $this->recoveryMock - ] - )->setMethods(['initMountPoints'])->getMock(); - - /** @var UserHooks $userHooks */ - $this->assertNull($userHooks->setPassphrase($this->params)); - } - - protected function setUp(): void { - parent::setUp(); - $this->loggerMock = $this->createMock(LoggerInterface::class); - $this->keyManagerMock = $this->getMockBuilder(KeyManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userManagerMock = $this->getMockBuilder(IUserManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userSetupMock = $this->getMockBuilder(Setup::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->user = $this->createMock(IUser::class); - $this->user->expects($this->any()) - ->method('getUID') - ->willReturn('testUser'); - - $this->userSessionMock = $this->createMock(IUserSession::class); - $this->userSessionMock->expects($this->any()) - ->method('getUser') - ->willReturn($this->user); - - $utilMock = $this->getMockBuilder(Util::class) - ->disableOriginalConstructor() - ->getMock(); - - $sessionMock = $this->getMockBuilder(Session::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->cryptMock = $this->getMockBuilder(Crypt::class) - ->disableOriginalConstructor() - ->getMock(); - $recoveryMock = $this->getMockBuilder(Recovery::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->sessionMock = $sessionMock; - $this->recoveryMock = $recoveryMock; - $this->utilMock = $utilMock; - $this->utilMock->expects($this->any())->method('isMasterKeyEnabled')->willReturn(false); - - $this->instance = $this->getMockBuilder(UserHooks::class) - ->setConstructorArgs( - [ - $this->keyManagerMock, - $this->userManagerMock, - $this->loggerMock, - $this->userSetupMock, - $this->userSessionMock, - $this->utilMock, - $this->sessionMock, - $this->cryptMock, - $this->recoveryMock - ] - )->setMethods(['setupFS'])->getMock(); - } -} diff --git a/apps/encryption/tests/Listeners/UserEventsListenersTest.php b/apps/encryption/tests/Listeners/UserEventsListenersTest.php new file mode 100644 index 0000000000000..cb31523f105f8 --- /dev/null +++ b/apps/encryption/tests/Listeners/UserEventsListenersTest.php @@ -0,0 +1,258 @@ +util = $this->createMock(Util::class); + $this->userSetup = $this->createMock(Setup::class); + $this->session = $this->createMock(Session::class); + $this->keyManager = $this->createMock(KeyManager::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->setupManager = $this->createMock(SetupManager::class); + $this->passphraseService = $this->createMock(PassphraseService::class); + + $this->instance = new UserEventsListener( + $this->util, + $this->userSetup, + $this->session, + $this->keyManager, + $this->userManager, + $this->userSession, + $this->setupManager, + $this->passphraseService, + ); + } + + public function testLogin(): void { + $this->userSetup->expects(self::once()) + ->method('setupUser') + ->willReturn(true); + + $this->keyManager->expects(self::once()) + ->method('init') + ->with('testUser', 'password'); + + $this->util->method('isMasterKeyEnabled')->willReturn(false); + + $user = $this->createMock(IUser::class); + $user->expects(self::any()) + ->method('getUID') + ->willReturn('testUser'); + $event = $this->createMock(UserLoggedInEvent::class); + $event->expects(self::atLeastOnce()) + ->method('getUser') + ->willReturn($user); + $event->expects(self::atLeastOnce()) + ->method('getPassword') + ->willReturn('password'); + + $this->instance->handle($event); + } + + public function testLoginMasterKey(): void { + $this->util->method('isMasterKeyEnabled')->willReturn(true); + + $this->userSetup->expects(self::never()) + ->method('setupUser'); + + $this->keyManager->expects(self::once()) + ->method('init') + ->with('testUser', 'password'); + + $user = $this->createMock(IUser::class); + $user->expects(self::any()) + ->method('getUID') + ->willReturn('testUser'); + + $event = $this->createMock(UserLoggedInEvent::class); + $event->expects(self::atLeastOnce()) + ->method('getUser') + ->willReturn($user); + $event->expects(self::atLeastOnce()) + ->method('getPassword') + ->willReturn('password'); + + $this->instance->handle($event); + } + + public function testLogout(): void { + $this->session->expects(self::once()) + ->method('clear'); + + $event = $this->createMock(UserLoggedOutEvent::class); + $this->instance->handle($event); + } + + public function testUserCreated(): void { + $this->userSetup->expects(self::once()) + ->method('setupUser') + ->with('testUser', 'password'); + + $event = $this->createMock(UserCreatedEvent::class); + $event->expects(self::atLeastOnce()) + ->method('getUid') + ->willReturn('testUser'); + $event->expects(self::atLeastOnce()) + ->method('getPassword') + ->willReturn('password'); + + $this->instance->handle($event); + } + + public function testUserDeleted(): void { + $this->keyManager->expects(self::once()) + ->method('deletePublicKey') + ->with('testUser'); + + $event = $this->createMock(UserDeletedEvent::class); + $event->expects(self::atLeastOnce()) + ->method('getUid') + ->willReturn('testUser'); + $this->instance->handle($event); + } + + public function testBeforePasswordUpdated(): void { + $this->passphraseService->expects(self::never()) + ->method('setPassphraseForUser'); + + $user = $this->createMock(IUser::class); + $user->expects(self::atLeastOnce()) + ->method('canChangePassword') + ->willReturn(true); + + $event = $this->createMock(BeforePasswordUpdatedEvent::class); + $event->expects(self::atLeastOnce()) + ->method('getUser') + ->willReturn($user); + $event->expects(self::atLeastOnce()) + ->method('getPassword') + ->willReturn('password'); + $this->instance->handle($event); + } + + public function testBeforePasswordUpdated_CannotChangePassword(): void { + $this->passphraseService->expects(self::once()) + ->method('setPassphraseForUser') + ->with('testUser', 'password'); + + $user = $this->createMock(IUser::class); + $user->expects(self::atLeastOnce()) + ->method('getUID') + ->willReturn('testUser'); + $user->expects(self::atLeastOnce()) + ->method('canChangePassword') + ->willReturn(false); + + $event = $this->createMock(BeforePasswordUpdatedEvent::class); + $event->expects(self::atLeastOnce()) + ->method('getUser') + ->willReturn($user); + $event->expects(self::atLeastOnce()) + ->method('getPassword') + ->willReturn('password'); + $this->instance->handle($event); + } + + public function testPasswordUpdated(): void { + $this->passphraseService->expects(self::once()) + ->method('setPassphraseForUser') + ->with('testUser', 'password'); + + $event = $this->createMock(PasswordUpdatedEvent::class); + $event->expects(self::atLeastOnce()) + ->method('getUid') + ->willReturn('testUser'); + $event->expects(self::atLeastOnce()) + ->method('getPassword') + ->willReturn('password'); + + $this->instance->handle($event); + } + + public function testBeforePasswordReset(): void { + $this->passphraseService->expects(self::once()) + ->method('setProcessingReset') + ->with('testUser'); + + $event = $this->createMock(BeforePasswordResetEvent::class); + $event->expects(self::atLeastOnce()) + ->method('getUid') + ->willReturn('testUser'); + $this->instance->handle($event); + } + + public function testPasswordReset(): void { + // backup required + $this->keyManager->expects(self::once()) + ->method('backupUserKeys') + ->with('passwordReset', 'testUser'); + // delete old keys + $this->keyManager->expects(self::once()) + ->method('deleteUserKeys') + ->with('testUser'); + // create new keys + $this->userSetup->expects(self::once()) + ->method('setupUser') + ->with('testUser', 'password'); + // reset ends + $this->passphraseService->expects(self::once()) + ->method('setProcessingReset') + ->with('testUser', false); + + $event = $this->createMock(PasswordResetEvent::class); + $event->expects(self::atLeastOnce()) + ->method('getUid') + ->willReturn('testUser'); + $event->expects(self::atLeastOnce()) + ->method('getPassword') + ->willReturn('password'); + $this->instance->handle($event); + } + +} diff --git a/apps/encryption/tests/PassphraseServiceTest.php b/apps/encryption/tests/PassphraseServiceTest.php new file mode 100644 index 0000000000000..c2dc9d8173c77 --- /dev/null +++ b/apps/encryption/tests/PassphraseServiceTest.php @@ -0,0 +1,196 @@ +util = $this->createMock(Util::class); + $this->crypt = $this->createMock(Crypt::class); + $this->session = $this->createMock(Session::class); + $this->recovery = $this->createMock(Recovery::class); + $this->keyManager = $this->createMock(KeyManager::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->userSession = $this->createMock(IUserSession::class); + + $this->instance = new PassphraseService( + $this->util, + $this->crypt, + $this->session, + $this->recovery, + $this->keyManager, + $this->createMock(LoggerInterface::class), + $this->userManager, + $this->userSession, + ); + } + + public function testSetProcessingReset(): void { + $this->instance->setProcessingReset('userId'); + $this->assertEquals(['userId' => true], $this->invokePrivate($this->instance, 'passwordResetUsers')); + } + + public function testUnsetProcessingReset(): void { + $this->instance->setProcessingReset('userId'); + $this->assertEquals(['userId' => true], $this->invokePrivate($this->instance, 'passwordResetUsers')); + $this->instance->setProcessingReset('userId', false); + $this->assertEquals([], $this->invokePrivate($this->instance, 'passwordResetUsers')); + } + + /** + * Check that the passphrase setting skips if a reset is processed + */ + public function testSetPassphraseResetUserMode(): void { + $this->session->expects(self::never()) + ->method('getPrivateKey'); + $this->keyManager->expects(self::never()) + ->method('setPrivateKey'); + + $this->instance->setProcessingReset('userId'); + $this->assertTrue($this->instance->setPassphraseForUser('userId', 'password')); + } + + public function testSetPassphrase_currentUser() { + $instance = $this->getMockBuilder(PassphraseService::class) + ->onlyMethods(['initMountPoints']) + ->setConstructorArgs([ + $this->util, + $this->crypt, + $this->session, + $this->recovery, + $this->keyManager, + $this->createMock(LoggerInterface::class), + $this->userManager, + $this->userSession, + ]) + ->getMock(); + + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('testUser'); + $this->userSession->expects(self::atLeastOnce()) + ->method('getUser') + ->willReturn($user); + $this->userManager->expects(self::atLeastOnce()) + ->method('get') + ->with('testUser') + ->willReturn($user); + $this->session->expects(self::any()) + ->method('getPrivateKey') + ->willReturn('private-key'); + $this->crypt->expects(self::any()) + ->method('encryptPrivateKey') + ->with('private-key') + ->willReturn('encrypted-key'); + $this->crypt->expects(self::any()) + ->method('generateHeader') + ->willReturn('crypt-header: '); + + $this->keyManager->expects(self::atLeastOnce()) + ->method('setPrivateKey') + ->with('testUser', 'crypt-header: encrypted-key'); + + $this->assertTrue($instance->setPassphraseForUser('testUser', 'password')); + } + + public function testSetPassphrase_currentUserFails() { + $instance = $this->getMockBuilder(PassphraseService::class) + ->onlyMethods(['initMountPoints']) + ->setConstructorArgs([ + $this->util, + $this->crypt, + $this->session, + $this->recovery, + $this->keyManager, + $this->createMock(LoggerInterface::class), + $this->userManager, + $this->userSession, + ]) + ->getMock(); + + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('testUser'); + $this->userManager->expects(self::atLeastOnce()) + ->method('get') + ->with('testUser') + ->willReturn($user); + $this->userSession->expects(self::atLeastOnce()) + ->method('getUser') + ->willReturn($user); + $this->session->expects(self::any()) + ->method('getPrivateKey') + ->willReturn('private-key'); + $this->crypt->expects(self::any()) + ->method('encryptPrivateKey') + ->with('private-key') + ->willReturn(false); + + $this->keyManager->expects(self::never()) + ->method('setPrivateKey'); + + $this->assertFalse($instance->setPassphraseForUser('testUser', 'password')); + } + + public function testSetPassphrase_currentUserNotExists() { + $instance = $this->getMockBuilder(PassphraseService::class) + ->onlyMethods(['initMountPoints']) + ->setConstructorArgs([ + $this->util, + $this->crypt, + $this->session, + $this->recovery, + $this->keyManager, + $this->createMock(LoggerInterface::class), + $this->userManager, + $this->userSession, + ]) + ->getMock(); + + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('testUser'); + $this->userManager->expects(self::atLeastOnce()) + ->method('get') + ->with('testUser') + ->willReturn(null); + $this->userSession->expects(self::never()) + ->method('getUser'); + $this->keyManager->expects(self::never()) + ->method('setPrivateKey'); + + $this->assertFalse($instance->setPassphraseForUser('testUser', 'password')); + } + +} diff --git a/core/Events/BeforePasswordResetEvent.php b/core/Events/BeforePasswordResetEvent.php index bd437479150c3..428ebfeefadc4 100644 --- a/core/Events/BeforePasswordResetEvent.php +++ b/core/Events/BeforePasswordResetEvent.php @@ -34,6 +34,13 @@ public function getUser(): IUser { return $this->user; } + /** + * @since 31.0.0 + */ + public function getUid(): string { + return $this->user->getUID(); + } + /** * @since 25.0.0 */ diff --git a/core/Events/PasswordResetEvent.php b/core/Events/PasswordResetEvent.php index a159c71026332..868899513212f 100644 --- a/core/Events/PasswordResetEvent.php +++ b/core/Events/PasswordResetEvent.php @@ -34,6 +34,13 @@ public function getUser(): IUser { return $this->user; } + /** + * @since 31.0.0 + */ + public function getUid(): string { + return $this->user->getUID(); + } + /** * @since 25.0.0 */ diff --git a/lib/private/Log/ExceptionSerializer.php b/lib/private/Log/ExceptionSerializer.php index 25e52e66f39e9..904107f6998fb 100644 --- a/lib/private/Log/ExceptionSerializer.php +++ b/lib/private/Log/ExceptionSerializer.php @@ -14,8 +14,9 @@ use OCA\Encryption\Controller\SettingsController; use OCA\Encryption\Crypto\Crypt; use OCA\Encryption\Crypto\Encryption; -use OCA\Encryption\Hooks\UserHooks; use OCA\Encryption\KeyManager; +use OCA\Encryption\Listeners\UserEventsListener; +use OCA\Encryption\Services\PassphraseService; use OCA\Encryption\Session; use OCP\HintException; @@ -169,14 +170,16 @@ public function __construct( \OCA\Encryption\Users\Setup::class => [ 'setupUser', ], - UserHooks::class => [ - 'login', - 'postCreateUser', - 'postDeleteUser', - 'prePasswordReset', - 'postPasswordReset', - 'preSetPassphrase', - 'setPassphrase', + UserEventsListener::class => [ + 'handle', + 'onUserCreated', + 'onUserLogin', + 'onBeforePasswordUpdated', + 'onPasswordUpdated', + 'onPasswordReset', + ], + PassphraseService::class => [ + 'setPassphraseForUser', ], ]; diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php index 57e76f268d986..b9e5413e5c260 100644 --- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php +++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php @@ -69,7 +69,7 @@ public function registerDashboardWidget(string $widgetClass): void; * @param string $name * @param callable $factory * @psalm-param callable(\Psr\Container\ContainerInterface): mixed $factory - * @param bool $shared + * @param bool $shared If set to true the factory result will be cached otherwise every query will call the factory again * * @return void * @see IContainer::registerService() diff --git a/lib/public/User/Events/PasswordUpdatedEvent.php b/lib/public/User/Events/PasswordUpdatedEvent.php index 948b69e54b742..d3fd8f078fed2 100644 --- a/lib/public/User/Events/PasswordUpdatedEvent.php +++ b/lib/public/User/Events/PasswordUpdatedEvent.php @@ -49,6 +49,13 @@ public function getUser(): IUser { return $this->user; } + /** + * @since 31.0.0 + */ + public function getUid(): string { + return $this->user->getUID(); + } + /** * @return string * @since 18.0.0 diff --git a/lib/public/User/Events/UserDeletedEvent.php b/lib/public/User/Events/UserDeletedEvent.php index c5b8e11c888fa..9b74ff960630c 100644 --- a/lib/public/User/Events/UserDeletedEvent.php +++ b/lib/public/User/Events/UserDeletedEvent.php @@ -34,4 +34,11 @@ public function __construct(IUser $user) { public function getUser(): IUser { return $this->user; } + + /** + * @since 31.0.0 + */ + public function getUid(): string { + return $this->user->getUID(); + } } diff --git a/lib/public/User/Events/UserLoggedInEvent.php b/lib/public/User/Events/UserLoggedInEvent.php index 61a7b1f79cdba..53f61073a5966 100644 --- a/lib/public/User/Events/UserLoggedInEvent.php +++ b/lib/public/User/Events/UserLoggedInEvent.php @@ -45,6 +45,13 @@ public function getUser(): IUser { return $this->user; } + /** + * @since 31.0.0 + */ + public function getUid(): string { + return $this->user->getUID(); + } + /** * @since 21.0.0 */