hooks.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Sam Tuke
  6. * @copyright 2012 Sam Tuke samtuke@owncloud.org
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10. * License as published by the Free Software Foundation; either
  11. * version 3 of the License, or any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public
  19. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. namespace OCA\Encryption;
  23. use OC\Files\Filesystem;
  24. /**
  25. * Class for hook specific logic
  26. */
  27. class Hooks {
  28. /**
  29. * @brief Startup encryption backend upon user login
  30. * @note This method should never be called for users using client side encryption
  31. */
  32. public static function login($params) {
  33. if (\OCP\App::isEnabled('files_encryption') === false) {
  34. return true;
  35. }
  36. $l = new \OC_L10N('files_encryption');
  37. $view = new \OC_FilesystemView('/');
  38. // ensure filesystem is loaded
  39. if(!\OC\Files\Filesystem::$loaded) {
  40. \OC_Util::setupFS($params['uid']);
  41. }
  42. $privateKey = \OCA\Encryption\Keymanager::getPrivateKey($view, $params['uid']);
  43. // if no private key exists, check server configuration
  44. if(!$privateKey) {
  45. //check if all requirements are met
  46. if(!Helper::checkRequirements() || !Helper::checkConfiguration()) {
  47. $error_msg = $l->t("Missing requirements.");
  48. $hint = $l->t('Please make sure that PHP 5.3.3 or newer is installed and that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled.');
  49. \OC_App::disable('files_encryption');
  50. \OCP\Util::writeLog('Encryption library', $error_msg . ' ' . $hint, \OCP\Util::ERROR);
  51. \OCP\Template::printErrorPage($error_msg, $hint);
  52. }
  53. }
  54. $util = new Util($view, $params['uid']);
  55. // setup user, if user not ready force relogin
  56. if (Helper::setupUser($util, $params['password']) === false) {
  57. return false;
  58. }
  59. $session = $util->initEncryption($params);
  60. // Check if first-run file migration has already been performed
  61. $ready = false;
  62. if ($util->getMigrationStatus() === Util::MIGRATION_OPEN) {
  63. $ready = $util->beginMigration();
  64. }
  65. // If migration not yet done
  66. if ($ready) {
  67. $userView = new \OC_FilesystemView('/' . $params['uid']);
  68. // Set legacy encryption key if it exists, to support
  69. // depreciated encryption system
  70. if (
  71. $userView->file_exists('encryption.key')
  72. && $encLegacyKey = $userView->file_get_contents('encryption.key')
  73. ) {
  74. $plainLegacyKey = Crypt::legacyDecrypt($encLegacyKey, $params['password']);
  75. $session->setLegacyKey($plainLegacyKey);
  76. }
  77. // Encrypt existing user files:
  78. if (
  79. $util->encryptAll('/' . $params['uid'] . '/' . 'files', $session->getLegacyKey(), $params['password'])
  80. ) {
  81. \OC_Log::write(
  82. 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed'
  83. , \OC_Log::INFO
  84. );
  85. }
  86. // Register successful migration in DB
  87. $util->finishMigration();
  88. }
  89. return true;
  90. }
  91. /**
  92. * @brief setup encryption backend upon user created
  93. * @note This method should never be called for users using client side encryption
  94. */
  95. public static function postCreateUser($params) {
  96. if (\OCP\App::isEnabled('files_encryption')) {
  97. $view = new \OC_FilesystemView('/');
  98. $util = new Util($view, $params['uid']);
  99. Helper::setupUser($util, $params['password']);
  100. }
  101. }
  102. /**
  103. * @brief cleanup encryption backend upon user deleted
  104. * @note This method should never be called for users using client side encryption
  105. */
  106. public static function postDeleteUser($params) {
  107. if (\OCP\App::isEnabled('files_encryption')) {
  108. $view = new \OC_FilesystemView('/');
  109. // cleanup public key
  110. $publicKey = '/public-keys/' . $params['uid'] . '.public.key';
  111. // Disable encryption proxy to prevent recursive calls
  112. $proxyStatus = \OC_FileProxy::$enabled;
  113. \OC_FileProxy::$enabled = false;
  114. $view->unlink($publicKey);
  115. \OC_FileProxy::$enabled = $proxyStatus;
  116. }
  117. }
  118. /**
  119. * @brief If the password can't be changed within ownCloud, than update the key password in advance.
  120. */
  121. public static function preSetPassphrase($params) {
  122. if (\OCP\App::isEnabled('files_encryption')) {
  123. if ( ! \OC_User::canUserChangePassword($params['uid']) ) {
  124. self::setPassphrase($params);
  125. }
  126. }
  127. }
  128. /**
  129. * @brief Change a user's encryption passphrase
  130. * @param array $params keys: uid, password
  131. */
  132. public static function setPassphrase($params) {
  133. if (\OCP\App::isEnabled('files_encryption') === false) {
  134. return true;
  135. }
  136. // Only attempt to change passphrase if server-side encryption
  137. // is in use (client-side encryption does not have access to
  138. // the necessary keys)
  139. if (Crypt::mode() === 'server') {
  140. if ($params['uid'] === \OCP\User::getUser()) {
  141. $view = new \OC_FilesystemView('/');
  142. $session = new \OCA\Encryption\Session($view);
  143. // Get existing decrypted private key
  144. $privateKey = $session->getPrivateKey();
  145. // Encrypt private key with new user pwd as passphrase
  146. $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password']);
  147. // Save private key
  148. Keymanager::setPrivateKey($encryptedPrivateKey);
  149. // NOTE: Session does not need to be updated as the
  150. // private key has not changed, only the passphrase
  151. // used to decrypt it has changed
  152. } else { // admin changed the password for a different user, create new keys and reencrypt file keys
  153. $user = $params['uid'];
  154. $recoveryPassword = $params['recoveryPassword'];
  155. $newUserPassword = $params['password'];
  156. $view = new \OC_FilesystemView('/');
  157. // make sure that the users home is mounted
  158. \OC\Files\Filesystem::initMountPoints($user);
  159. $keypair = Crypt::createKeypair();
  160. // Disable encryption proxy to prevent recursive calls
  161. $proxyStatus = \OC_FileProxy::$enabled;
  162. \OC_FileProxy::$enabled = false;
  163. // Save public key
  164. $view->file_put_contents('/public-keys/' . $user . '.public.key', $keypair['publicKey']);
  165. // Encrypt private key empty passphrase
  166. $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword);
  167. // Save private key
  168. $view->file_put_contents(
  169. '/' . $user . '/files_encryption/' . $user . '.private.key', $encryptedPrivateKey);
  170. if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
  171. $util = new Util($view, $user);
  172. $util->recoverUsersFiles($recoveryPassword);
  173. }
  174. \OC_FileProxy::$enabled = $proxyStatus;
  175. }
  176. }
  177. }
  178. /*
  179. * @brief check if files can be encrypted to every user.
  180. */
  181. /**
  182. * @param $params
  183. */
  184. public static function preShared($params) {
  185. if (\OCP\App::isEnabled('files_encryption') === false) {
  186. return true;
  187. }
  188. $l = new \OC_L10N('files_encryption');
  189. $users = array();
  190. $view = new \OC\Files\View('/public-keys/');
  191. switch ($params['shareType']) {
  192. case \OCP\Share::SHARE_TYPE_USER:
  193. $users[] = $params['shareWith'];
  194. break;
  195. case \OCP\Share::SHARE_TYPE_GROUP:
  196. $users = \OC_Group::usersInGroup($params['shareWith']);
  197. break;
  198. }
  199. $notConfigured = array();
  200. foreach ($users as $user) {
  201. if (!$view->file_exists($user . '.public.key')) {
  202. $notConfigured[] = $user;
  203. }
  204. }
  205. if (count($notConfigured) > 0) {
  206. $params['run'] = false;
  207. $params['error'] = $l->t('Following users are not set up for encryption:') . ' ' . join(', ' , $notConfigured);
  208. }
  209. }
  210. /**
  211. * @brief
  212. */
  213. public static function postShared($params) {
  214. // NOTE: $params has keys:
  215. // [itemType] => file
  216. // itemSource -> int, filecache file ID
  217. // [parent] =>
  218. // [itemTarget] => /13
  219. // shareWith -> string, uid of user being shared to
  220. // fileTarget -> path of file being shared
  221. // uidOwner -> owner of the original file being shared
  222. // [shareType] => 0
  223. // [shareWith] => test1
  224. // [uidOwner] => admin
  225. // [permissions] => 17
  226. // [fileSource] => 13
  227. // [fileTarget] => /test8
  228. // [id] => 10
  229. // [token] =>
  230. // [run] => whether emitting script should continue to run
  231. // TODO: Should other kinds of item be encrypted too?
  232. if (\OCP\App::isEnabled('files_encryption') === false) {
  233. return true;
  234. }
  235. if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
  236. $view = new \OC_FilesystemView('/');
  237. $session = new \OCA\Encryption\Session($view);
  238. $userId = \OCP\User::getUser();
  239. $util = new Util($view, $userId);
  240. $path = $util->fileIdToPath($params['itemSource']);
  241. $share = $util->getParentFromShare($params['id']);
  242. //if parent is set, then this is a re-share action
  243. if ($share['parent'] !== null) {
  244. // get the parent from current share
  245. $parent = $util->getShareParent($params['parent']);
  246. // if parent is file the it is an 1:1 share
  247. if ($parent['item_type'] === 'file') {
  248. // prefix path with Shared
  249. $path = '/Shared' . $parent['file_target'];
  250. } else {
  251. // NOTE: parent is folder but shared was a file!
  252. // we try to rebuild the missing path
  253. // some examples we face here
  254. // user1 share folder1 with user2 folder1 has
  255. // the following structure
  256. // /folder1/subfolder1/subsubfolder1/somefile.txt
  257. // user2 re-share subfolder2 with user3
  258. // user3 re-share somefile.txt user4
  259. // so our path should be
  260. // /Shared/subfolder1/subsubfolder1/somefile.txt
  261. // while user3 is sharing
  262. if ($params['itemType'] === 'file') {
  263. // get target path
  264. $targetPath = $util->fileIdToPath($params['fileSource']);
  265. $targetPathSplit = array_reverse(explode('/', $targetPath));
  266. // init values
  267. $path = '';
  268. $sharedPart = ltrim($parent['file_target'], '/');
  269. // rebuild path
  270. foreach ($targetPathSplit as $pathPart) {
  271. if ($pathPart !== $sharedPart) {
  272. $path = '/' . $pathPart . $path;
  273. } else {
  274. break;
  275. }
  276. }
  277. // prefix path with Shared
  278. $path = '/Shared' . $parent['file_target'] . $path;
  279. } else {
  280. // prefix path with Shared
  281. $path = '/Shared' . $parent['file_target'] . $params['fileTarget'];
  282. }
  283. }
  284. }
  285. $sharingEnabled = \OCP\Share::isEnabled();
  286. // get the path including mount point only if not a shared folder
  287. if (strncmp($path, '/Shared', strlen('/Shared') !== 0)) {
  288. // get path including the the storage mount point
  289. $path = $util->getPathWithMountPoint($params['itemSource']);
  290. }
  291. // if a folder was shared, get a list of all (sub-)folders
  292. if ($params['itemType'] === 'folder') {
  293. $allFiles = $util->getAllFiles($path);
  294. } else {
  295. $allFiles = array($path);
  296. }
  297. foreach ($allFiles as $path) {
  298. $usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
  299. $util->setSharedFileKeyfiles($session, $usersSharing, $path);
  300. }
  301. }
  302. }
  303. /**
  304. * @brief
  305. */
  306. public static function postUnshare($params) {
  307. // NOTE: $params has keys:
  308. // [itemType] => file
  309. // [itemSource] => 13
  310. // [shareType] => 0
  311. // [shareWith] => test1
  312. // [itemParent] =>
  313. if (\OCP\App::isEnabled('files_encryption') === false) {
  314. return true;
  315. }
  316. if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
  317. $view = new \OC_FilesystemView('/');
  318. $userId = \OCP\User::getUser();
  319. $util = new Util($view, $userId);
  320. $path = $util->fileIdToPath($params['itemSource']);
  321. // check if this is a re-share
  322. if ($params['itemParent']) {
  323. // get the parent from current share
  324. $parent = $util->getShareParent($params['itemParent']);
  325. // get target path
  326. $targetPath = $util->fileIdToPath($params['itemSource']);
  327. $targetPathSplit = array_reverse(explode('/', $targetPath));
  328. // init values
  329. $path = '';
  330. $sharedPart = ltrim($parent['file_target'], '/');
  331. // rebuild path
  332. foreach ($targetPathSplit as $pathPart) {
  333. if ($pathPart !== $sharedPart) {
  334. $path = '/' . $pathPart . $path;
  335. } else {
  336. break;
  337. }
  338. }
  339. // prefix path with Shared
  340. $path = '/Shared' . $parent['file_target'] . $path;
  341. }
  342. // for group shares get a list of the group members
  343. if ($params['shareType'] === \OCP\Share::SHARE_TYPE_GROUP) {
  344. $userIds = \OC_Group::usersInGroup($params['shareWith']);
  345. } else {
  346. if ($params['shareType'] === \OCP\Share::SHARE_TYPE_LINK) {
  347. $userIds = array($util->getPublicShareKeyId());
  348. } else {
  349. $userIds = array($params['shareWith']);
  350. }
  351. }
  352. // get the path including mount point only if not a shared folder
  353. if (strncmp($path, '/Shared', strlen('/Shared') !== 0)) {
  354. // get path including the the storage mount point
  355. $path = $util->getPathWithMountPoint($params['itemSource']);
  356. }
  357. // if we unshare a folder we need a list of all (sub-)files
  358. if ($params['itemType'] === 'folder') {
  359. $allFiles = $util->getAllFiles($path);
  360. } else {
  361. $allFiles = array($path);
  362. }
  363. foreach ($allFiles as $path) {
  364. // check if the user still has access to the file, otherwise delete share key
  365. $sharingUsers = $util->getSharingUsersArray(true, $path);
  366. // Unshare every user who no longer has access to the file
  367. $delUsers = array_diff($userIds, $sharingUsers);
  368. // delete share key
  369. Keymanager::delShareKey($view, $delUsers, $path);
  370. }
  371. }
  372. }
  373. /**
  374. * @brief after a file is renamed, rename its keyfile and share-keys also fix the file size and fix also the sharing
  375. * @param array with oldpath and newpath
  376. *
  377. * This function is connected to the rename signal of OC_Filesystem and adjust the name and location
  378. * of the stored versions along the actual file
  379. */
  380. public static function postRename($params) {
  381. if (\OCP\App::isEnabled('files_encryption') === false) {
  382. return true;
  383. }
  384. // Disable encryption proxy to prevent recursive calls
  385. $proxyStatus = \OC_FileProxy::$enabled;
  386. \OC_FileProxy::$enabled = false;
  387. $view = new \OC_FilesystemView('/');
  388. $session = new \OCA\Encryption\Session($view);
  389. $userId = \OCP\User::getUser();
  390. $util = new Util($view, $userId);
  391. // Format paths to be relative to user files dir
  392. if ($util->isSystemWideMountPoint($params['oldpath'])) {
  393. $baseDir = 'files_encryption/';
  394. $oldKeyfilePath = $baseDir . 'keyfiles/' . $params['oldpath'];
  395. } else {
  396. $baseDir = $userId . '/' . 'files_encryption/';
  397. $oldKeyfilePath = $baseDir . 'keyfiles/' . $params['oldpath'];
  398. }
  399. if ($util->isSystemWideMountPoint($params['newpath'])) {
  400. $newKeyfilePath = $baseDir . 'keyfiles/' . $params['newpath'];
  401. } else {
  402. $newKeyfilePath = $baseDir . 'keyfiles/' . $params['newpath'];
  403. }
  404. // add key ext if this is not an folder
  405. if (!$view->is_dir($oldKeyfilePath)) {
  406. $oldKeyfilePath .= '.key';
  407. $newKeyfilePath .= '.key';
  408. // handle share-keys
  409. $localKeyPath = $view->getLocalFile($baseDir . 'share-keys/' . $params['oldpath']);
  410. $escapedPath = Helper::escapeGlobPattern($localKeyPath);
  411. $matches = glob($escapedPath . '*.shareKey');
  412. foreach ($matches as $src) {
  413. $dst = \OC\Files\Filesystem::normalizePath(str_replace($params['oldpath'], $params['newpath'], $src));
  414. // create destination folder if not exists
  415. if (!file_exists(dirname($dst))) {
  416. mkdir(dirname($dst), 0750, true);
  417. }
  418. rename($src, $dst);
  419. }
  420. } else {
  421. // handle share-keys folders
  422. $oldShareKeyfilePath = $baseDir . 'share-keys/' . $params['oldpath'];
  423. $newShareKeyfilePath = $baseDir . 'share-keys/' . $params['newpath'];
  424. // create destination folder if not exists
  425. if (!$view->file_exists(dirname($newShareKeyfilePath))) {
  426. $view->mkdir(dirname($newShareKeyfilePath), 0750, true);
  427. }
  428. $view->rename($oldShareKeyfilePath, $newShareKeyfilePath);
  429. }
  430. // Rename keyfile so it isn't orphaned
  431. if ($view->file_exists($oldKeyfilePath)) {
  432. // create destination folder if not exists
  433. if (!$view->file_exists(dirname($newKeyfilePath))) {
  434. $view->mkdir(dirname($newKeyfilePath), 0750, true);
  435. }
  436. $view->rename($oldKeyfilePath, $newKeyfilePath);
  437. }
  438. // build the path to the file
  439. $newPath = '/' . $userId . '/files' . $params['newpath'];
  440. $newPathRelative = $params['newpath'];
  441. if ($util->fixFileSize($newPath)) {
  442. // get sharing app state
  443. $sharingEnabled = \OCP\Share::isEnabled();
  444. // get users
  445. $usersSharing = $util->getSharingUsersArray($sharingEnabled, $newPathRelative);
  446. // update sharing-keys
  447. $util->setSharedFileKeyfiles($session, $usersSharing, $newPathRelative);
  448. }
  449. \OC_FileProxy::$enabled = $proxyStatus;
  450. }
  451. /**
  452. * set migration status and the init status back to '0' so that all new files get encrypted
  453. * if the app gets enabled again
  454. * @param array $params contains the app ID
  455. */
  456. public static function preDisable($params) {
  457. if ($params['app'] === 'files_encryption') {
  458. $setMigrationStatus = \OC_DB::prepare('UPDATE `*PREFIX*encryption` SET `migration_status`=0');
  459. $setMigrationStatus->execute();
  460. $session = new \OCA\Encryption\Session(new \OC\Files\View('/'));
  461. $session->setInitialized(\OCA\Encryption\Session::NOT_INITIALIZED);
  462. }
  463. }
  464. /**
  465. * set the init status to 'NOT_INITIALIZED' (0) if the app gets enabled
  466. * @param array $params contains the app ID
  467. */
  468. public static function postEnable($params) {
  469. if ($params['app'] === 'files_encryption') {
  470. $session = new \OCA\Encryption\Session(new \OC\Files\View('/'));
  471. $session->setInitialized(\OCA\Encryption\Session::NOT_INITIALIZED);
  472. }
  473. }
  474. }