UserHooks.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\Encryption\Hooks;
  8. use OC\Files\Filesystem;
  9. use OCA\Encryption\Crypto\Crypt;
  10. use OCA\Encryption\Hooks\Contracts\IHook;
  11. use OCA\Encryption\KeyManager;
  12. use OCA\Encryption\Recovery;
  13. use OCA\Encryption\Session;
  14. use OCA\Encryption\Users\Setup;
  15. use OCA\Encryption\Util;
  16. use OCP\Encryption\Exceptions\GenericEncryptionException;
  17. use OCP\IUserManager;
  18. use OCP\IUserSession;
  19. use OCP\Util as OCUtil;
  20. use Psr\Log\LoggerInterface;
  21. class UserHooks implements IHook {
  22. /**
  23. * list of user for which we perform a password reset
  24. * @var array<string, true>
  25. */
  26. protected static array $passwordResetUsers = [];
  27. public function __construct(
  28. private KeyManager $keyManager,
  29. private IUserManager $userManager,
  30. private LoggerInterface $logger,
  31. private Setup $userSetup,
  32. private IUserSession $userSession,
  33. private Util $util,
  34. private Session $session,
  35. private Crypt $crypt,
  36. private Recovery $recovery,
  37. ) {
  38. }
  39. /**
  40. * Connects Hooks
  41. *
  42. * @return null
  43. */
  44. public function addHooks() {
  45. OCUtil::connectHook('OC_User', 'post_login', $this, 'login');
  46. OCUtil::connectHook('OC_User', 'logout', $this, 'logout');
  47. // this hooks only make sense if no master key is used
  48. if ($this->util->isMasterKeyEnabled() === false) {
  49. OCUtil::connectHook('OC_User',
  50. 'post_setPassword',
  51. $this,
  52. 'setPassphrase');
  53. OCUtil::connectHook('OC_User',
  54. 'pre_setPassword',
  55. $this,
  56. 'preSetPassphrase');
  57. OCUtil::connectHook('\OC\Core\LostPassword\Controller\LostController',
  58. 'post_passwordReset',
  59. $this,
  60. 'postPasswordReset');
  61. OCUtil::connectHook('\OC\Core\LostPassword\Controller\LostController',
  62. 'pre_passwordReset',
  63. $this,
  64. 'prePasswordReset');
  65. OCUtil::connectHook('OC_User',
  66. 'post_createUser',
  67. $this,
  68. 'postCreateUser');
  69. OCUtil::connectHook('OC_User',
  70. 'post_deleteUser',
  71. $this,
  72. 'postDeleteUser');
  73. }
  74. }
  75. /**
  76. * Startup encryption backend upon user login
  77. *
  78. * @note This method should never be called for users using client side encryption
  79. * @param array $params
  80. * @return boolean|null
  81. */
  82. public function login($params) {
  83. // ensure filesystem is loaded
  84. if (!\OC\Files\Filesystem::$loaded) {
  85. $this->setupFS($params['uid']);
  86. }
  87. if ($this->util->isMasterKeyEnabled() === false) {
  88. $this->userSetup->setupUser($params['uid'], $params['password']);
  89. }
  90. $this->keyManager->init($params['uid'], $params['password']);
  91. }
  92. /**
  93. * remove keys from session during logout
  94. */
  95. public function logout() {
  96. $this->session->clear();
  97. }
  98. /**
  99. * setup encryption backend upon user created
  100. *
  101. * @note This method should never be called for users using client side encryption
  102. * @param array $params
  103. */
  104. public function postCreateUser($params) {
  105. $this->userSetup->setupUser($params['uid'], $params['password']);
  106. }
  107. /**
  108. * cleanup encryption backend upon user deleted
  109. *
  110. * @param array $params : uid, password
  111. * @note This method should never be called for users using client side encryption
  112. */
  113. public function postDeleteUser($params) {
  114. $this->keyManager->deletePublicKey($params['uid']);
  115. }
  116. public function prePasswordReset($params) {
  117. $user = $params['uid'];
  118. self::$passwordResetUsers[$user] = true;
  119. }
  120. public function postPasswordReset($params) {
  121. $uid = $params['uid'];
  122. $password = $params['password'];
  123. $this->keyManager->backupUserKeys('passwordReset', $uid);
  124. $this->keyManager->deleteUserKeys($uid);
  125. $this->userSetup->setupUser($uid, $password);
  126. unset(self::$passwordResetUsers[$uid]);
  127. }
  128. /**
  129. * If the password can't be changed within Nextcloud, than update the key password in advance.
  130. *
  131. * @param array $params : uid, password
  132. * @return boolean|null
  133. */
  134. public function preSetPassphrase($params) {
  135. $user = $this->userManager->get($params['uid']);
  136. if ($user && !$user->canChangePassword()) {
  137. $this->setPassphrase($params);
  138. }
  139. }
  140. /**
  141. * Change a user's encryption passphrase
  142. *
  143. * @param array $params keys: uid, password
  144. * @return boolean|null
  145. */
  146. public function setPassphrase($params) {
  147. // if we are in the process to resetting a user password, we have nothing
  148. // to do here
  149. if (isset(self::$passwordResetUsers[$params['uid']])) {
  150. return true;
  151. }
  152. // Get existing decrypted private key
  153. $user = $this->userSession->getUser();
  154. // current logged in user changes his own password
  155. if ($user && $params['uid'] === $user->getUID()) {
  156. $privateKey = $this->session->getPrivateKey();
  157. // Encrypt private key with new user pwd as passphrase
  158. $encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $params['password'], $params['uid']);
  159. // Save private key
  160. if ($encryptedPrivateKey) {
  161. $this->keyManager->setPrivateKey($user->getUID(),
  162. $this->crypt->generateHeader() . $encryptedPrivateKey);
  163. } else {
  164. $this->logger->error('Encryption could not update users encryption password');
  165. }
  166. // NOTE: Session does not need to be updated as the
  167. // private key has not changed, only the passphrase
  168. // used to decrypt it has changed
  169. } else { // admin changed the password for a different user, create new keys and re-encrypt file keys
  170. $userId = $params['uid'];
  171. $this->initMountPoints($userId);
  172. $recoveryPassword = $params['recoveryPassword'] ?? null;
  173. $recoveryKeyId = $this->keyManager->getRecoveryKeyId();
  174. $recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId);
  175. try {
  176. $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $recoveryPassword);
  177. } catch (\Exception $e) {
  178. $decryptedRecoveryKey = false;
  179. }
  180. if ($decryptedRecoveryKey === false) {
  181. $message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.';
  182. throw new GenericEncryptionException($message, $message);
  183. }
  184. // we generate new keys if...
  185. // ...we have a recovery password and the user enabled the recovery key
  186. // ...encryption was activated for the first time (no keys exists)
  187. // ...the user doesn't have any files
  188. if (
  189. ($this->recovery->isRecoveryEnabledForUser($userId) && $recoveryPassword)
  190. || !$this->keyManager->userHasKeys($userId)
  191. || !$this->util->userHasFiles($userId)
  192. ) {
  193. // backup old keys
  194. //$this->backupAllKeys('recovery');
  195. $newUserPassword = $params['password'];
  196. $keyPair = $this->crypt->createKeyPair();
  197. // Save public key
  198. $this->keyManager->setPublicKey($userId, $keyPair['publicKey']);
  199. // Encrypt private key with new password
  200. $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $newUserPassword, $userId);
  201. if ($encryptedKey) {
  202. $this->keyManager->setPrivateKey($userId, $this->crypt->generateHeader() . $encryptedKey);
  203. if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
  204. $this->recovery->recoverUsersFiles($recoveryPassword, $userId);
  205. }
  206. } else {
  207. $this->logger->error('Encryption Could not update users encryption password');
  208. }
  209. }
  210. }
  211. }
  212. /**
  213. * init mount points for given user
  214. *
  215. * @param string $user
  216. * @throws \OC\User\NoUserException
  217. */
  218. protected function initMountPoints($user) {
  219. Filesystem::initMountPoints($user);
  220. }
  221. /**
  222. * setup file system for user
  223. *
  224. * @param string $uid user id
  225. */
  226. protected function setupFS($uid) {
  227. \OC_Util::setupFS($uid);
  228. }
  229. }