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>pull/48332/head
parent
cd3dc1719b
commit
f3aa004b1c
@ -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); |
||||
} |
||||
} |
||||
@ -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); |
||||
} |
||||
} |
||||
@ -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); |
||||
} |
||||
} |
||||
@ -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(); |
||||
} |
||||
} |
||||
@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -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')); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue