mirror of
https://github.com/nextcloud/server.git
synced 2026-02-19 02:38:40 -05:00
refactor(encryption): Migrate away from Hooks to typed events
Co-authored-by: Ferdinand Thiessen <opensource@fthiessen.de> Co-authored-by: Louis <louis@chmn.me> Co-authored-by: Côme Chilliet <91878298+come-nc@users.noreply.github.com> Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
cd3dc1719b
commit
f3aa004b1c
17 changed files with 803 additions and 818 deletions
|
|
@ -26,12 +26,11 @@ return array(
|
|||
'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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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\IBootContext;
|
|||
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 @@ class Application extends App implements IBootstrap {
|
|||
}
|
||||
|
||||
$context->injectFn($this->registerEncryptionModule(...));
|
||||
$context->injectFn($this->registerHooks(...));
|
||||
$context->injectFn($this->registerEventListeners(...));
|
||||
$context->injectFn($this->setUp(...));
|
||||
});
|
||||
}
|
||||
|
|
@ -57,38 +64,29 @@ class Application extends App implements IBootstrap {
|
|||
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))
|
||||
]);
|
||||
|
||||
$hookManager->fireHooks();
|
||||
} else {
|
||||
// Logout user if we are in maintenance to force re-login
|
||||
$this->getContainer()->getServer()->getUserSession()->logout();
|
||||
public function registerEventListeners(IConfig $config, IEventDispatcher $eventDispatcher, IManager $encryptionManager): void {
|
||||
if (!$encryptionManager->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($config->getSystemValueBool('maintenance')) {
|
||||
// Logout user if we are in maintenance to force re-login
|
||||
$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 @@ class Application extends App implements IBootstrap {
|
|||
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),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class Crypt {
|
|||
/**
|
||||
* create new private/public key-pair for user
|
||||
*
|
||||
* @return array|bool
|
||||
* @return array{publicKey: string, privateKey: string}|false
|
||||
*/
|
||||
public function createKeyPair() {
|
||||
$res = $this->getOpenSSLPKey();
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OCA\Encryption;
|
||||
|
||||
use OCA\Encryption\Hooks\Contracts\IHook;
|
||||
|
||||
class HookManager {
|
||||
/** @var IHook[] */
|
||||
private $hookInstances = [];
|
||||
|
||||
/**
|
||||
* @param array|IHook $instances
|
||||
* - This accepts either a single instance of IHook or an array of instances of IHook
|
||||
* @return bool
|
||||
*/
|
||||
public function registerHook($instances) {
|
||||
if (is_array($instances)) {
|
||||
foreach ($instances as $instance) {
|
||||
if (!$instance instanceof IHook) {
|
||||
return false;
|
||||
}
|
||||
$this->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OCA\Encryption\Hooks\Contracts;
|
||||
|
||||
interface IHook {
|
||||
/**
|
||||
* Connects Hooks
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function addHooks();
|
||||
}
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OCA\Encryption\Hooks;
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OCA\Encryption\Crypto\Crypt;
|
||||
use OCA\Encryption\Hooks\Contracts\IHook;
|
||||
use OCA\Encryption\KeyManager;
|
||||
use OCA\Encryption\Recovery;
|
||||
use OCA\Encryption\Session;
|
||||
use OCA\Encryption\Users\Setup;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Util as OCUtil;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class UserHooks implements IHook {
|
||||
/**
|
||||
* list of user for which we perform a password reset
|
||||
* @var array<string, true>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -287,11 +287,9 @@ class KeyManager {
|
|||
/**
|
||||
* 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 @@ class KeyManager {
|
|||
$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);
|
||||
|
|
|
|||
143
apps/encryption/lib/Listeners/UserEventsListener.php
Normal file
143
apps/encryption/lib/Listeners/UserEventsListener.php
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Encryption\Listeners;
|
||||
|
||||
use OC\Core\Events\BeforePasswordResetEvent;
|
||||
use OC\Core\Events\PasswordResetEvent;
|
||||
use OC\Files\SetupManager;
|
||||
use OCA\Encryption\KeyManager;
|
||||
use OCA\Encryption\Services\PassphraseService;
|
||||
use OCA\Encryption\Session;
|
||||
use OCA\Encryption\Users\Setup;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\User\Events\BeforePasswordUpdatedEvent;
|
||||
use OCP\User\Events\PasswordUpdatedEvent;
|
||||
use OCP\User\Events\UserCreatedEvent;
|
||||
use OCP\User\Events\UserDeletedEvent;
|
||||
use OCP\User\Events\UserLoggedInEvent;
|
||||
use OCP\User\Events\UserLoggedOutEvent;
|
||||
|
||||
/**
|
||||
* @template-implements IEventListener<UserCreatedEvent|UserDeletedEvent|UserLoggedInEvent|UserLoggedOutEvent|BeforePasswordUpdatedEvent|PasswordUpdatedEvent|BeforePasswordResetEvent|PasswordResetEvent>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
142
apps/encryption/lib/Services/PassphraseService.php
Normal file
142
apps/encryption/lib/Services/PassphraseService.php
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Encryption\Services;
|
||||
|
||||
use OCA\Encryption\Crypto\Crypt;
|
||||
use OCA\Encryption\KeyManager;
|
||||
use OCA\Encryption\Recovery;
|
||||
use OCA\Encryption\Session;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class PassphraseService {
|
||||
|
||||
/** @var array<string, bool> */
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,15 +11,11 @@ use OCA\Encryption\Crypto\Crypt;
|
|||
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,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -25,11 +25,7 @@ class Util {
|
|||
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 @@ class Util {
|
|||
public function getStorage($path) {
|
||||
return $this->files->getMount($path)->getStorage();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OCA\Encryption\Tests;
|
||||
|
||||
use OCA\Encryption\HookManager;
|
||||
use OCA\Encryption\Hooks\Contracts\IHook;
|
||||
use OCP\IConfig;
|
||||
use Test\TestCase;
|
||||
|
||||
class HookManagerTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @var HookManager
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
|
||||
public function testRegisterHookWithArray(): void {
|
||||
self::$instance->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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,370 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OCA\Encryption\Tests\Hooks;
|
||||
|
||||
use OCA\Encryption\Crypto\Crypt;
|
||||
use OCA\Encryption\Hooks\UserHooks;
|
||||
use OCA\Encryption\KeyManager;
|
||||
use OCA\Encryption\Recovery;
|
||||
use OCA\Encryption\Session;
|
||||
use OCA\Encryption\Users\Setup;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
/**
|
||||
* Class UserHooksTest
|
||||
*
|
||||
* @group DB
|
||||
* @package OCA\Encryption\Tests\Hooks
|
||||
*/
|
||||
class UserHooksTest extends TestCase {
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $utilMock;
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $recoveryMock;
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $sessionMock;
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $keyManagerMock;
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $userManagerMock;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $userSetupMock;
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $userSessionMock;
|
||||
/**
|
||||
* @var MockObject|IUser
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $cryptMock;
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $loggerMock;
|
||||
/**
|
||||
* @var UserHooks
|
||||
*/
|
||||
private $instance;
|
||||
|
||||
private $params = ['uid' => '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();
|
||||
}
|
||||
}
|
||||
258
apps/encryption/tests/Listeners/UserEventsListenersTest.php
Normal file
258
apps/encryption/tests/Listeners/UserEventsListenersTest.php
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\Encryption\Tests\Listeners;
|
||||
|
||||
use OC\Core\Events\BeforePasswordResetEvent;
|
||||
use OC\Core\Events\PasswordResetEvent;
|
||||
use OC\Files\SetupManager;
|
||||
use OCA\Encryption\KeyManager;
|
||||
use OCA\Encryption\Listeners\UserEventsListener;
|
||||
use OCA\Encryption\Services\PassphraseService;
|
||||
use OCA\Encryption\Session;
|
||||
use OCA\Encryption\Users\Setup;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\User\Events\BeforePasswordUpdatedEvent;
|
||||
use OCP\User\Events\PasswordUpdatedEvent;
|
||||
use OCP\User\Events\UserCreatedEvent;
|
||||
use OCP\User\Events\UserDeletedEvent;
|
||||
use OCP\User\Events\UserLoggedInEvent;
|
||||
use OCP\User\Events\UserLoggedOutEvent;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
/**
|
||||
* @group DB
|
||||
*/
|
||||
class UserEventsListenersTest extends TestCase {
|
||||
|
||||
protected Util&MockObject $util;
|
||||
protected Setup&MockObject $userSetup;
|
||||
protected Session&MockObject $session;
|
||||
protected KeyManager&MockObject $keyManager;
|
||||
protected IUserManager&MockObject $userManager;
|
||||
protected IUserSession&MockObject $userSession;
|
||||
protected SetupManager&MockObject $setupManager;
|
||||
protected PassphraseService&MockObject $passphraseService;
|
||||
|
||||
protected UserEventsListener $instance;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->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);
|
||||
}
|
||||
|
||||
}
|
||||
196
apps/encryption/tests/PassphraseServiceTest.php
Normal file
196
apps/encryption/tests/PassphraseServiceTest.php
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\Encryption\Tests;
|
||||
|
||||
use OCA\Encryption\Crypto\Crypt;
|
||||
use OCA\Encryption\KeyManager;
|
||||
use OCA\Encryption\Recovery;
|
||||
use OCA\Encryption\Services\PassphraseService;
|
||||
use OCA\Encryption\Session;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
/**
|
||||
* @group DB
|
||||
*/
|
||||
class PassphraseServiceTest extends TestCase {
|
||||
|
||||
protected Util&MockObject $util;
|
||||
protected Crypt&MockObject $crypt;
|
||||
protected Session&MockObject $session;
|
||||
protected Recovery&MockObject $recovery;
|
||||
protected KeyManager&MockObject $keyManager;
|
||||
protected IUserManager&MockObject $userManager;
|
||||
protected IUserSession&MockObject $userSession;
|
||||
|
||||
protected PassphraseService $instance;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->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'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,8 +14,9 @@ use OCA\Encryption\Controller\RecoveryController;
|
|||
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 @@ class ExceptionSerializer {
|
|||
\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',
|
||||
],
|
||||
];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue