util.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. <?php
  2. /**
  3. * @author Björn Schießle <schiessle@owncloud.com>
  4. * @author Thomas Müller <thomas.mueller@tmit.eu>
  5. *
  6. * @copyright Copyright (c) 2015, ownCloud, Inc.
  7. * @license AGPL-3.0
  8. *
  9. * This code is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License, version 3,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program 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 License, version 3,
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>
  20. *
  21. */
  22. namespace OC\Encryption;
  23. use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException;
  24. use OC\Encryption\Exceptions\EncryptionHeaderToLargeException;
  25. use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
  26. use OC\Files\Filesystem;
  27. use OC\Files\View;
  28. use OCP\Encryption\IEncryptionModule;
  29. use OCP\IConfig;
  30. class Util {
  31. const HEADER_START = 'HBEGIN';
  32. const HEADER_END = 'HEND';
  33. const HEADER_PADDING_CHAR = '-';
  34. const HEADER_ENCRYPTION_MODULE_KEY = 'oc_encryption_module';
  35. /**
  36. * block size will always be 8192 for a PHP stream
  37. * @see https://bugs.php.net/bug.php?id=21641
  38. * @var integer
  39. */
  40. protected $headerSize = 8192;
  41. /**
  42. * block size will always be 8192 for a PHP stream
  43. * @see https://bugs.php.net/bug.php?id=21641
  44. * @var integer
  45. */
  46. protected $blockSize = 8192;
  47. /** @var View */
  48. protected $view;
  49. /** @var array */
  50. protected $ocHeaderKeys;
  51. /** @var \OC\User\Manager */
  52. protected $userManager;
  53. /** @var IConfig */
  54. protected $config;
  55. /** @var array paths excluded from encryption */
  56. protected $excludedPaths;
  57. /** @var \OC\Group\Manager $manager */
  58. protected $groupManager;
  59. /**
  60. *
  61. * @param \OC\Files\View $view
  62. * @param \OC\User\Manager $userManager
  63. * @param \OC\Group\Manager $groupManager
  64. * @param IConfig $config
  65. */
  66. public function __construct(
  67. \OC\Files\View $view,
  68. \OC\User\Manager $userManager,
  69. \OC\Group\Manager $groupManager,
  70. IConfig $config) {
  71. $this->ocHeaderKeys = [
  72. self::HEADER_ENCRYPTION_MODULE_KEY
  73. ];
  74. $this->view = $view;
  75. $this->userManager = $userManager;
  76. $this->groupManager = $groupManager;
  77. $this->config = $config;
  78. $this->excludedPaths[] = 'files_encryption';
  79. }
  80. /**
  81. * read encryption module ID from header
  82. *
  83. * @param array $header
  84. * @return string
  85. * @throws ModuleDoesNotExistsException
  86. */
  87. public function getEncryptionModuleId(array $header = null) {
  88. $id = '';
  89. $encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY;
  90. if (isset($header[$encryptionModuleKey])) {
  91. $id = $header[$encryptionModuleKey];
  92. } elseif (isset($header['cipher'])) {
  93. if (class_exists('\OCA\Encryption\Crypto\Encryption')) {
  94. // fall back to default encryption if the user migrated from
  95. // ownCloud <= 8.0 with the old encryption
  96. $id = \OCA\Encryption\Crypto\Encryption::ID;
  97. } else {
  98. throw new ModuleDoesNotExistsException('ownCloud default encryption module missing');
  99. }
  100. }
  101. return $id;
  102. }
  103. /**
  104. * read header into array
  105. *
  106. * @param string $header
  107. * @return array
  108. */
  109. public function readHeader($header) {
  110. $result = array();
  111. if (substr($header, 0, strlen(self::HEADER_START)) === self::HEADER_START) {
  112. $endAt = strpos($header, self::HEADER_END);
  113. if ($endAt !== false) {
  114. $header = substr($header, 0, $endAt + strlen(self::HEADER_END));
  115. // +1 to not start with an ':' which would result in empty element at the beginning
  116. $exploded = explode(':', substr($header, strlen(self::HEADER_START)+1));
  117. $element = array_shift($exploded);
  118. while ($element !== self::HEADER_END) {
  119. $result[$element] = array_shift($exploded);
  120. $element = array_shift($exploded);
  121. }
  122. }
  123. }
  124. return $result;
  125. }
  126. /**
  127. * create header for encrypted file
  128. *
  129. * @param array $headerData
  130. * @param IEncryptionModule $encryptionModule
  131. * @return string
  132. * @throws EncryptionHeaderToLargeException if header has to many arguments
  133. * @throws EncryptionHeaderKeyExistsException if header key is already in use
  134. */
  135. public function createHeader(array $headerData, IEncryptionModule $encryptionModule) {
  136. $header = self::HEADER_START . ':' . self::HEADER_ENCRYPTION_MODULE_KEY . ':' . $encryptionModule->getId() . ':';
  137. foreach ($headerData as $key => $value) {
  138. if (in_array($key, $this->ocHeaderKeys)) {
  139. throw new EncryptionHeaderKeyExistsException($key);
  140. }
  141. $header .= $key . ':' . $value . ':';
  142. }
  143. $header .= self::HEADER_END;
  144. if (strlen($header) > $this->getHeaderSize()) {
  145. throw new EncryptionHeaderToLargeException();
  146. }
  147. $paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT);
  148. return $paddedHeader;
  149. }
  150. /**
  151. * go recursively through a dir and collect all files and sub files.
  152. *
  153. * @param string $dir relative to the users files folder
  154. * @return array with list of files relative to the users files folder
  155. */
  156. public function getAllFiles($dir) {
  157. $result = array();
  158. $dirList = array($dir);
  159. while ($dirList) {
  160. $dir = array_pop($dirList);
  161. $content = $this->view->getDirectoryContent($dir);
  162. foreach ($content as $c) {
  163. if ($c->getType() === 'dir') {
  164. $dirList[] = $c->getPath();
  165. } else {
  166. $result[] = $c->getPath();
  167. }
  168. }
  169. }
  170. return $result;
  171. }
  172. /**
  173. * check if it is a file uploaded by the user stored in data/user/files
  174. * or a metadata file
  175. *
  176. * @param string $path relative to the data/ folder
  177. * @return boolean
  178. */
  179. public function isFile($path) {
  180. $parts = explode('/', Filesystem::normalizePath($path), 4);
  181. if (isset($parts[2]) && $parts[2] === 'files') {
  182. return true;
  183. }
  184. return false;
  185. }
  186. /**
  187. * return size of encryption header
  188. *
  189. * @return integer
  190. */
  191. public function getHeaderSize() {
  192. return $this->headerSize;
  193. }
  194. /**
  195. * return size of block read by a PHP stream
  196. *
  197. * @return integer
  198. */
  199. public function getBlockSize() {
  200. return $this->blockSize;
  201. }
  202. /**
  203. * get the owner and the path for the file relative to the owners files folder
  204. *
  205. * @param string $path
  206. * @return array
  207. * @throws \BadMethodCallException
  208. */
  209. public function getUidAndFilename($path) {
  210. $parts = explode('/', $path);
  211. $uid = '';
  212. if (count($parts) > 2) {
  213. $uid = $parts[1];
  214. }
  215. if (!$this->userManager->userExists($uid)) {
  216. throw new \BadMethodCallException(
  217. 'path needs to be relative to the system wide data folder and point to a user specific file'
  218. );
  219. }
  220. $ownerPath = implode('/', array_slice($parts, 2));
  221. return array($uid, Filesystem::normalizePath($ownerPath));
  222. }
  223. /**
  224. * Remove .path extension from a file path
  225. * @param string $path Path that may identify a .part file
  226. * @return string File path without .part extension
  227. * @note this is needed for reusing keys
  228. */
  229. public function stripPartialFileExtension($path) {
  230. $extension = pathinfo($path, PATHINFO_EXTENSION);
  231. if ( $extension === 'part') {
  232. $newLength = strlen($path) - 5; // 5 = strlen(".part")
  233. $fPath = substr($path, 0, $newLength);
  234. // if path also contains a transaction id, we remove it too
  235. $extension = pathinfo($fPath, PATHINFO_EXTENSION);
  236. if(substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId")
  237. $newLength = strlen($fPath) - strlen($extension) -1;
  238. $fPath = substr($fPath, 0, $newLength);
  239. }
  240. return $fPath;
  241. } else {
  242. return $path;
  243. }
  244. }
  245. public function getUserWithAccessToMountPoint($users, $groups) {
  246. $result = array();
  247. if (in_array('all', $users)) {
  248. $result = \OCP\User::getUsers();
  249. } else {
  250. $result = array_merge($result, $users);
  251. foreach ($groups as $group) {
  252. $result = array_merge($result, \OC_Group::usersInGroup($group));
  253. }
  254. }
  255. return $result;
  256. }
  257. /**
  258. * check if the file is stored on a system wide mount point
  259. * @param string $path relative to /data/user with leading '/'
  260. * @param string $uid
  261. * @return boolean
  262. */
  263. public function isSystemWideMountPoint($path, $uid) {
  264. if (\OCP\App::isEnabled("files_external")) {
  265. $mounts = \OC_Mount_Config::getSystemMountPoints();
  266. foreach ($mounts as $mount) {
  267. if (strpos($path, '/files/' . $mount['mountpoint']) === 0) {
  268. if ($this->isMountPointApplicableToUser($mount, $uid)) {
  269. return true;
  270. }
  271. }
  272. }
  273. }
  274. return false;
  275. }
  276. /**
  277. * check if mount point is applicable to user
  278. *
  279. * @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups']
  280. * @param string $uid
  281. * @return boolean
  282. */
  283. private function isMountPointApplicableToUser($mount, $uid) {
  284. $acceptedUids = array('all', $uid);
  285. // check if mount point is applicable for the user
  286. $intersection = array_intersect($acceptedUids, $mount['applicable']['users']);
  287. if (!empty($intersection)) {
  288. return true;
  289. }
  290. // check if mount point is applicable for group where the user is a member
  291. foreach ($mount['applicable']['groups'] as $gid) {
  292. if ($this->groupManager->isInGroup($uid, $gid)) {
  293. return true;
  294. }
  295. }
  296. return false;
  297. }
  298. /**
  299. * check if it is a path which is excluded by ownCloud from encryption
  300. *
  301. * @param string $path
  302. * @return boolean
  303. */
  304. public function isExcluded($path) {
  305. $normalizedPath = \OC\Files\Filesystem::normalizePath($path);
  306. $root = explode('/', $normalizedPath, 4);
  307. if (count($root) > 2) {
  308. //detect system wide folders
  309. if (in_array($root[1], $this->excludedPaths)) {
  310. return true;
  311. }
  312. // detect user specific folders
  313. if ($this->userManager->userExists($root[1])
  314. && in_array($root[2], $this->excludedPaths)) {
  315. return true;
  316. }
  317. }
  318. return false;
  319. }
  320. /**
  321. * check if recovery key is enabled for user
  322. *
  323. * @param string $uid
  324. * @return boolean
  325. */
  326. public function recoveryEnabled($uid) {
  327. $enabled = $this->config->getUserValue($uid, 'encryption', 'recovery_enabled', '0');
  328. return ($enabled === '1') ? true : false;
  329. }
  330. }