hooks.php 17 KB

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