You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2143 lines
80 KiB

9 years ago
9 years ago
10 years ago
9 years ago
9 years ago
10 years ago
10 years ago
12 years ago
11 years ago
10 years ago
10 years ago
12 years ago
10 years ago
10 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
10 years ago
10 years ago
10 years ago
12 years ago
12 years ago
10 years ago
10 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Bart Visscher <bartv@thisnet.nl>
  6. * @author Bernhard Reiter <ockham@raz.or.at>
  7. * @author Bjoern Schiessle <bjoern@schiessle.org>
  8. * @author Björn Schießle <bjoern@schiessle.org>
  9. * @author Christopher Schäpers <kondou@ts.unde.re>
  10. * @author Joas Schilling <coding@schilljs.com>
  11. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  12. * @author Lukas Reschke <lukas@statuscode.ch>
  13. * @author Morris Jobke <hey@morrisjobke.de>
  14. * @author Robin Appelman <robin@icewind.nl>
  15. * @author Robin McCorkell <robin@mccorkell.me.uk>
  16. * @author Roeland Jago Douma <roeland@famdouma.nl>
  17. * @author Sebastian Döll <sebastian.doell@libasys.de>
  18. * @author Stefan Weil <sw@weilnetz.de>
  19. * @author Thomas Müller <thomas.mueller@tmit.eu>
  20. * @author Torben Dannhauer <torben@dannhauer.de>
  21. * @author Vincent Petry <pvince81@owncloud.com>
  22. * @author Volkan Gezer <volkangezer@gmail.com>
  23. *
  24. * @license AGPL-3.0
  25. *
  26. * This code is free software: you can redistribute it and/or modify
  27. * it under the terms of the GNU Affero General Public License, version 3,
  28. * as published by the Free Software Foundation.
  29. *
  30. * This program is distributed in the hope that it will be useful,
  31. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  32. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  33. * GNU Affero General Public License for more details.
  34. *
  35. * You should have received a copy of the GNU Affero General Public License, version 3,
  36. * along with this program. If not, see <http://www.gnu.org/licenses/>
  37. *
  38. */
  39. namespace OC\Share;
  40. use OC\Files\Filesystem;
  41. use OCP\DB\QueryBuilder\IQueryBuilder;
  42. use OCP\ILogger;
  43. use OCP\IUserManager;
  44. use OCP\IUserSession;
  45. use OCP\IDBConnection;
  46. use OCP\IConfig;
  47. use OCP\Util;
  48. /**
  49. * This class provides the ability for apps to share their content between users.
  50. * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
  51. *
  52. * It provides the following hooks:
  53. * - post_shared
  54. */
  55. class Share extends Constants {
  56. /** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
  57. * Construct permissions for share() and setPermissions with Or (|) e.g.
  58. * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
  59. *
  60. * Check if permission is granted with And (&) e.g. Check if delete is
  61. * granted: if ($permissions & PERMISSION_DELETE)
  62. *
  63. * Remove permissions with And (&) and Not (~) e.g. Remove the update
  64. * permission: $permissions &= ~PERMISSION_UPDATE
  65. *
  66. * Apps are required to handle permissions on their own, this class only
  67. * stores and manages the permissions of shares
  68. * @see lib/public/constants.php
  69. */
  70. /**
  71. * Register a sharing backend class that implements OCP\Share_Backend for an item type
  72. * @param string $itemType Item type
  73. * @param string $class Backend class
  74. * @param string $collectionOf (optional) Depends on item type
  75. * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
  76. * @return boolean true if backend is registered or false if error
  77. */
  78. public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
  79. if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') {
  80. if (!isset(self::$backendTypes[$itemType])) {
  81. self::$backendTypes[$itemType] = array(
  82. 'class' => $class,
  83. 'collectionOf' => $collectionOf,
  84. 'supportedFileExtensions' => $supportedFileExtensions
  85. );
  86. if(count(self::$backendTypes) === 1) {
  87. Util::addScript('core', 'merged-share-backend');
  88. \OC_Util::addStyle('core', 'share');
  89. }
  90. return true;
  91. }
  92. \OCP\Util::writeLog('OCP\Share',
  93. 'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
  94. .' is already registered for '.$itemType,
  95. \OCP\Util::WARN);
  96. }
  97. return false;
  98. }
  99. /**
  100. * Get the items of item type shared with the current user
  101. * @param string $itemType
  102. * @param int $format (optional) Format type must be defined by the backend
  103. * @param mixed $parameters (optional)
  104. * @param int $limit Number of items to return (optional) Returns all by default
  105. * @param boolean $includeCollections (optional)
  106. * @return mixed Return depends on format
  107. */
  108. public static function getItemsSharedWith($itemType, $format = self::FORMAT_NONE,
  109. $parameters = null, $limit = -1, $includeCollections = false) {
  110. return self::getItems($itemType, null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format,
  111. $parameters, $limit, $includeCollections);
  112. }
  113. /**
  114. * Get the items of item type shared with a user
  115. * @param string $itemType
  116. * @param string $user id for which user we want the shares
  117. * @param int $format (optional) Format type must be defined by the backend
  118. * @param mixed $parameters (optional)
  119. * @param int $limit Number of items to return (optional) Returns all by default
  120. * @param boolean $includeCollections (optional)
  121. * @return mixed Return depends on format
  122. */
  123. public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
  124. $parameters = null, $limit = -1, $includeCollections = false) {
  125. return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
  126. $parameters, $limit, $includeCollections);
  127. }
  128. /**
  129. * Get the item of item type shared with a given user by source
  130. * @param string $itemType
  131. * @param string $itemSource
  132. * @param string $user User to whom the item was shared
  133. * @param string $owner Owner of the share
  134. * @param int $shareType only look for a specific share type
  135. * @return array Return list of items with file_target, permissions and expiration
  136. */
  137. public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
  138. $shares = array();
  139. $fileDependent = false;
  140. $where = 'WHERE';
  141. $fileDependentWhere = '';
  142. if ($itemType === 'file' || $itemType === 'folder') {
  143. $fileDependent = true;
  144. $column = 'file_source';
  145. $fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
  146. $fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
  147. } else {
  148. $column = 'item_source';
  149. }
  150. $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
  151. $where .= ' `' . $column . '` = ? AND `item_type` = ? ';
  152. $arguments = array($itemSource, $itemType);
  153. // for link shares $user === null
  154. if ($user !== null) {
  155. $where .= ' AND `share_with` = ? ';
  156. $arguments[] = $user;
  157. }
  158. if ($shareType !== null) {
  159. $where .= ' AND `share_type` = ? ';
  160. $arguments[] = $shareType;
  161. }
  162. if ($owner !== null) {
  163. $where .= ' AND `uid_owner` = ? ';
  164. $arguments[] = $owner;
  165. }
  166. $query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
  167. $result = \OC_DB::executeAudited($query, $arguments);
  168. while ($row = $result->fetchRow()) {
  169. if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
  170. continue;
  171. }
  172. if ($fileDependent && (int)$row['file_parent'] === -1) {
  173. // if it is a mount point we need to get the path from the mount manager
  174. $mountManager = \OC\Files\Filesystem::getMountManager();
  175. $mountPoint = $mountManager->findByStorageId($row['storage_id']);
  176. if (!empty($mountPoint)) {
  177. $path = $mountPoint[0]->getMountPoint();
  178. $path = trim($path, '/');
  179. $path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
  180. $row['path'] = $path;
  181. } else {
  182. \OC::$server->getLogger()->warning(
  183. 'Could not resolve mount point for ' . $row['storage_id'],
  184. ['app' => 'OCP\Share']
  185. );
  186. }
  187. }
  188. $shares[] = $row;
  189. }
  190. //if didn't found a result than let's look for a group share.
  191. if(empty($shares) && $user !== null) {
  192. $userObject = \OC::$server->getUserManager()->get($user);
  193. $groups = [];
  194. if ($userObject) {
  195. $groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
  196. }
  197. if (!empty($groups)) {
  198. $where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
  199. $arguments = array($itemSource, $itemType, $groups);
  200. $types = array(null, null, IQueryBuilder::PARAM_STR_ARRAY);
  201. if ($owner !== null) {
  202. $where .= ' AND `uid_owner` = ?';
  203. $arguments[] = $owner;
  204. $types[] = null;
  205. }
  206. // TODO: inject connection, hopefully one day in the future when this
  207. // class isn't static anymore...
  208. $conn = \OC::$server->getDatabaseConnection();
  209. $result = $conn->executeQuery(
  210. 'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
  211. $arguments,
  212. $types
  213. );
  214. while ($row = $result->fetch()) {
  215. $shares[] = $row;
  216. }
  217. }
  218. }
  219. return $shares;
  220. }
  221. /**
  222. * Get the item of item type shared with the current user by source
  223. * @param string $itemType
  224. * @param string $itemSource
  225. * @param int $format (optional) Format type must be defined by the backend
  226. * @param mixed $parameters
  227. * @param boolean $includeCollections
  228. * @param string $shareWith (optional) define against which user should be checked, default: current user
  229. * @return array
  230. */
  231. public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE,
  232. $parameters = null, $includeCollections = false, $shareWith = null) {
  233. $shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith;
  234. return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format,
  235. $parameters, 1, $includeCollections, true);
  236. }
  237. /**
  238. * Based on the given token the share information will be returned - password protected shares will be verified
  239. * @param string $token
  240. * @param bool $checkPasswordProtection
  241. * @return array|boolean false will be returned in case the token is unknown or unauthorized
  242. */
  243. public static function getShareByToken($token, $checkPasswordProtection = true) {
  244. $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?', 1);
  245. $result = $query->execute(array($token));
  246. if ($result === false) {
  247. \OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, \OCP\Util::ERROR);
  248. }
  249. $row = $result->fetchRow();
  250. if ($row === false) {
  251. return false;
  252. }
  253. if (is_array($row) and self::expireItem($row)) {
  254. return false;
  255. }
  256. // password protected shares need to be authenticated
  257. if ($checkPasswordProtection && !\OC\Share\Share::checkPasswordProtectedShare($row)) {
  258. return false;
  259. }
  260. return $row;
  261. }
  262. /**
  263. * resolves reshares down to the last real share
  264. * @param array $linkItem
  265. * @return array file owner
  266. */
  267. public static function resolveReShare($linkItem)
  268. {
  269. if (isset($linkItem['parent'])) {
  270. $parent = $linkItem['parent'];
  271. while (isset($parent)) {
  272. $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `id` = ?', 1);
  273. $item = $query->execute(array($parent))->fetchRow();
  274. if (isset($item['parent'])) {
  275. $parent = $item['parent'];
  276. } else {
  277. return $item;
  278. }
  279. }
  280. }
  281. return $linkItem;
  282. }
  283. /**
  284. * Get the shared items of item type owned by the current user
  285. * @param string $itemType
  286. * @param int $format (optional) Format type must be defined by the backend
  287. * @param mixed $parameters
  288. * @param int $limit Number of items to return (optional) Returns all by default
  289. * @param boolean $includeCollections
  290. * @return mixed Return depends on format
  291. */
  292. public static function getItemsShared($itemType, $format = self::FORMAT_NONE, $parameters = null,
  293. $limit = -1, $includeCollections = false) {
  294. return self::getItems($itemType, null, null, null, \OC_User::getUser(), $format,
  295. $parameters, $limit, $includeCollections);
  296. }
  297. /**
  298. * Get the shared item of item type owned by the current user
  299. * @param string $itemType
  300. * @param string $itemSource
  301. * @param int $format (optional) Format type must be defined by the backend
  302. * @param mixed $parameters
  303. * @param boolean $includeCollections
  304. * @return mixed Return depends on format
  305. */
  306. public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
  307. $parameters = null, $includeCollections = false) {
  308. return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
  309. $parameters, -1, $includeCollections);
  310. }
  311. /**
  312. * Share an item with a user, group, or via private link
  313. * @param string $itemType
  314. * @param string $itemSource
  315. * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
  316. * @param string $shareWith User or group the item is being shared with
  317. * @param int $permissions CRUDS
  318. * @param string $itemSourceName
  319. * @param \DateTime|null $expirationDate
  320. * @param bool|null $passwordChanged
  321. * @return boolean|string Returns true on success or false on failure, Returns token on success for links
  322. * @throws \OC\HintException when the share type is remote and the shareWith is invalid
  323. * @throws \Exception
  324. * @since 5.0.0 - parameter $itemSourceName was added in 6.0.0, parameter $expirationDate was added in 7.0.0, parameter $passwordChanged added in 9.0.0
  325. */
  326. public static function shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions, $itemSourceName = null, \DateTime $expirationDate = null, $passwordChanged = null) {
  327. $backend = self::getBackend($itemType);
  328. $l = \OC::$server->getL10N('lib');
  329. if ($backend->isShareTypeAllowed($shareType) === false) {
  330. $message = 'Sharing %s failed, because the backend does not allow shares from type %i';
  331. $message_t = $l->t('Sharing %s failed, because the backend does not allow shares from type %i', array($itemSourceName, $shareType));
  332. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareType), \OCP\Util::DEBUG);
  333. throw new \Exception($message_t);
  334. }
  335. $uidOwner = \OC_User::getUser();
  336. $shareWithinGroupOnly = self::shareWithGroupMembersOnly();
  337. if (is_null($itemSourceName)) {
  338. $itemSourceName = $itemSource;
  339. }
  340. $itemName = $itemSourceName;
  341. // check if file can be shared
  342. if ($itemType === 'file' or $itemType === 'folder') {
  343. $path = \OC\Files\Filesystem::getPath($itemSource);
  344. $itemName = $path;
  345. // verify that the file exists before we try to share it
  346. if (!$path) {
  347. $message = 'Sharing %s failed, because the file does not exist';
  348. $message_t = $l->t('Sharing %s failed, because the file does not exist', array($itemSourceName));
  349. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
  350. throw new \Exception($message_t);
  351. }
  352. // verify that the user has share permission
  353. if (!\OC\Files\Filesystem::isSharable($path) || \OCP\Util::isSharingDisabledForUser()) {
  354. $message = 'You are not allowed to share %s';
  355. $message_t = $l->t('You are not allowed to share %s', [$path]);
  356. \OCP\Util::writeLog('OCP\Share', sprintf($message, $path), \OCP\Util::DEBUG);
  357. throw new \Exception($message_t);
  358. }
  359. }
  360. //verify that we don't share a folder which already contains a share mount point
  361. if ($itemType === 'folder') {
  362. $path = '/' . $uidOwner . '/files' . \OC\Files\Filesystem::getPath($itemSource) . '/';
  363. $mountManager = \OC\Files\Filesystem::getMountManager();
  364. $mounts = $mountManager->findIn($path);
  365. foreach ($mounts as $mount) {
  366. if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
  367. $message = 'Sharing "' . $itemSourceName . '" failed, because it contains files shared with you!';
  368. \OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
  369. throw new \Exception($message);
  370. }
  371. }
  372. }
  373. // single file shares should never have delete permissions
  374. if ($itemType === 'file') {
  375. $permissions = (int)$permissions & ~\OCP\Constants::PERMISSION_DELETE;
  376. }
  377. //Validate expirationDate
  378. if ($expirationDate !== null) {
  379. try {
  380. /*
  381. * Reuse the validateExpireDate.
  382. * We have to pass time() since the second arg is the time
  383. * the file was shared, since it is not shared yet we just use
  384. * the current time.
  385. */
  386. $expirationDate = self::validateExpireDate($expirationDate->format('Y-m-d'), time(), $itemType, $itemSource);
  387. } catch (\Exception $e) {
  388. throw new \OC\HintException($e->getMessage(), $e->getMessage(), 404);
  389. }
  390. }
  391. // Verify share type and sharing conditions are met
  392. if ($shareType === self::SHARE_TYPE_USER) {
  393. if ($shareWith == $uidOwner) {
  394. $message = 'Sharing %s failed, because you can not share with yourself';
  395. $message_t = $l->t('Sharing %s failed, because you can not share with yourself', [$itemName]);
  396. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
  397. throw new \Exception($message_t);
  398. }
  399. if (!\OC::$server->getUserManager()->userExists($shareWith)) {
  400. $message = 'Sharing %s failed, because the user %s does not exist';
  401. $message_t = $l->t('Sharing %s failed, because the user %s does not exist', array($itemSourceName, $shareWith));
  402. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
  403. throw new \Exception($message_t);
  404. }
  405. if ($shareWithinGroupOnly) {
  406. $userManager = \OC::$server->getUserManager();
  407. $groupManager = \OC::$server->getGroupManager();
  408. $userOwner = $userManager->get($uidOwner);
  409. $userShareWith = $userManager->get($shareWith);
  410. $groupsOwner = [];
  411. $groupsShareWith = [];
  412. if ($userOwner) {
  413. $groupsOwner = $groupManager->getUserGroupIds($userOwner);
  414. }
  415. if ($userShareWith) {
  416. $groupsShareWith = $groupManager->getUserGroupIds($userShareWith);
  417. }
  418. $inGroup = array_intersect($groupsOwner, $groupsShareWith);
  419. if (empty($inGroup)) {
  420. $message = 'Sharing %s failed, because the user '
  421. .'%s is not a member of any groups that %s is a member of';
  422. $message_t = $l->t('Sharing %s failed, because the user %s is not a member of any groups that %s is a member of', array($itemName, $shareWith, $uidOwner));
  423. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemName, $shareWith, $uidOwner), \OCP\Util::DEBUG);
  424. throw new \Exception($message_t);
  425. }
  426. }
  427. // Check if the item source is already shared with the user, either from the same owner or a different user
  428. if ($checkExists = self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups,
  429. $shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
  430. // Only allow the same share to occur again if it is the same
  431. // owner and is not a user share, this use case is for increasing
  432. // permissions for a specific user
  433. if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
  434. $message = 'Sharing %s failed, because this item is already shared with %s';
  435. $message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
  436. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
  437. throw new \Exception($message_t);
  438. }
  439. }
  440. if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_USER,
  441. $shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
  442. // Only allow the same share to occur again if it is the same
  443. // owner and is not a user share, this use case is for increasing
  444. // permissions for a specific user
  445. if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
  446. $message = 'Sharing %s failed, because this item is already shared with user %s';
  447. $message_t = $l->t('Sharing %s failed, because this item is already shared with user %s', array($itemSourceName, $shareWith));
  448. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::ERROR);
  449. throw new \Exception($message_t);
  450. }
  451. }
  452. } else if ($shareType === self::SHARE_TYPE_GROUP) {
  453. if (!\OC::$server->getGroupManager()->groupExists($shareWith)) {
  454. $message = 'Sharing %s failed, because the group %s does not exist';
  455. $message_t = $l->t('Sharing %s failed, because the group %s does not exist', array($itemSourceName, $shareWith));
  456. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
  457. throw new \Exception($message_t);
  458. }
  459. if ($shareWithinGroupOnly) {
  460. $group = \OC::$server->getGroupManager()->get($shareWith);
  461. $user = \OC::$server->getUserManager()->get($uidOwner);
  462. if (!$group || !$user || !$group->inGroup($user)) {
  463. $message = 'Sharing %s failed, because '
  464. . '%s is not a member of the group %s';
  465. $message_t = $l->t('Sharing %s failed, because %s is not a member of the group %s', array($itemSourceName, $uidOwner, $shareWith));
  466. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $uidOwner, $shareWith), \OCP\Util::DEBUG);
  467. throw new \Exception($message_t);
  468. }
  469. }
  470. // Check if the item source is already shared with the group, either from the same owner or a different user
  471. // The check for each user in the group is done inside the put() function
  472. if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_GROUP, $shareWith,
  473. null, self::FORMAT_NONE, null, 1, true, true)) {
  474. if ($checkExists['share_with'] === $shareWith && $checkExists['share_type'] === \OCP\Share::SHARE_TYPE_GROUP) {
  475. $message = 'Sharing %s failed, because this item is already shared with %s';
  476. $message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
  477. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
  478. throw new \Exception($message_t);
  479. }
  480. }
  481. // Convert share with into an array with the keys group and users
  482. $group = $shareWith;
  483. $shareWith = array();
  484. $shareWith['group'] = $group;
  485. $groupObject = \OC::$server->getGroupManager()->get($group);
  486. $userIds = [];
  487. if ($groupObject) {
  488. $users = $groupObject->searchUsers('', -1, 0);
  489. foreach ($users as $user) {
  490. $userIds[] = $user->getUID();
  491. }
  492. }
  493. $shareWith['users'] = array_diff($userIds, array($uidOwner));
  494. } else if ($shareType === self::SHARE_TYPE_LINK) {
  495. $updateExistingShare = false;
  496. if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') == 'yes') {
  497. // IF the password is changed via the old ajax endpoint verify it before deleting the old share
  498. if ($passwordChanged === true) {
  499. self::verifyPassword($shareWith);
  500. }
  501. // when updating a link share
  502. // FIXME Don't delete link if we update it
  503. if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null,
  504. $uidOwner, self::FORMAT_NONE, null, 1)) {
  505. // remember old token
  506. $oldToken = $checkExists['token'];
  507. $oldPermissions = $checkExists['permissions'];
  508. //delete the old share
  509. Helper::delete($checkExists['id']);
  510. $updateExistingShare = true;
  511. }
  512. if ($passwordChanged === null) {
  513. // Generate hash of password - same method as user passwords
  514. if (is_string($shareWith) && $shareWith !== '') {
  515. self::verifyPassword($shareWith);
  516. $shareWith = \OC::$server->getHasher()->hash($shareWith);
  517. } else {
  518. // reuse the already set password, but only if we change permissions
  519. // otherwise the user disabled the password protection
  520. if ($checkExists && (int)$permissions !== (int)$oldPermissions) {
  521. $shareWith = $checkExists['share_with'];
  522. }
  523. }
  524. } else {
  525. if ($passwordChanged === true) {
  526. if (is_string($shareWith) && $shareWith !== '') {
  527. self::verifyPassword($shareWith);
  528. $shareWith = \OC::$server->getHasher()->hash($shareWith);
  529. }
  530. } else if ($updateExistingShare) {
  531. $shareWith = $checkExists['share_with'];
  532. }
  533. }
  534. if (\OCP\Util::isPublicLinkPasswordRequired() && empty($shareWith)) {
  535. $message = 'You need to provide a password to create a public link, only protected links are allowed';
  536. $message_t = $l->t('You need to provide a password to create a public link, only protected links are allowed');
  537. \OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
  538. throw new \Exception($message_t);
  539. }
  540. if ($updateExistingShare === false &&
  541. self::isDefaultExpireDateEnabled() &&
  542. empty($expirationDate)) {
  543. $expirationDate = Helper::calcExpireDate();
  544. }
  545. // Generate token
  546. if (isset($oldToken)) {
  547. $token = $oldToken;
  548. } else {
  549. $token = \OC::$server->getSecureRandom()->generate(self::TOKEN_LENGTH,
  550. \OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
  551. );
  552. }
  553. $result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions,
  554. null, $token, $itemSourceName, $expirationDate);
  555. if ($result) {
  556. return $token;
  557. } else {
  558. return false;
  559. }
  560. }
  561. $message = 'Sharing %s failed, because sharing with links is not allowed';
  562. $message_t = $l->t('Sharing %s failed, because sharing with links is not allowed', array($itemSourceName));
  563. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
  564. throw new \Exception($message_t);
  565. } else if ($shareType === self::SHARE_TYPE_REMOTE) {
  566. /*
  567. * Check if file is not already shared with the remote user
  568. */
  569. if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_REMOTE,
  570. $shareWith, $uidOwner, self::FORMAT_NONE, null, 1, true, true)) {
  571. $message = 'Sharing %s failed, because this item is already shared with %s';
  572. $message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
  573. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
  574. throw new \Exception($message_t);
  575. }
  576. // don't allow federated shares if source and target server are the same
  577. list($user, $remote) = Helper::splitUserRemote($shareWith);
  578. $currentServer = self::removeProtocolFromUrl(\OC::$server->getURLGenerator()->getAbsoluteURL('/'));
  579. $currentUser = \OC::$server->getUserSession()->getUser()->getUID();
  580. if (Helper::isSameUserOnSameServer($user, $remote, $currentUser, $currentServer)) {
  581. $message = 'Not allowed to create a federated share with the same user.';
  582. $message_t = $l->t('Not allowed to create a federated share with the same user');
  583. \OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
  584. throw new \Exception($message_t);
  585. }
  586. $token = \OC::$server->getSecureRandom()->generate(self::TOKEN_LENGTH, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_UPPER .
  587. \OCP\Security\ISecureRandom::CHAR_DIGITS);
  588. $shareWith = $user . '@' . $remote;
  589. $shareId = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, $token, $itemSourceName);
  590. $send = false;
  591. if ($shareId) {
  592. $send = self::sendRemoteShare($token, $shareWith, $itemSourceName, $shareId, $uidOwner);
  593. }
  594. if ($send === false) {
  595. $currentUser = \OC::$server->getUserSession()->getUser()->getUID();
  596. self::unshare($itemType, $itemSource, $shareType, $shareWith, $currentUser);
  597. $message_t = $l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.', array($itemSourceName, $shareWith));
  598. throw new \Exception($message_t);
  599. }
  600. return $send;
  601. } else {
  602. // Future share types need to include their own conditions
  603. $message = 'Share type %s is not valid for %s';
  604. $message_t = $l->t('Share type %s is not valid for %s', array($shareType, $itemSource));
  605. \OCP\Util::writeLog('OCP\Share', sprintf($message, $shareType, $itemSource), \OCP\Util::DEBUG);
  606. throw new \Exception($message_t);
  607. }
  608. // Put the item into the database
  609. $result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, null, $itemSourceName, $expirationDate);
  610. return $result ? true : false;
  611. }
  612. /**
  613. * Unshare an item from a user, group, or delete a private link
  614. * @param string $itemType
  615. * @param string $itemSource
  616. * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
  617. * @param string $shareWith User or group the item is being shared with
  618. * @param string $owner owner of the share, if null the current user is used
  619. * @return boolean true on success or false on failure
  620. */
  621. public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) {
  622. // check if it is a valid itemType
  623. self::getBackend($itemType);
  624. $items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType);
  625. $toDelete = array();
  626. $newParent = null;
  627. $currentUser = $owner ? $owner : \OC_User::getUser();
  628. foreach ($items as $item) {
  629. // delete the item with the expected share_type and owner
  630. if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
  631. $toDelete = $item;
  632. // if there is more then one result we don't have to delete the children
  633. // but update their parent. For group shares the new parent should always be
  634. // the original group share and not the db entry with the unique name
  635. } else if ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
  636. $newParent = $item['parent'];
  637. } else {
  638. $newParent = $item['id'];
  639. }
  640. }
  641. if (!empty($toDelete)) {
  642. self::unshareItem($toDelete, $newParent);
  643. return true;
  644. }
  645. return false;
  646. }
  647. /**
  648. * sent status if users got informed by mail about share
  649. * @param string $itemType
  650. * @param string $itemSource
  651. * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
  652. * @param string $recipient with whom was the file shared
  653. * @param boolean $status
  654. */
  655. public static function setSendMailStatus($itemType, $itemSource, $shareType, $recipient, $status) {
  656. $status = $status ? 1 : 0;
  657. $query = \OC_DB::prepare(
  658. 'UPDATE `*PREFIX*share`
  659. SET `mail_send` = ?
  660. WHERE `item_type` = ? AND `item_source` = ? AND `share_type` = ? AND `share_with` = ?');
  661. $result = $query->execute(array($status, $itemType, $itemSource, $shareType, $recipient));
  662. if($result === false) {
  663. \OCP\Util::writeLog('OCP\Share', 'Couldn\'t set send mail status', \OCP\Util::ERROR);
  664. }
  665. }
  666. /**
  667. * validate expiration date if it meets all constraints
  668. *
  669. * @param string $expireDate well formatted date string, e.g. "DD-MM-YYYY"
  670. * @param string $shareTime timestamp when the file was shared
  671. * @param string $itemType
  672. * @param string $itemSource
  673. * @return \DateTime validated date
  674. * @throws \Exception when the expire date is in the past or further in the future then the enforced date
  675. */
  676. private static function validateExpireDate($expireDate, $shareTime, $itemType, $itemSource) {
  677. $l = \OC::$server->getL10N('lib');
  678. $date = new \DateTime($expireDate);
  679. $today = new \DateTime('now');
  680. // if the user doesn't provide a share time we need to get it from the database
  681. // fall-back mode to keep API stable, because the $shareTime parameter was added later
  682. $defaultExpireDateEnforced = \OCP\Util::isDefaultExpireDateEnforced();
  683. if ($defaultExpireDateEnforced && $shareTime === null) {
  684. $items = self::getItemShared($itemType, $itemSource);
  685. $firstItem = reset($items);
  686. $shareTime = (int)$firstItem['stime'];
  687. }
  688. if ($defaultExpireDateEnforced) {
  689. // initialize max date with share time
  690. $maxDate = new \DateTime();
  691. $maxDate->setTimestamp($shareTime);
  692. $maxDays = \OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
  693. $maxDate->add(new \DateInterval('P' . $maxDays . 'D'));
  694. if ($date > $maxDate) {
  695. $warning = 'Cannot set expiration date. Shares cannot expire later than ' . $maxDays . ' after they have been shared';
  696. $warning_t = $l->t('Cannot set expiration date. Shares cannot expire later than %s after they have been shared', array($maxDays));
  697. \OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN);
  698. throw new \Exception($warning_t);
  699. }
  700. }
  701. if ($date < $today) {
  702. $message = 'Cannot set expiration date. Expiration date is in the past';
  703. $message_t = $l->t('Cannot set expiration date. Expiration date is in the past');
  704. \OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::WARN);
  705. throw new \Exception($message_t);
  706. }
  707. return $date;
  708. }
  709. /**
  710. * Checks whether a share has expired, calls unshareItem() if yes.
  711. * @param array $item Share data (usually database row)
  712. * @return boolean True if item was expired, false otherwise.
  713. */
  714. protected static function expireItem(array $item) {
  715. $result = false;
  716. // only use default expiration date for link shares
  717. if ((int) $item['share_type'] === self::SHARE_TYPE_LINK) {
  718. // calculate expiration date
  719. if (!empty($item['expiration'])) {
  720. $userDefinedExpire = new \DateTime($item['expiration']);
  721. $expires = $userDefinedExpire->getTimestamp();
  722. } else {
  723. $expires = null;
  724. }
  725. // get default expiration settings
  726. $defaultSettings = Helper::getDefaultExpireSetting();
  727. $expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
  728. if (is_int($expires)) {
  729. $now = time();
  730. if ($now > $expires) {
  731. self::unshareItem($item);
  732. $result = true;
  733. }
  734. }
  735. }
  736. return $result;
  737. }
  738. /**
  739. * Unshares a share given a share data array
  740. * @param array $item Share data (usually database row)
  741. * @param int $newParent parent ID
  742. * @return null
  743. */
  744. protected static function unshareItem(array $item, $newParent = null) {
  745. $shareType = (int)$item['share_type'];
  746. $shareWith = null;
  747. if ($shareType !== \OCP\Share::SHARE_TYPE_LINK) {
  748. $shareWith = $item['share_with'];
  749. }
  750. // Pass all the vars we have for now, they may be useful
  751. $hookParams = array(
  752. 'id' => $item['id'],
  753. 'itemType' => $item['item_type'],
  754. 'itemSource' => $item['item_source'],
  755. 'shareType' => $shareType,
  756. 'shareWith' => $shareWith,
  757. 'itemParent' => $item['parent'],
  758. 'uidOwner' => $item['uid_owner'],
  759. );
  760. if($item['item_type'] === 'file' || $item['item_type'] === 'folder') {
  761. $hookParams['fileSource'] = $item['file_source'];
  762. $hookParams['fileTarget'] = $item['file_target'];
  763. }
  764. \OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
  765. $deletedShares = Helper::delete($item['id'], false, null, $newParent);
  766. $deletedShares[] = $hookParams;
  767. $hookParams['deletedShares'] = $deletedShares;
  768. \OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
  769. if ((int)$item['share_type'] === \OCP\Share::SHARE_TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
  770. list(, $remote) = Helper::splitUserRemote($item['share_with']);
  771. self::sendRemoteUnshare($remote, $item['id'], $item['token']);
  772. }
  773. }
  774. /**
  775. * Get the backend class for the specified item type
  776. * @param string $itemType
  777. * @throws \Exception
  778. * @return \OCP\Share_Backend
  779. */
  780. public static function getBackend($itemType) {
  781. $l = \OC::$server->getL10N('lib');
  782. if (isset(self::$backends[$itemType])) {
  783. return self::$backends[$itemType];
  784. } else if (isset(self::$backendTypes[$itemType]['class'])) {
  785. $class = self::$backendTypes[$itemType]['class'];
  786. if (class_exists($class)) {
  787. self::$backends[$itemType] = new $class;
  788. if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
  789. $message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
  790. $message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', array($class));
  791. \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), \OCP\Util::ERROR);
  792. throw new \Exception($message_t);
  793. }
  794. return self::$backends[$itemType];
  795. } else {
  796. $message = 'Sharing backend %s not found';
  797. $message_t = $l->t('Sharing backend %s not found', array($class));
  798. \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), \OCP\Util::ERROR);
  799. throw new \Exception($message_t);
  800. }
  801. }
  802. $message = 'Sharing backend for %s not found';
  803. $message_t = $l->t('Sharing backend for %s not found', array($itemType));
  804. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), \OCP\Util::ERROR);
  805. throw new \Exception($message_t);
  806. }
  807. /**
  808. * Check if resharing is allowed
  809. * @return boolean true if allowed or false
  810. *
  811. * Resharing is allowed by default if not configured
  812. */
  813. public static function isResharingAllowed() {
  814. if (!isset(self::$isResharingAllowed)) {
  815. if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
  816. self::$isResharingAllowed = true;
  817. } else {
  818. self::$isResharingAllowed = false;
  819. }
  820. }
  821. return self::$isResharingAllowed;
  822. }
  823. /**
  824. * Get a list of collection item types for the specified item type
  825. * @param string $itemType
  826. * @return array
  827. */
  828. private static function getCollectionItemTypes($itemType) {
  829. $collectionTypes = array($itemType);
  830. foreach (self::$backendTypes as $type => $backend) {
  831. if (in_array($backend['collectionOf'], $collectionTypes)) {
  832. $collectionTypes[] = $type;
  833. }
  834. }
  835. // TODO Add option for collections to be collection of themselves, only 'folder' does it now...
  836. if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
  837. unset($collectionTypes[0]);
  838. }
  839. // Return array if collections were found or the item type is a
  840. // collection itself - collections can be inside collections
  841. if (count($collectionTypes) > 0) {
  842. return $collectionTypes;
  843. }
  844. return false;
  845. }
  846. /**
  847. * Get the owners of items shared with a user.
  848. *
  849. * @param string $user The user the items are shared with.
  850. * @param string $type The type of the items shared with the user.
  851. * @param boolean $includeCollections Include collection item types (optional)
  852. * @param boolean $includeOwner include owner in the list of users the item is shared with (optional)
  853. * @return array
  854. */
  855. public static function getSharedItemsOwners($user, $type, $includeCollections = false, $includeOwner = false) {
  856. // First, we find out if $type is part of a collection (and if that collection is part of
  857. // another one and so on).
  858. $collectionTypes = array();
  859. if (!$includeCollections || !$collectionTypes = self::getCollectionItemTypes($type)) {
  860. $collectionTypes[] = $type;
  861. }
  862. // Of these collection types, along with our original $type, we make a
  863. // list of the ones for which a sharing backend has been registered.
  864. // FIXME: Ideally, we wouldn't need to nest getItemsSharedWith in this loop but just call it
  865. // with its $includeCollections parameter set to true. Unfortunately, this fails currently.
  866. $allMaybeSharedItems = array();
  867. foreach ($collectionTypes as $collectionType) {
  868. if (isset(self::$backends[$collectionType])) {
  869. $allMaybeSharedItems[$collectionType] = self::getItemsSharedWithUser(
  870. $collectionType,
  871. $user,
  872. self::FORMAT_NONE
  873. );
  874. }
  875. }
  876. $owners = array();
  877. if ($includeOwner) {
  878. $owners[] = $user;
  879. }
  880. // We take a look at all shared items of the given $type (or of the collections it is part of)
  881. // and find out their owners. Then, we gather the tags for the original $type from all owners,
  882. // and return them as elements of a list that look like "Tag (owner)".
  883. foreach ($allMaybeSharedItems as $collectionType => $maybeSharedItems) {
  884. foreach ($maybeSharedItems as $sharedItem) {
  885. if (isset($sharedItem['id'])) { //workaround for https://github.com/owncloud/core/issues/2814
  886. $owners[] = $sharedItem['uid_owner'];
  887. }
  888. }
  889. }
  890. return $owners;
  891. }
  892. /**
  893. * Get shared items from the database
  894. * @param string $itemType
  895. * @param string $item Item source or target (optional)
  896. * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
  897. * @param string $shareWith User or group the item is being shared with
  898. * @param string $uidOwner User that is the owner of shared items (optional)
  899. * @param int $format Format to convert items to with formatItems() (optional)
  900. * @param mixed $parameters to pass to formatItems() (optional)
  901. * @param int $limit Number of items to return, -1 to return all matches (optional)
  902. * @param boolean $includeCollections Include collection item types (optional)
  903. * @param boolean $itemShareWithBySource (optional)
  904. * @param boolean $checkExpireDate
  905. * @return array
  906. *
  907. * See public functions getItem(s)... for parameter usage
  908. *
  909. */
  910. public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
  911. $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
  912. $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
  913. if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
  914. return array();
  915. }
  916. $backend = self::getBackend($itemType);
  917. $collectionTypes = false;
  918. // Get filesystem root to add it to the file target and remove from the
  919. // file source, match file_source with the file cache
  920. if ($itemType == 'file' || $itemType == 'folder') {
  921. if(!is_null($uidOwner)) {
  922. $root = \OC\Files\Filesystem::getRoot();
  923. } else {
  924. $root = '';
  925. }
  926. $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
  927. if (!isset($item)) {
  928. $where .= ' AND `file_target` IS NOT NULL ';
  929. }
  930. $where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
  931. $fileDependent = true;
  932. $queryArgs = array();
  933. } else {
  934. $fileDependent = false;
  935. $root = '';
  936. $collectionTypes = self::getCollectionItemTypes($itemType);
  937. if ($includeCollections && !isset($item) && $collectionTypes) {
  938. // If includeCollections is true, find collections of this item type, e.g. a music album contains songs
  939. if (!in_array($itemType, $collectionTypes)) {
  940. $itemTypes = array_merge(array($itemType), $collectionTypes);
  941. } else {
  942. $itemTypes = $collectionTypes;
  943. }
  944. $placeholders = join(',', array_fill(0, count($itemTypes), '?'));
  945. $where = ' WHERE `item_type` IN ('.$placeholders.'))';
  946. $queryArgs = $itemTypes;
  947. } else {
  948. $where = ' WHERE `item_type` = ?';
  949. $queryArgs = array($itemType);
  950. }
  951. }
  952. if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
  953. $where .= ' AND `share_type` != ?';
  954. $queryArgs[] = self::SHARE_TYPE_LINK;
  955. }
  956. if (isset($shareType)) {
  957. // Include all user and group items
  958. if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
  959. $where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
  960. $queryArgs[] = self::SHARE_TYPE_USER;
  961. $queryArgs[] = self::$shareTypeGroupUserUnique;
  962. $queryArgs[] = $shareWith;
  963. $user = \OC::$server->getUserManager()->get($shareWith);
  964. $groups = [];
  965. if ($user) {
  966. $groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
  967. }
  968. if (!empty($groups)) {
  969. $placeholders = join(',', array_fill(0, count($groups), '?'));
  970. $where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
  971. $queryArgs[] = self::SHARE_TYPE_GROUP;
  972. $queryArgs = array_merge($queryArgs, $groups);
  973. }
  974. $where .= ')';
  975. // Don't include own group shares
  976. $where .= ' AND `uid_owner` != ?';
  977. $queryArgs[] = $shareWith;
  978. } else {
  979. $where .= ' AND `share_type` = ?';
  980. $queryArgs[] = $shareType;
  981. if (isset($shareWith)) {
  982. $where .= ' AND `share_with` = ?';
  983. $queryArgs[] = $shareWith;
  984. }
  985. }
  986. }
  987. if (isset($uidOwner)) {
  988. $where .= ' AND `uid_owner` = ?';
  989. $queryArgs[] = $uidOwner;
  990. if (!isset($shareType)) {
  991. // Prevent unique user targets for group shares from being selected
  992. $where .= ' AND `share_type` != ?';
  993. $queryArgs[] = self::$shareTypeGroupUserUnique;
  994. }
  995. if ($fileDependent) {
  996. $column = 'file_source';
  997. } else {
  998. $column = 'item_source';
  999. }
  1000. } else {
  1001. if ($fileDependent) {
  1002. $column = 'file_target';
  1003. } else {
  1004. $column = 'item_target';
  1005. }
  1006. }
  1007. if (isset($item)) {
  1008. $collectionTypes = self::getCollectionItemTypes($itemType);
  1009. if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
  1010. $where .= ' AND (';
  1011. } else {
  1012. $where .= ' AND';
  1013. }
  1014. // If looking for own shared items, check item_source else check item_target
  1015. if (isset($uidOwner) || $itemShareWithBySource) {
  1016. // If item type is a file, file source needs to be checked in case the item was converted
  1017. if ($fileDependent) {
  1018. $where .= ' `file_source` = ?';
  1019. $column = 'file_source';
  1020. } else {
  1021. $where .= ' `item_source` = ?';
  1022. $column = 'item_source';
  1023. }
  1024. } else {
  1025. if ($fileDependent) {
  1026. $where .= ' `file_target` = ?';
  1027. $item = \OC\Files\Filesystem::normalizePath($item);
  1028. } else {
  1029. $where .= ' `item_target` = ?';
  1030. }
  1031. }
  1032. $queryArgs[] = $item;
  1033. if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
  1034. $placeholders = join(',', array_fill(0, count($collectionTypes), '?'));
  1035. $where .= ' OR `item_type` IN ('.$placeholders.'))';
  1036. $queryArgs = array_merge($queryArgs, $collectionTypes);
  1037. }
  1038. }
  1039. if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
  1040. // Make sure the unique user target is returned if it exists,
  1041. // unique targets should follow the group share in the database
  1042. // If the limit is not 1, the filtering can be done later
  1043. $where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
  1044. } else {
  1045. $where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
  1046. }
  1047. if ($limit != -1 && !$includeCollections) {
  1048. // The limit must be at least 3, because filtering needs to be done
  1049. if ($limit < 3) {
  1050. $queryLimit = 3;
  1051. } else {
  1052. $queryLimit = $limit;
  1053. }
  1054. } else {
  1055. $queryLimit = null;
  1056. }
  1057. $select = self::createSelectStatement($format, $fileDependent, $uidOwner);
  1058. $root = strlen($root);
  1059. $query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
  1060. $result = $query->execute($queryArgs);
  1061. if ($result === false) {
  1062. \OCP\Util::writeLog('OCP\Share',
  1063. \OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
  1064. \OCP\Util::ERROR);
  1065. }
  1066. $items = array();
  1067. $targets = array();
  1068. $switchedItems = array();
  1069. $mounts = array();
  1070. while ($row = $result->fetchRow()) {
  1071. self::transformDBResults($row);
  1072. // Filter out duplicate group shares for users with unique targets
  1073. if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
  1074. continue;
  1075. }
  1076. if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
  1077. $row['share_type'] = self::SHARE_TYPE_GROUP;
  1078. $row['unique_name'] = true; // remember that we use a unique name for this user
  1079. $row['share_with'] = $items[$row['parent']]['share_with'];
  1080. // if the group share was unshared from the user we keep the permission, otherwise
  1081. // we take the permission from the parent because this is always the up-to-date
  1082. // permission for the group share
  1083. if ($row['permissions'] > 0) {
  1084. $row['permissions'] = $items[$row['parent']]['permissions'];
  1085. }
  1086. // Remove the parent group share
  1087. unset($items[$row['parent']]);
  1088. if ($row['permissions'] == 0) {
  1089. continue;
  1090. }
  1091. } else if (!isset($uidOwner)) {
  1092. // Check if the same target already exists
  1093. if (isset($targets[$row['id']])) {
  1094. // Check if the same owner shared with the user twice
  1095. // through a group and user share - this is allowed
  1096. $id = $targets[$row['id']];
  1097. if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
  1098. // Switch to group share type to ensure resharing conditions aren't bypassed
  1099. if ($items[$id]['share_type'] != self::SHARE_TYPE_GROUP) {
  1100. $items[$id]['share_type'] = self::SHARE_TYPE_GROUP;
  1101. $items[$id]['share_with'] = $row['share_with'];
  1102. }
  1103. // Switch ids if sharing permission is granted on only
  1104. // one share to ensure correct parent is used if resharing
  1105. if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
  1106. && (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
  1107. $items[$row['id']] = $items[$id];
  1108. $switchedItems[$id] = $row['id'];
  1109. unset($items[$id]);
  1110. $id = $row['id'];
  1111. }
  1112. $items[$id]['permissions'] |= (int)$row['permissions'];
  1113. }
  1114. continue;
  1115. } elseif (!empty($row['parent'])) {
  1116. $targets[$row['parent']] = $row['id'];
  1117. }
  1118. }
  1119. // Remove root from file source paths if retrieving own shared items
  1120. if (isset($uidOwner) && isset($row['path'])) {
  1121. if (isset($row['parent'])) {
  1122. $query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?');
  1123. $parentResult = $query->execute(array($row['parent']));
  1124. if ($result === false) {
  1125. \OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
  1126. \OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
  1127. \OCP\Util::ERROR);
  1128. } else {
  1129. $parentRow = $parentResult->fetchRow();
  1130. $tmpPath = $parentRow['file_target'];
  1131. // find the right position where the row path continues from the target path
  1132. $pos = strrpos($row['path'], $parentRow['file_target']);
  1133. $subPath = substr($row['path'], $pos);
  1134. $splitPath = explode('/', $subPath);
  1135. foreach (array_slice($splitPath, 2) as $pathPart) {
  1136. $tmpPath = $tmpPath . '/' . $pathPart;
  1137. }
  1138. $row['path'] = $tmpPath;
  1139. }
  1140. } else {
  1141. if (!isset($mounts[$row['storage']])) {
  1142. $mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
  1143. if (is_array($mountPoints) && !empty($mountPoints)) {
  1144. $mounts[$row['storage']] = current($mountPoints);
  1145. }
  1146. }
  1147. if (!empty($mounts[$row['storage']])) {
  1148. $path = $mounts[$row['storage']]->getMountPoint().$row['path'];
  1149. $relPath = substr($path, $root); // path relative to data/user
  1150. $row['path'] = rtrim($relPath, '/');
  1151. }
  1152. }
  1153. }
  1154. if($checkExpireDate) {
  1155. if (self::expireItem($row)) {
  1156. continue;
  1157. }
  1158. }
  1159. // Check if resharing is allowed, if not remove share permission
  1160. if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
  1161. $row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
  1162. }
  1163. // Add display names to result
  1164. $row['share_with_displayname'] = $row['share_with'];
  1165. if ( isset($row['share_with']) && $row['share_with'] != '' &&
  1166. $row['share_type'] === self::SHARE_TYPE_USER) {
  1167. $row['share_with_displayname'] = \OCP\User::getDisplayName($row['share_with']);
  1168. } else if(isset($row['share_with']) && $row['share_with'] != '' &&
  1169. $row['share_type'] === self::SHARE_TYPE_REMOTE) {
  1170. $addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
  1171. foreach ($addressBookEntries as $entry) {
  1172. foreach ($entry['CLOUD'] as $cloudID) {
  1173. if ($cloudID === $row['share_with']) {
  1174. $row['share_with_displayname'] = $entry['FN'];
  1175. }
  1176. }
  1177. }
  1178. }
  1179. if ( isset($row['uid_owner']) && $row['uid_owner'] != '') {
  1180. $row['displayname_owner'] = \OCP\User::getDisplayName($row['uid_owner']);
  1181. }
  1182. if ($row['permissions'] > 0) {
  1183. $items[$row['id']] = $row;
  1184. }
  1185. }
  1186. // group items if we are looking for items shared with the current user
  1187. if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
  1188. $items = self::groupItems($items, $itemType);
  1189. }
  1190. if (!empty($items)) {
  1191. $collectionItems = array();
  1192. foreach ($items as &$row) {
  1193. // Return only the item instead of a 2-dimensional array
  1194. if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) {
  1195. if ($format == self::FORMAT_NONE) {
  1196. return $row;
  1197. } else {
  1198. break;
  1199. }
  1200. }
  1201. // Check if this is a collection of the requested item type
  1202. if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
  1203. if (($collectionBackend = self::getBackend($row['item_type']))
  1204. && $collectionBackend instanceof \OCP\Share_Backend_Collection) {
  1205. // Collections can be inside collections, check if the item is a collection
  1206. if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
  1207. $collectionItems[] = $row;
  1208. } else {
  1209. $collection = array();
  1210. $collection['item_type'] = $row['item_type'];
  1211. if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
  1212. $collection['path'] = basename($row['path']);
  1213. }
  1214. $row['collection'] = $collection;
  1215. // Fetch all of the children sources
  1216. $children = $collectionBackend->getChildren($row[$column]);
  1217. foreach ($children as $child) {
  1218. $childItem = $row;
  1219. $childItem['item_type'] = $itemType;
  1220. if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
  1221. $childItem['item_source'] = $child['source'];
  1222. $childItem['item_target'] = $child['target'];
  1223. }
  1224. if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
  1225. if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
  1226. $childItem['file_source'] = $child['source'];
  1227. } else { // TODO is this really needed if we already know that we use the file backend?
  1228. $meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
  1229. $childItem['file_source'] = $meta['fileid'];
  1230. }
  1231. $childItem['file_target'] =
  1232. \OC\Files\Filesystem::normalizePath($child['file_path']);
  1233. }
  1234. if (isset($item)) {
  1235. if ($childItem[$column] == $item) {
  1236. // Return only the item instead of a 2-dimensional array
  1237. if ($limit == 1) {
  1238. if ($format == self::FORMAT_NONE) {
  1239. return $childItem;
  1240. } else {
  1241. // Unset the items array and break out of both loops
  1242. $items = array();
  1243. $items[] = $childItem;
  1244. break 2;
  1245. }
  1246. } else {
  1247. $collectionItems[] = $childItem;
  1248. }
  1249. }
  1250. } else {
  1251. $collectionItems[] = $childItem;
  1252. }
  1253. }
  1254. }
  1255. }
  1256. // Remove collection item
  1257. $toRemove = $row['id'];
  1258. if (array_key_exists($toRemove, $switchedItems)) {
  1259. $toRemove = $switchedItems[$toRemove];
  1260. }
  1261. unset($items[$toRemove]);
  1262. } elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
  1263. // FIXME: Thats a dirty hack to improve file sharing performance,
  1264. // see github issue #10588 for more details
  1265. // Need to find a solution which works for all back-ends
  1266. $collectionBackend = self::getBackend($row['item_type']);
  1267. $sharedParents = $collectionBackend->getParents($row['item_source']);
  1268. foreach ($sharedParents as $parent) {
  1269. $collectionItems[] = $parent;
  1270. }
  1271. }
  1272. }
  1273. if (!empty($collectionItems)) {
  1274. $collectionItems = array_unique($collectionItems, SORT_REGULAR);
  1275. $items = array_merge($items, $collectionItems);
  1276. }
  1277. // filter out invalid items, these can appear when subshare entries exist
  1278. // for a group in which the requested user isn't a member any more
  1279. $items = array_filter($items, function($item) {
  1280. return $item['share_type'] !== self::$shareTypeGroupUserUnique;
  1281. });
  1282. return self::formatResult($items, $column, $backend, $format, $parameters);
  1283. } elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
  1284. // FIXME: Thats a dirty hack to improve file sharing performance,
  1285. // see github issue #10588 for more details
  1286. // Need to find a solution which works for all back-ends
  1287. $collectionItems = array();
  1288. $collectionBackend = self::getBackend('folder');
  1289. $sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
  1290. foreach ($sharedParents as $parent) {
  1291. $collectionItems[] = $parent;
  1292. }
  1293. if ($limit === 1) {
  1294. return reset($collectionItems);
  1295. }
  1296. return self::formatResult($collectionItems, $column, $backend, $format, $parameters);
  1297. }
  1298. return array();
  1299. }
  1300. /**
  1301. * group items with link to the same source
  1302. *
  1303. * @param array $items
  1304. * @param string $itemType
  1305. * @return array of grouped items
  1306. */
  1307. protected static function groupItems($items, $itemType) {
  1308. $fileSharing = $itemType === 'file' || $itemType === 'folder';
  1309. $result = array();
  1310. foreach ($items as $item) {
  1311. $grouped = false;
  1312. foreach ($result as $key => $r) {
  1313. // for file/folder shares we need to compare file_source, otherwise we compare item_source
  1314. // only group shares if they already point to the same target, otherwise the file where shared
  1315. // before grouping of shares was added. In this case we don't group them toi avoid confusions
  1316. if (( $fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
  1317. (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
  1318. // add the first item to the list of grouped shares
  1319. if (!isset($result[$key]['grouped'])) {
  1320. $result[$key]['grouped'][] = $result[$key];
  1321. }
  1322. $result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
  1323. $result[$key]['grouped'][] = $item;
  1324. $grouped = true;
  1325. break;
  1326. }
  1327. }
  1328. if (!$grouped) {
  1329. $result[] = $item;
  1330. }
  1331. }
  1332. return $result;
  1333. }
  1334. /**
  1335. * Put shared item into the database
  1336. * @param string $itemType Item type
  1337. * @param string $itemSource Item source
  1338. * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
  1339. * @param string $shareWith User or group the item is being shared with
  1340. * @param string $uidOwner User that is the owner of shared item
  1341. * @param int $permissions CRUDS permissions
  1342. * @param boolean|array $parentFolder Parent folder target (optional)
  1343. * @param string $token (optional)
  1344. * @param string $itemSourceName name of the source item (optional)
  1345. * @param \DateTime $expirationDate (optional)
  1346. * @throws \Exception
  1347. * @return mixed id of the new share or false
  1348. */
  1349. private static function put($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
  1350. $permissions, $parentFolder = null, $token = null, $itemSourceName = null, \DateTime $expirationDate = null) {
  1351. $queriesToExecute = array();
  1352. $suggestedItemTarget = null;
  1353. $groupFileTarget = $fileTarget = $suggestedFileTarget = $filePath = '';
  1354. $groupItemTarget = $itemTarget = $fileSource = $parent = 0;
  1355. $result = self::checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate);
  1356. if(!empty($result)) {
  1357. $parent = $result['parent'];
  1358. $itemSource = $result['itemSource'];
  1359. $fileSource = $result['fileSource'];
  1360. $suggestedItemTarget = $result['suggestedItemTarget'];
  1361. $suggestedFileTarget = $result['suggestedFileTarget'];
  1362. $filePath = $result['filePath'];
  1363. }
  1364. $isGroupShare = false;
  1365. if ($shareType == self::SHARE_TYPE_GROUP) {
  1366. $isGroupShare = true;
  1367. if (isset($shareWith['users'])) {
  1368. $users = $shareWith['users'];
  1369. } else {
  1370. $group = \OC::$server->getGroupManager()->get($shareWith['group']);
  1371. if ($group) {
  1372. $users = $group->searchUsers('', -1, 0);
  1373. $userIds = [];
  1374. foreach ($users as $user) {
  1375. $userIds[] = $user->getUID();
  1376. }
  1377. $users = $userIds;
  1378. } else {
  1379. $users = [];
  1380. }
  1381. }
  1382. // remove current user from list
  1383. if (in_array(\OCP\User::getUser(), $users)) {
  1384. unset($users[array_search(\OCP\User::getUser(), $users)]);
  1385. }
  1386. $groupItemTarget = Helper::generateTarget($itemType, $itemSource,
  1387. $shareType, $shareWith['group'], $uidOwner, $suggestedItemTarget);
  1388. $groupFileTarget = Helper::generateTarget($itemType, $itemSource,
  1389. $shareType, $shareWith['group'], $uidOwner, $filePath);
  1390. // add group share to table and remember the id as parent
  1391. $queriesToExecute['groupShare'] = array(
  1392. 'itemType' => $itemType,
  1393. 'itemSource' => $itemSource,
  1394. 'itemTarget' => $groupItemTarget,
  1395. 'shareType' => $shareType,
  1396. 'shareWith' => $shareWith['group'],
  1397. 'uidOwner' => $uidOwner,
  1398. 'permissions' => $permissions,
  1399. 'shareTime' => time(),
  1400. 'fileSource' => $fileSource,
  1401. 'fileTarget' => $groupFileTarget,
  1402. 'token' => $token,
  1403. 'parent' => $parent,
  1404. 'expiration' => $expirationDate,
  1405. );
  1406. } else {
  1407. $users = array($shareWith);
  1408. $itemTarget = Helper::generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
  1409. $suggestedItemTarget);
  1410. }
  1411. $run = true;
  1412. $error = '';
  1413. $preHookData = array(
  1414. 'itemType' => $itemType,
  1415. 'itemSource' => $itemSource,
  1416. 'shareType' => $shareType,
  1417. 'uidOwner' => $uidOwner,
  1418. 'permissions' => $permissions,
  1419. 'fileSource' => $fileSource,
  1420. 'expiration' => $expirationDate,
  1421. 'token' => $token,
  1422. 'run' => &$run,
  1423. 'error' => &$error
  1424. );
  1425. $preHookData['itemTarget'] = $isGroupShare ? $groupItemTarget : $itemTarget;
  1426. $preHookData['shareWith'] = $isGroupShare ? $shareWith['group'] : $shareWith;
  1427. \OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
  1428. if ($run === false) {
  1429. throw new \Exception($error);
  1430. }
  1431. foreach ($users as $user) {
  1432. $sourceId = ($itemType === 'file' || $itemType === 'folder') ? $fileSource : $itemSource;
  1433. $sourceExists = self::getItemSharedWithBySource($itemType, $sourceId, self::FORMAT_NONE, null, true, $user);
  1434. $userShareType = $isGroupShare ? self::$shareTypeGroupUserUnique : $shareType;
  1435. if ($sourceExists && $sourceExists['item_source'] === $itemSource) {
  1436. $fileTarget = $sourceExists['file_target'];
  1437. $itemTarget = $sourceExists['item_target'];
  1438. // for group shares we don't need a additional entry if the target is the same
  1439. if($isGroupShare && $groupItemTarget === $itemTarget) {
  1440. continue;
  1441. }
  1442. } elseif(!$sourceExists && !$isGroupShare) {
  1443. $itemTarget = Helper::generateTarget($itemType, $itemSource, $userShareType, $user,
  1444. $uidOwner, $suggestedItemTarget, $parent);
  1445. if (isset($fileSource)) {
  1446. if ($parentFolder) {
  1447. if ($parentFolder === true) {
  1448. $fileTarget = Helper::generateTarget('file', $filePath, $userShareType, $user,
  1449. $uidOwner, $suggestedFileTarget, $parent);
  1450. if ($fileTarget != $groupFileTarget) {
  1451. $parentFolders[$user]['folder'] = $fileTarget;
  1452. }
  1453. } else if (isset($parentFolder[$user])) {
  1454. $fileTarget = $parentFolder[$user]['folder'].$itemSource;
  1455. $parent = $parentFolder[$user]['id'];
  1456. }
  1457. } else {
  1458. $fileTarget = Helper::generateTarget('file', $filePath, $userShareType,
  1459. $user, $uidOwner, $suggestedFileTarget, $parent);
  1460. }
  1461. } else {
  1462. $fileTarget = null;
  1463. }
  1464. } else {
  1465. // group share which doesn't exists until now, check if we need a unique target for this user
  1466. $itemTarget = Helper::generateTarget($itemType, $itemSource, self::SHARE_TYPE_USER, $user,
  1467. $uidOwner, $suggestedItemTarget, $parent);
  1468. // do we also need a file target
  1469. if (isset($fileSource)) {
  1470. $fileTarget = Helper::generateTarget('file', $filePath, self::SHARE_TYPE_USER, $user,
  1471. $uidOwner, $suggestedFileTarget, $parent);
  1472. } else {
  1473. $fileTarget = null;
  1474. }
  1475. if (($itemTarget === $groupItemTarget) &&
  1476. (!isset($fileSource) || $fileTarget === $groupFileTarget)) {
  1477. continue;
  1478. }
  1479. }
  1480. $queriesToExecute[] = array(
  1481. 'itemType' => $itemType,
  1482. 'itemSource' => $itemSource,
  1483. 'itemTarget' => $itemTarget,
  1484. 'shareType' => $userShareType,
  1485. 'shareWith' => $user,
  1486. 'uidOwner' => $uidOwner,
  1487. 'permissions' => $permissions,
  1488. 'shareTime' => time(),
  1489. 'fileSource' => $fileSource,
  1490. 'fileTarget' => $fileTarget,
  1491. 'token' => $token,
  1492. 'parent' => $parent,
  1493. 'expiration' => $expirationDate,
  1494. );
  1495. }
  1496. $id = false;
  1497. if ($isGroupShare) {
  1498. $id = self::insertShare($queriesToExecute['groupShare']);
  1499. // Save this id, any extra rows for this group share will need to reference it
  1500. $parent = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share');
  1501. unset($queriesToExecute['groupShare']);
  1502. }
  1503. foreach ($queriesToExecute as $shareQuery) {
  1504. $shareQuery['parent'] = $parent;
  1505. $id = self::insertShare($shareQuery);
  1506. }
  1507. $postHookData = array(
  1508. 'itemType' => $itemType,
  1509. 'itemSource' => $itemSource,
  1510. 'parent' => $parent,
  1511. 'shareType' => $shareType,
  1512. 'uidOwner' => $uidOwner,
  1513. 'permissions' => $permissions,
  1514. 'fileSource' => $fileSource,
  1515. 'id' => $parent,
  1516. 'token' => $token,
  1517. 'expirationDate' => $expirationDate,
  1518. );
  1519. $postHookData['shareWith'] = $isGroupShare ? $shareWith['group'] : $shareWith;
  1520. $postHookData['itemTarget'] = $isGroupShare ? $groupItemTarget : $itemTarget;
  1521. $postHookData['fileTarget'] = $isGroupShare ? $groupFileTarget : $fileTarget;
  1522. \OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
  1523. return $id ? $id : false;
  1524. }
  1525. /**
  1526. * @param string $itemType
  1527. * @param string $itemSource
  1528. * @param int $shareType
  1529. * @param string $shareWith
  1530. * @param string $uidOwner
  1531. * @param int $permissions
  1532. * @param string|null $itemSourceName
  1533. * @param null|\DateTime $expirationDate
  1534. */
  1535. private static function checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate) {
  1536. $backend = self::getBackend($itemType);
  1537. $l = \OC::$server->getL10N('lib');
  1538. $result = array();
  1539. $column = ($itemType === 'file' || $itemType === 'folder') ? 'file_source' : 'item_source';
  1540. $checkReshare = self::getItemSharedWithBySource($itemType, $itemSource, self::FORMAT_NONE, null, true);
  1541. if ($checkReshare) {
  1542. // Check if attempting to share back to owner
  1543. if ($checkReshare['uid_owner'] == $shareWith && $shareType == self::SHARE_TYPE_USER) {
  1544. $message = 'Sharing %s failed, because the user %s is the original sharer';
  1545. $message_t = $l->t('Sharing failed, because the user %s is the original sharer', [$shareWith]);
  1546. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
  1547. throw new \Exception($message_t);
  1548. }
  1549. }
  1550. if ($checkReshare && $checkReshare['uid_owner'] !== \OC_User::getUser()) {
  1551. // Check if share permissions is granted
  1552. if (self::isResharingAllowed() && (int)$checkReshare['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
  1553. if (~(int)$checkReshare['permissions'] & $permissions) {
  1554. $message = 'Sharing %s failed, because the permissions exceed permissions granted to %s';
  1555. $message_t = $l->t('Sharing %s failed, because the permissions exceed permissions granted to %s', array($itemSourceName, $uidOwner));
  1556. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $uidOwner), \OCP\Util::DEBUG);
  1557. throw new \Exception($message_t);
  1558. } else {
  1559. // TODO Don't check if inside folder
  1560. $result['parent'] = $checkReshare['id'];
  1561. $result['expirationDate'] = $expirationDate;
  1562. // $checkReshare['expiration'] could be null and then is always less than any value
  1563. if(isset($checkReshare['expiration']) && $checkReshare['expiration'] < $expirationDate) {
  1564. $result['expirationDate'] = $checkReshare['expiration'];
  1565. }
  1566. // only suggest the same name as new target if it is a reshare of the
  1567. // same file/folder and not the reshare of a child
  1568. if ($checkReshare[$column] === $itemSource) {
  1569. $result['filePath'] = $checkReshare['file_target'];
  1570. $result['itemSource'] = $checkReshare['item_source'];
  1571. $result['fileSource'] = $checkReshare['file_source'];
  1572. $result['suggestedItemTarget'] = $checkReshare['item_target'];
  1573. $result['suggestedFileTarget'] = $checkReshare['file_target'];
  1574. } else {
  1575. $result['filePath'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $backend->getFilePath($itemSource, $uidOwner) : null;
  1576. $result['suggestedItemTarget'] = null;
  1577. $result['suggestedFileTarget'] = null;
  1578. $result['itemSource'] = $itemSource;
  1579. $result['fileSource'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $itemSource : null;
  1580. }
  1581. }
  1582. } else {
  1583. $message = 'Sharing %s failed, because resharing is not allowed';
  1584. $message_t = $l->t('Sharing %s failed, because resharing is not allowed', array($itemSourceName));
  1585. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
  1586. throw new \Exception($message_t);
  1587. }
  1588. } else {
  1589. $result['parent'] = null;
  1590. $result['suggestedItemTarget'] = null;
  1591. $result['suggestedFileTarget'] = null;
  1592. $result['itemSource'] = $itemSource;
  1593. $result['expirationDate'] = $expirationDate;
  1594. if (!$backend->isValidSource($itemSource, $uidOwner)) {
  1595. $message = 'Sharing %s failed, because the sharing backend for '
  1596. .'%s could not find its source';
  1597. $message_t = $l->t('Sharing %s failed, because the sharing backend for %s could not find its source', array($itemSource, $itemType));
  1598. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource, $itemType), \OCP\Util::DEBUG);
  1599. throw new \Exception($message_t);
  1600. }
  1601. if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
  1602. $result['filePath'] = $backend->getFilePath($itemSource, $uidOwner);
  1603. if ($itemType == 'file' || $itemType == 'folder') {
  1604. $result['fileSource'] = $itemSource;
  1605. } else {
  1606. $meta = \OC\Files\Filesystem::getFileInfo($result['filePath']);
  1607. $result['fileSource'] = $meta['fileid'];
  1608. }
  1609. if ($result['fileSource'] == -1) {
  1610. $message = 'Sharing %s failed, because the file could not be found in the file cache';
  1611. $message_t = $l->t('Sharing %s failed, because the file could not be found in the file cache', array($itemSource));
  1612. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource), \OCP\Util::DEBUG);
  1613. throw new \Exception($message_t);
  1614. }
  1615. } else {
  1616. $result['filePath'] = null;
  1617. $result['fileSource'] = null;
  1618. }
  1619. }
  1620. return $result;
  1621. }
  1622. /**
  1623. *
  1624. * @param array $shareData
  1625. * @return mixed false in case of a failure or the id of the new share
  1626. */
  1627. private static function insertShare(array $shareData) {
  1628. $query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` ('
  1629. .' `item_type`, `item_source`, `item_target`, `share_type`,'
  1630. .' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
  1631. .' `file_target`, `token`, `parent`, `expiration`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)');
  1632. $query->bindValue(1, $shareData['itemType']);
  1633. $query->bindValue(2, $shareData['itemSource']);
  1634. $query->bindValue(3, $shareData['itemTarget']);
  1635. $query->bindValue(4, $shareData['shareType']);
  1636. $query->bindValue(5, $shareData['shareWith']);
  1637. $query->bindValue(6, $shareData['uidOwner']);
  1638. $query->bindValue(7, $shareData['permissions']);
  1639. $query->bindValue(8, $shareData['shareTime']);
  1640. $query->bindValue(9, $shareData['fileSource']);
  1641. $query->bindValue(10, $shareData['fileTarget']);
  1642. $query->bindValue(11, $shareData['token']);
  1643. $query->bindValue(12, $shareData['parent']);
  1644. $query->bindValue(13, $shareData['expiration'], 'datetime');
  1645. $result = $query->execute();
  1646. $id = false;
  1647. if ($result) {
  1648. $id = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share');
  1649. }
  1650. return $id;
  1651. }
  1652. /**
  1653. * In case a password protected link is not yet authenticated this function will return false
  1654. *
  1655. * @param array $linkItem
  1656. * @return boolean
  1657. */
  1658. public static function checkPasswordProtectedShare(array $linkItem) {
  1659. if (!isset($linkItem['share_with'])) {
  1660. return true;
  1661. }
  1662. if (!isset($linkItem['share_type'])) {
  1663. return true;
  1664. }
  1665. if (!isset($linkItem['id'])) {
  1666. return true;
  1667. }
  1668. if ($linkItem['share_type'] != \OCP\Share::SHARE_TYPE_LINK) {
  1669. return true;
  1670. }
  1671. if ( \OC::$server->getSession()->exists('public_link_authenticated')
  1672. && \OC::$server->getSession()->get('public_link_authenticated') === (string)$linkItem['id'] ) {
  1673. return true;
  1674. }
  1675. return false;
  1676. }
  1677. /**
  1678. * construct select statement
  1679. * @param int $format
  1680. * @param boolean $fileDependent ist it a file/folder share or a generla share
  1681. * @param string $uidOwner
  1682. * @return string select statement
  1683. */
  1684. private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
  1685. $select = '*';
  1686. if ($format == self::FORMAT_STATUSES) {
  1687. if ($fileDependent) {
  1688. $select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
  1689. . '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
  1690. . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
  1691. . '`uid_initiator`';
  1692. } else {
  1693. $select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
  1694. }
  1695. } else {
  1696. if (isset($uidOwner)) {
  1697. if ($fileDependent) {
  1698. $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
  1699. . ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
  1700. . ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
  1701. . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
  1702. } else {
  1703. $select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
  1704. . ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
  1705. }
  1706. } else {
  1707. if ($fileDependent) {
  1708. if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
  1709. $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
  1710. . '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
  1711. . '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
  1712. . '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
  1713. } else {
  1714. $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
  1715. . '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
  1716. . '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
  1717. . '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
  1718. . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
  1719. }
  1720. }
  1721. }
  1722. }
  1723. return $select;
  1724. }
  1725. /**
  1726. * transform db results
  1727. * @param array $row result
  1728. */
  1729. private static function transformDBResults(&$row) {
  1730. if (isset($row['id'])) {
  1731. $row['id'] = (int) $row['id'];
  1732. }
  1733. if (isset($row['share_type'])) {
  1734. $row['share_type'] = (int) $row['share_type'];
  1735. }
  1736. if (isset($row['parent'])) {
  1737. $row['parent'] = (int) $row['parent'];
  1738. }
  1739. if (isset($row['file_parent'])) {
  1740. $row['file_parent'] = (int) $row['file_parent'];
  1741. }
  1742. if (isset($row['file_source'])) {
  1743. $row['file_source'] = (int) $row['file_source'];
  1744. }
  1745. if (isset($row['permissions'])) {
  1746. $row['permissions'] = (int) $row['permissions'];
  1747. }
  1748. if (isset($row['storage'])) {
  1749. $row['storage'] = (int) $row['storage'];
  1750. }
  1751. if (isset($row['stime'])) {
  1752. $row['stime'] = (int) $row['stime'];
  1753. }
  1754. if (isset($row['expiration']) && $row['share_type'] !== self::SHARE_TYPE_LINK) {
  1755. // discard expiration date for non-link shares, which might have been
  1756. // set by ancient bugs
  1757. $row['expiration'] = null;
  1758. }
  1759. }
  1760. /**
  1761. * format result
  1762. * @param array $items result
  1763. * @param string $column is it a file share or a general share ('file_target' or 'item_target')
  1764. * @param \OCP\Share_Backend $backend sharing backend
  1765. * @param int $format
  1766. * @param array $parameters additional format parameters
  1767. * @return array format result
  1768. */
  1769. private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
  1770. if ($format === self::FORMAT_NONE) {
  1771. return $items;
  1772. } else if ($format === self::FORMAT_STATUSES) {
  1773. $statuses = array();
  1774. foreach ($items as $item) {
  1775. if ($item['share_type'] === self::SHARE_TYPE_LINK) {
  1776. if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
  1777. continue;
  1778. }
  1779. $statuses[$item[$column]]['link'] = true;
  1780. } else if (!isset($statuses[$item[$column]])) {
  1781. $statuses[$item[$column]]['link'] = false;
  1782. }
  1783. if (!empty($item['file_target'])) {
  1784. $statuses[$item[$column]]['path'] = $item['path'];
  1785. }
  1786. }
  1787. return $statuses;
  1788. } else {
  1789. return $backend->formatItems($items, $format, $parameters);
  1790. }
  1791. }
  1792. /**
  1793. * remove protocol from URL
  1794. *
  1795. * @param string $url
  1796. * @return string
  1797. */
  1798. public static function removeProtocolFromUrl($url) {
  1799. if (strpos($url, 'https://') === 0) {
  1800. return substr($url, strlen('https://'));
  1801. } else if (strpos($url, 'http://') === 0) {
  1802. return substr($url, strlen('http://'));
  1803. }
  1804. return $url;
  1805. }
  1806. /**
  1807. * try http post first with https and then with http as a fallback
  1808. *
  1809. * @param string $remoteDomain
  1810. * @param string $urlSuffix
  1811. * @param array $fields post parameters
  1812. * @return array
  1813. */
  1814. private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) {
  1815. $protocol = 'https://';
  1816. $result = [
  1817. 'success' => false,
  1818. 'result' => '',
  1819. ];
  1820. $try = 0;
  1821. $discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class);
  1822. while ($result['success'] === false && $try < 2) {
  1823. $federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING');
  1824. $endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
  1825. $result = \OC::$server->getHTTPHelper()->post($protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT, $fields);
  1826. $try++;
  1827. $protocol = 'http://';
  1828. }
  1829. return $result;
  1830. }
  1831. /**
  1832. * send server-to-server share to remote server
  1833. *
  1834. * @param string $token
  1835. * @param string $shareWith
  1836. * @param string $name
  1837. * @param int $remote_id
  1838. * @param string $owner
  1839. * @return bool
  1840. */
  1841. private static function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner) {
  1842. list($user, $remote) = Helper::splitUserRemote($shareWith);
  1843. if ($user && $remote) {
  1844. $url = $remote;
  1845. $local = \OC::$server->getURLGenerator()->getAbsoluteURL('/');
  1846. $fields = array(
  1847. 'shareWith' => $user,
  1848. 'token' => $token,
  1849. 'name' => $name,
  1850. 'remoteId' => $remote_id,
  1851. 'owner' => $owner,
  1852. 'remote' => $local,
  1853. );
  1854. $url = self::removeProtocolFromUrl($url);
  1855. $result = self::tryHttpPostToShareEndpoint($url, '', $fields);
  1856. $status = json_decode($result['result'], true);
  1857. if ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200)) {
  1858. \OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $remote]);
  1859. return true;
  1860. }
  1861. }
  1862. return false;
  1863. }
  1864. /**
  1865. * send server-to-server unshare to remote server
  1866. *
  1867. * @param string $remote url
  1868. * @param int $id share id
  1869. * @param string $token
  1870. * @return bool
  1871. */
  1872. private static function sendRemoteUnshare($remote, $id, $token) {
  1873. $url = rtrim($remote, '/');
  1874. $fields = array('token' => $token, 'format' => 'json');
  1875. $url = self::removeProtocolFromUrl($url);
  1876. $result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields);
  1877. $status = json_decode($result['result'], true);
  1878. return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
  1879. }
  1880. /**
  1881. * check if user can only share with group members
  1882. * @return bool
  1883. */
  1884. public static function shareWithGroupMembersOnly() {
  1885. $value = \OC::$server->getConfig()->getAppValue('core', 'shareapi_only_share_with_group_members', 'no');
  1886. return $value === 'yes';
  1887. }
  1888. /**
  1889. * @return bool
  1890. */
  1891. public static function isDefaultExpireDateEnabled() {
  1892. $defaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no');
  1893. return $defaultExpireDateEnabled === 'yes';
  1894. }
  1895. /**
  1896. * @return int
  1897. */
  1898. public static function getExpireInterval() {
  1899. return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
  1900. }
  1901. /**
  1902. * Checks whether the given path is reachable for the given owner
  1903. *
  1904. * @param string $path path relative to files
  1905. * @param string $ownerStorageId storage id of the owner
  1906. *
  1907. * @return boolean true if file is reachable, false otherwise
  1908. */
  1909. private static function isFileReachable($path, $ownerStorageId) {
  1910. // if outside the home storage, file is always considered reachable
  1911. if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
  1912. substr($ownerStorageId, 0, 13) === 'object::user:'
  1913. )) {
  1914. return true;
  1915. }
  1916. // if inside the home storage, the file has to be under "/files/"
  1917. $path = ltrim($path, '/');
  1918. if (substr($path, 0, 6) === 'files/') {
  1919. return true;
  1920. }
  1921. return false;
  1922. }
  1923. /**
  1924. * @param IConfig $config
  1925. * @return bool
  1926. */
  1927. public static function enforcePassword(IConfig $config) {
  1928. $enforcePassword = $config->getAppValue('core', 'shareapi_enforce_links_password', 'no');
  1929. return $enforcePassword === 'yes';
  1930. }
  1931. /**
  1932. * @param string $password
  1933. * @throws \Exception
  1934. */
  1935. private static function verifyPassword($password) {
  1936. $accepted = true;
  1937. $message = '';
  1938. \OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
  1939. 'password' => $password,
  1940. 'accepted' => &$accepted,
  1941. 'message' => &$message
  1942. ]);
  1943. if (!$accepted) {
  1944. throw new \Exception($message);
  1945. }
  1946. }
  1947. }