share.php 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Michael Gapczynski
  6. * @copyright 2012 Michael Gapczynski mtgap@owncloud.com
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10. * License as published by the Free Software Foundation; either
  11. * version 3 of the License, or any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public
  19. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. namespace OCP;
  22. /**
  23. * This class provides the ability for apps to share their content between users.
  24. * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
  25. *
  26. * It provides the following hooks:
  27. * - post_shared
  28. */
  29. class Share {
  30. const SHARE_TYPE_USER = 0;
  31. const SHARE_TYPE_GROUP = 1;
  32. const SHARE_TYPE_LINK = 3;
  33. const SHARE_TYPE_EMAIL = 4;
  34. const SHARE_TYPE_CONTACT = 5;
  35. const SHARE_TYPE_REMOTE = 6;
  36. /** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
  37. * Construct permissions for share() and setPermissions with Or (|) e.g. Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
  38. * Check if permission is granted with And (&) e.g. Check if delete is granted: if ($permissions & PERMISSION_DELETE)
  39. * Remove permissions with And (&) and Not (~) e.g. Remove the update permission: $permissions &= ~PERMISSION_UPDATE
  40. * Apps are required to handle permissions on their own, this class only stores and manages the permissions of shares
  41. * @see lib/public/constants.php
  42. */
  43. const FORMAT_NONE = -1;
  44. const FORMAT_STATUSES = -2;
  45. const FORMAT_SOURCES = -3;
  46. const TOKEN_LENGTH = 32; // see db_structure.xml
  47. private static $shareTypeUserAndGroups = -1;
  48. private static $shareTypeGroupUserUnique = 2;
  49. private static $backends = array();
  50. private static $backendTypes = array();
  51. /**
  52. * @brief Register a sharing backend class that implements OCP\Share_Backend for an item type
  53. * @param string Item type
  54. * @param string Backend class
  55. * @param string (optional) Depends on item type
  56. * @param array (optional) List of supported file extensions if this item type depends on files
  57. * @return Returns true if backend is registered or false if error
  58. */
  59. public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
  60. if (self::isEnabled()) {
  61. if (!isset(self::$backendTypes[$itemType])) {
  62. self::$backendTypes[$itemType] = array('class' => $class, 'collectionOf' => $collectionOf, 'supportedFileExtensions' => $supportedFileExtensions);
  63. if(count(self::$backendTypes) === 1) {
  64. \OC_Util::addScript('core', 'share');
  65. \OC_Util::addStyle('core', 'share');
  66. }
  67. return true;
  68. }
  69. \OC_Log::write('OCP\Share', 'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class'].' is already registered for '.$itemType, \OC_Log::WARN);
  70. }
  71. return false;
  72. }
  73. /**
  74. * @brief Check if the Share API is enabled
  75. * @return Returns true if enabled or false
  76. *
  77. * The Share API is enabled by default if not configured
  78. *
  79. */
  80. public static function isEnabled() {
  81. if (\OC_Appconfig::getValue('core', 'shareapi_enabled', 'yes') == 'yes') {
  82. return true;
  83. }
  84. return false;
  85. }
  86. /**
  87. * @brief Get the items of item type shared with the current user
  88. * @param string Item type
  89. * @param int Format (optional) Format type must be defined by the backend
  90. * @param int Number of items to return (optional) Returns all by default
  91. * @return Return depends on format
  92. */
  93. public static function getItemsSharedWith($itemType, $format = self::FORMAT_NONE, $parameters = null, $limit = -1, $includeCollections = false) {
  94. return self::getItems($itemType, null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format, $parameters, $limit, $includeCollections);
  95. }
  96. /**
  97. * @brief Get the item of item type shared with the current user
  98. * @param string Item type
  99. * @param string Item target
  100. * @param int Format (optional) Format type must be defined by the backend
  101. * @return Return depends on format
  102. */
  103. public static function getItemSharedWith($itemType, $itemTarget, $format = self::FORMAT_NONE, $parameters = null, $includeCollections = false) {
  104. return self::getItems($itemType, $itemTarget, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format, $parameters, 1, $includeCollections);
  105. }
  106. /**
  107. * @brief Get the item of item type shared with the current user by source
  108. * @param string Item type
  109. * @param string Item source
  110. * @param int Format (optional) Format type must be defined by the backend
  111. * @return Return depends on format
  112. */
  113. public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE, $parameters = null, $includeCollections = false) {
  114. return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format, $parameters, 1, $includeCollections, true);
  115. }
  116. /**
  117. * @brief Get the item of item type shared by a link
  118. * @param string Item type
  119. * @param string Item source
  120. * @param string Owner of link
  121. * @return Item
  122. */
  123. public static function getItemSharedWithByLink($itemType, $itemSource, $uidOwner) {
  124. return self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null, $uidOwner, self::FORMAT_NONE, null, 1);
  125. }
  126. /**
  127. * @brief Get the item shared by a token
  128. * @param string token
  129. * @return Item
  130. */
  131. public static function getShareByToken($token) {
  132. $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?',1);
  133. $result = $query->execute(array($token));
  134. if (\OC_DB::isError($result)) {
  135. \OC_Log::write('OCP\Share', \OC_DB::getErrorMessage($result) . ', token=' . $token, \OC_Log::ERROR);
  136. }
  137. return $result->fetchRow();
  138. }
  139. /**
  140. * @brief Get the shared items of item type owned by the current user
  141. * @param string Item type
  142. * @param int Format (optional) Format type must be defined by the backend
  143. * @param int Number of items to return (optional) Returns all by default
  144. * @return Return depends on format
  145. */
  146. public static function getItemsShared($itemType, $format = self::FORMAT_NONE, $parameters = null, $limit = -1, $includeCollections = false) {
  147. return self::getItems($itemType, null, null, null, \OC_User::getUser(), $format, $parameters, $limit, $includeCollections);
  148. }
  149. /**
  150. * @brief Get the shared item of item type owned by the current user
  151. * @param string Item type
  152. * @param string Item source
  153. * @param int Format (optional) Format type must be defined by the backend
  154. * @return Return depends on format
  155. */
  156. public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE, $parameters = null, $includeCollections = false) {
  157. return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format, $parameters, -1, $includeCollections);
  158. }
  159. /**
  160. * @brief Share an item with a user, group, or via private link
  161. * @param string Item type
  162. * @param string Item source
  163. * @param int SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
  164. * @param string User or group the item is being shared with
  165. * @param int CRUDS permissions
  166. * @return bool|string Returns true on success or false on failure, Returns token on success for links
  167. */
  168. public static function shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions) {
  169. $uidOwner = \OC_User::getUser();
  170. $sharingPolicy = \OC_Appconfig::getValue('core', 'shareapi_share_policy', 'global');
  171. // Verify share type and sharing conditions are met
  172. if ($shareType === self::SHARE_TYPE_USER) {
  173. if ($shareWith == $uidOwner) {
  174. $message = 'Sharing '.$itemSource.' failed, because the user '.$shareWith.' is the item owner';
  175. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  176. throw new \Exception($message);
  177. }
  178. if (!\OC_User::userExists($shareWith)) {
  179. $message = 'Sharing '.$itemSource.' failed, because the user '.$shareWith.' does not exist';
  180. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  181. throw new \Exception($message);
  182. }
  183. if ($sharingPolicy == 'groups_only') {
  184. $inGroup = array_intersect(\OC_Group::getUserGroups($uidOwner), \OC_Group::getUserGroups($shareWith));
  185. if (empty($inGroup)) {
  186. $message = 'Sharing '.$itemSource.' failed, because the user '.$shareWith.' is not a member of any groups that '.$uidOwner.' is a member of';
  187. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  188. throw new \Exception($message);
  189. }
  190. }
  191. // Check if the item source is already shared with the user, either from the same owner or a different user
  192. if ($checkExists = self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
  193. // Only allow the same share to occur again if it is the same owner and is not a user share, this use case is for increasing permissions for a specific user
  194. if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
  195. $message = 'Sharing '.$itemSource.' failed, because this item is already shared with '.$shareWith;
  196. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  197. throw new \Exception($message);
  198. }
  199. }
  200. } else if ($shareType === self::SHARE_TYPE_GROUP) {
  201. if (!\OC_Group::groupExists($shareWith)) {
  202. $message = 'Sharing '.$itemSource.' failed, because the group '.$shareWith.' does not exist';
  203. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  204. throw new \Exception($message);
  205. }
  206. if ($sharingPolicy == 'groups_only' && !\OC_Group::inGroup($uidOwner, $shareWith)) {
  207. $message = 'Sharing '.$itemSource.' failed, because '.$uidOwner.' is not a member of the group '.$shareWith;
  208. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  209. throw new \Exception($message);
  210. }
  211. // Check if the item source is already shared with the group, either from the same owner or a different user
  212. // The check for each user in the group is done inside the put() function
  213. if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_GROUP, $shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
  214. // Only allow the same share to occur again if it is the same owner and is not a group share, this use case is for increasing permissions for a specific user
  215. if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
  216. $message = 'Sharing '.$itemSource.' failed, because this item is already shared with '.$shareWith;
  217. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  218. throw new \Exception($message);
  219. }
  220. }
  221. // Convert share with into an array with the keys group and users
  222. $group = $shareWith;
  223. $shareWith = array();
  224. $shareWith['group'] = $group;
  225. $shareWith['users'] = array_diff(\OC_Group::usersInGroup($group), array($uidOwner));
  226. } else if ($shareType === self::SHARE_TYPE_LINK) {
  227. if (\OC_Appconfig::getValue('core', 'shareapi_allow_links', 'yes') == 'yes') {
  228. // when updating a link share
  229. if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null, $uidOwner, self::FORMAT_NONE, null, 1)) {
  230. // remember old token
  231. $oldToken = $checkExists['token'];
  232. //delete the old share
  233. self::delete($checkExists['id']);
  234. }
  235. // Generate hash of password - same method as user passwords
  236. if (isset($shareWith)) {
  237. $forcePortable = (CRYPT_BLOWFISH != 1);
  238. $hasher = new \PasswordHash(8, $forcePortable);
  239. $shareWith = $hasher->HashPassword($shareWith.\OC_Config::getValue('passwordsalt', ''));
  240. }
  241. // Generate token
  242. if (isset($oldToken)) {
  243. $token = $oldToken;
  244. } else {
  245. $token = \OC_Util::generate_random_bytes(self::TOKEN_LENGTH);
  246. }
  247. $result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, $token);
  248. if ($result) {
  249. return $token;
  250. } else {
  251. return false;
  252. }
  253. }
  254. $message = 'Sharing '.$itemSource.' failed, because sharing with links is not allowed';
  255. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  256. throw new \Exception($message);
  257. return false;
  258. // } else if ($shareType === self::SHARE_TYPE_CONTACT) {
  259. // if (!\OC_App::isEnabled('contacts')) {
  260. // $message = 'Sharing '.$itemSource.' failed, because the contacts app is not enabled';
  261. // \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  262. // return false;
  263. // }
  264. // $vcard = \OC_Contacts_App::getContactVCard($shareWith);
  265. // if (!isset($vcard)) {
  266. // $message = 'Sharing '.$itemSource.' failed, because the contact does not exist';
  267. // \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  268. // throw new \Exception($message);
  269. // }
  270. // $details = \OC_Contacts_VCard::structureContact($vcard);
  271. // // TODO Add ownCloud user to contacts vcard
  272. // if (!isset($details['EMAIL'])) {
  273. // $message = 'Sharing '.$itemSource.' failed, because no email address is associated with the contact';
  274. // \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  275. // throw new \Exception($message);
  276. // }
  277. // return self::shareItem($itemType, $itemSource, self::SHARE_TYPE_EMAIL, $details['EMAIL'], $permissions);
  278. } else {
  279. // Future share types need to include their own conditions
  280. $message = 'Share type '.$shareType.' is not valid for '.$itemSource;
  281. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  282. throw new \Exception($message);
  283. }
  284. // If the item is a folder, scan through the folder looking for equivalent item types
  285. // if ($itemType == 'folder') {
  286. // $parentFolder = self::put('folder', $itemSource, $shareType, $shareWith, $uidOwner, $permissions, true);
  287. // if ($parentFolder && $files = \OC\Files\Filesystem::getDirectoryContent($itemSource)) {
  288. // for ($i = 0; $i < count($files); $i++) {
  289. // $name = substr($files[$i]['name'], strpos($files[$i]['name'], $itemSource) - strlen($itemSource));
  290. // if ($files[$i]['mimetype'] == 'httpd/unix-directory'
  291. // && $children = \OC\Files\Filesystem::getDirectoryContent($name, '/')
  292. // ) {
  293. // // Continue scanning into child folders
  294. // array_push($files, $children);
  295. // } else {
  296. // // Check file extension for an equivalent item type to convert to
  297. // $extension = strtolower(substr($itemSource, strrpos($itemSource, '.') + 1));
  298. // foreach (self::$backends as $type => $backend) {
  299. // if (isset($backend->dependsOn) && $backend->dependsOn == 'file' && isset($backend->supportedFileExtensions) && in_array($extension, $backend->supportedFileExtensions)) {
  300. // $itemType = $type;
  301. // break;
  302. // }
  303. // }
  304. // // Pass on to put() to check if this item should be converted, the item won't be inserted into the database unless it can be converted
  305. // self::put($itemType, $name, $shareType, $shareWith, $uidOwner, $permissions, $parentFolder);
  306. // }
  307. // }
  308. // return true;
  309. // }
  310. // return false;
  311. // } else {
  312. // Put the item into the database
  313. return self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions);
  314. // }
  315. }
  316. /**
  317. * @brief Unshare an item from a user, group, or delete a private link
  318. * @param string Item type
  319. * @param string Item source
  320. * @param int SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
  321. * @param string User or group the item is being shared with
  322. * @return Returns true on success or false on failure
  323. */
  324. public static function unshare($itemType, $itemSource, $shareType, $shareWith) {
  325. if ($item = self::getItems($itemType, $itemSource, $shareType, $shareWith, \OC_User::getUser(), self::FORMAT_NONE, null, 1)) {
  326. // Pass all the vars we have for now, they may be useful
  327. \OC_Hook::emit('OCP\Share', 'pre_unshare', array(
  328. 'itemType' => $itemType,
  329. 'itemSource' => $itemSource,
  330. 'shareType' => $shareType,
  331. 'shareWith' => $shareWith,
  332. ));
  333. self::delete($item['id']);
  334. return true;
  335. }
  336. return false;
  337. }
  338. /**
  339. * @brief Unshare an item from all users, groups, and remove all links
  340. * @param string Item type
  341. * @param string Item source
  342. * @return Returns true on success or false on failure
  343. */
  344. public static function unshareAll($itemType, $itemSource) {
  345. if ($shares = self::getItemShared($itemType, $itemSource)) {
  346. // Pass all the vars we have for now, they may be useful
  347. \OC_Hook::emit('OCP\Share', 'pre_unshareAll', array(
  348. 'itemType' => $itemType,
  349. 'itemSource' => $itemSource,
  350. 'shares' => $shares
  351. ));
  352. foreach ($shares as $share) {
  353. self::delete($share['id']);
  354. }
  355. return true;
  356. }
  357. return false;
  358. }
  359. /**
  360. * @brief Unshare an item shared with the current user
  361. * @param string Item type
  362. * @param string Item target
  363. * @return Returns true on success or false on failure
  364. *
  365. * Unsharing from self is not allowed for items inside collections
  366. *
  367. */
  368. public static function unshareFromSelf($itemType, $itemTarget) {
  369. if ($item = self::getItemSharedWith($itemType, $itemTarget)) {
  370. if ((int)$item['share_type'] === self::SHARE_TYPE_GROUP) {
  371. // Insert an extra row for the group share and set permission to 0 to prevent it from showing up for the user
  372. $query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (`item_type`, `item_source`, `item_target`, `parent`, `share_type`, `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`, `file_target`) VALUES (?,?,?,?,?,?,?,?,?,?,?)');
  373. $query->execute(array($item['item_type'], $item['item_source'], $item['item_target'], $item['id'], self::$shareTypeGroupUserUnique, \OC_User::getUser(), $item['uid_owner'], 0, $item['stime'], $item['file_source'], $item['file_target']));
  374. \OC_DB::insertid('*PREFIX*share');
  375. // Delete all reshares by this user of the group share
  376. self::delete($item['id'], true, \OC_User::getUser());
  377. } else if ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
  378. // Set permission to 0 to prevent it from showing up for the user
  379. $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = ? WHERE `id` = ?');
  380. $query->execute(array(0, $item['id']));
  381. self::delete($item['id'], true);
  382. } else {
  383. self::delete($item['id']);
  384. }
  385. return true;
  386. }
  387. return false;
  388. }
  389. /**
  390. * @brief Set the permissions of an item for a specific user or group
  391. * @param string Item type
  392. * @param string Item source
  393. * @param int SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
  394. * @param string User or group the item is being shared with
  395. * @param int CRUDS permissions
  396. * @return Returns true on success or false on failure
  397. */
  398. public static function setPermissions($itemType, $itemSource, $shareType, $shareWith, $permissions) {
  399. if ($item = self::getItems($itemType, $itemSource, $shareType, $shareWith, \OC_User::getUser(), self::FORMAT_NONE, null, 1, false)) {
  400. // Check if this item is a reshare and verify that the permissions granted don't exceed the parent shared item
  401. if (isset($item['parent'])) {
  402. $query = \OC_DB::prepare('SELECT `permissions` FROM `*PREFIX*share` WHERE `id` = ?', 1);
  403. $result = $query->execute(array($item['parent']))->fetchRow();
  404. if (~(int)$result['permissions'] & $permissions) {
  405. $message = 'Setting permissions for '.$itemSource.' failed, because the permissions exceed permissions granted to '.\OC_User::getUser();
  406. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  407. throw new \Exception($message);
  408. }
  409. }
  410. $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = ? WHERE `id` = ?');
  411. $query->execute(array($permissions, $item['id']));
  412. // Check if permissions were removed
  413. if ($item['permissions'] & ~$permissions) {
  414. // If share permission is removed all reshares must be deleted
  415. if (($item['permissions'] & PERMISSION_SHARE) && (~$permissions & PERMISSION_SHARE)) {
  416. self::delete($item['id'], true);
  417. } else {
  418. $ids = array();
  419. $parents = array($item['id']);
  420. while (!empty($parents)) {
  421. $parents = "'".implode("','", $parents)."'";
  422. $query = \OC_DB::prepare('SELECT `id`, `permissions` FROM `*PREFIX*share` WHERE `parent` IN ('.$parents.')');
  423. $result = $query->execute();
  424. // Reset parents array, only go through loop again if items are found that need permissions removed
  425. $parents = array();
  426. while ($item = $result->fetchRow()) {
  427. // Check if permissions need to be removed
  428. if ($item['permissions'] & ~$permissions) {
  429. // Add to list of items that need permissions removed
  430. $ids[] = $item['id'];
  431. $parents[] = $item['id'];
  432. }
  433. }
  434. }
  435. // Remove the permissions for all reshares of this item
  436. if (!empty($ids)) {
  437. $ids = "'".implode("','", $ids)."'";
  438. $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = `permissions` & ? WHERE `id` IN ('.$ids.')');
  439. $query->execute(array($permissions));
  440. }
  441. }
  442. }
  443. return true;
  444. }
  445. $message = 'Setting permissions for '.$itemSource.' failed, because the item was not found';
  446. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  447. throw new \Exception($message);
  448. }
  449. public static function setExpirationDate($itemType, $itemSource, $date) {
  450. if ($items = self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), self::FORMAT_NONE, null, -1, false)) {
  451. if (!empty($items)) {
  452. if ($date == '') {
  453. $date = null;
  454. } else {
  455. $date = new \DateTime($date);
  456. $date = date('Y-m-d H:i', $date->format('U') - $date->getOffset());
  457. }
  458. $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `expiration` = ? WHERE `id` = ?');
  459. foreach ($items as $item) {
  460. $query->execute(array($date, $item['id']));
  461. }
  462. return true;
  463. }
  464. }
  465. return false;
  466. }
  467. /**
  468. * @brief Get the backend class for the specified item type
  469. * @param string Item type
  470. * @return Sharing backend object
  471. */
  472. private static function getBackend($itemType) {
  473. if (isset(self::$backends[$itemType])) {
  474. return self::$backends[$itemType];
  475. } else if (isset(self::$backendTypes[$itemType]['class'])) {
  476. $class = self::$backendTypes[$itemType]['class'];
  477. if (class_exists($class)) {
  478. self::$backends[$itemType] = new $class;
  479. if (!(self::$backends[$itemType] instanceof Share_Backend)) {
  480. $message = 'Sharing backend '.$class.' must implement the interface OCP\Share_Backend';
  481. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  482. throw new \Exception($message);
  483. }
  484. return self::$backends[$itemType];
  485. } else {
  486. $message = 'Sharing backend '.$class.' not found';
  487. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  488. throw new \Exception($message);
  489. }
  490. }
  491. $message = 'Sharing backend for '.$itemType.' not found';
  492. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  493. throw new \Exception($message);
  494. }
  495. /**
  496. * @brief Get a list of collection item types for the specified item type
  497. * @param string Item type
  498. * @return array
  499. */
  500. private static function getCollectionItemTypes($itemType) {
  501. $collectionTypes = array($itemType);
  502. foreach (self::$backendTypes as $type => $backend) {
  503. if (in_array($backend['collectionOf'], $collectionTypes)) {
  504. $collectionTypes[] = $type;
  505. }
  506. }
  507. // TODO Add option for collections to be collection of themselves, only 'folder' does it now...
  508. if (!self::getBackend($itemType) instanceof Share_Backend_Collection || $itemType != 'folder') {
  509. unset($collectionTypes[0]);
  510. }
  511. // Return array if collections were found or the item type is a collection itself - collections can be inside collections
  512. if (count($collectionTypes) > 0) {
  513. return $collectionTypes;
  514. }
  515. return false;
  516. }
  517. /**
  518. * @brief Get shared items from the database
  519. * @param string Item type
  520. * @param string Item source or target (optional)
  521. * @param int SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
  522. * @param string User or group the item is being shared with
  523. * @param string User that is the owner of shared items (optional)
  524. * @param int Format to convert items to with formatItems()
  525. * @param mixed Parameters to pass to formatItems()
  526. * @param int Number of items to return, -1 to return all matches (optional)
  527. * @param bool Include collection item types (optional)
  528. * @return mixed
  529. *
  530. * See public functions getItem(s)... for parameter usage
  531. *
  532. */
  533. private static function getItems($itemType, $item = null, $shareType = null, $shareWith = null, $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1, $includeCollections = false, $itemShareWithBySource = false) {
  534. if (!self::isEnabled()) {
  535. if ($limit == 1 || (isset($uidOwner) && isset($item))) {
  536. return false;
  537. } else {
  538. return array();
  539. }
  540. }
  541. $backend = self::getBackend($itemType);
  542. // Get filesystem root to add it to the file target and remove from the file source, match file_source with the file cache
  543. if ($itemType == 'file' || $itemType == 'folder') {
  544. $root = \OC\Files\Filesystem::getRoot();
  545. $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid`';
  546. if (!isset($item)) {
  547. $where .= ' WHERE `file_target` IS NOT NULL';
  548. }
  549. $fileDependent = true;
  550. $queryArgs = array();
  551. } else {
  552. $fileDependent = false;
  553. $root = '';
  554. if ($includeCollections && !isset($item) && ($collectionTypes = self::getCollectionItemTypes($itemType))) {
  555. // If includeCollections is true, find collections of this item type, e.g. a music album contains songs
  556. if (!in_array($itemType, $collectionTypes)) {
  557. $itemTypes = array_merge(array($itemType), $collectionTypes);
  558. } else {
  559. $itemTypes = $collectionTypes;
  560. }
  561. $placeholders = join(',', array_fill(0, count($itemTypes), '?'));
  562. $where = ' WHERE `item_type` IN ('.$placeholders.'))';
  563. $queryArgs = $itemTypes;
  564. } else {
  565. $where = ' WHERE `item_type` = ?';
  566. $queryArgs = array($itemType);
  567. }
  568. }
  569. if (isset($shareType)) {
  570. // Include all user and group items
  571. if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
  572. $where .= ' AND `share_type` IN (?,?,?)';
  573. $queryArgs[] = self::SHARE_TYPE_USER;
  574. $queryArgs[] = self::SHARE_TYPE_GROUP;
  575. $queryArgs[] = self::$shareTypeGroupUserUnique;
  576. $userAndGroups = array_merge(array($shareWith), \OC_Group::getUserGroups($shareWith));
  577. $placeholders = join(',', array_fill(0, count($userAndGroups), '?'));
  578. $where .= ' AND `share_with` IN ('.$placeholders.')';
  579. $queryArgs = array_merge($queryArgs, $userAndGroups);
  580. // Don't include own group shares
  581. $where .= ' AND `uid_owner` != ?';
  582. $queryArgs[] = $shareWith;
  583. } else {
  584. $where .= ' AND `share_type` = ?';
  585. $queryArgs[] = $shareType;
  586. if (isset($shareWith)) {
  587. $where .= ' AND `share_with` = ?';
  588. $queryArgs[] = $shareWith;
  589. }
  590. }
  591. }
  592. if (isset($uidOwner)) {
  593. $where .= ' AND `uid_owner` = ?';
  594. $queryArgs[] = $uidOwner;
  595. if (!isset($shareType)) {
  596. // Prevent unique user targets for group shares from being selected
  597. $where .= ' AND `share_type` != ?';
  598. $queryArgs[] = self::$shareTypeGroupUserUnique;
  599. }
  600. if ($itemType == 'file' || $itemType == 'folder') {
  601. $column = 'file_source';
  602. } else {
  603. $column = 'item_source';
  604. }
  605. } else {
  606. if ($itemType == 'file' || $itemType == 'folder') {
  607. $column = 'file_target';
  608. } else {
  609. $column = 'item_target';
  610. }
  611. }
  612. if (isset($item)) {
  613. if ($includeCollections && $collectionTypes = self::getCollectionItemTypes($itemType)) {
  614. $where .= ' AND (';
  615. } else {
  616. $where .= ' AND';
  617. }
  618. // If looking for own shared items, check item_source else check item_target
  619. if (isset($uidOwner) || $itemShareWithBySource) {
  620. // If item type is a file, file source needs to be checked in case the item was converted
  621. if ($itemType == 'file' || $itemType == 'folder') {
  622. $where .= ' `file_source` = ?';
  623. $column = 'file_source';
  624. } else {
  625. $where .= ' `item_source` = ?';
  626. $column = 'item_source';
  627. }
  628. } else {
  629. if ($itemType == 'file' || $itemType == 'folder') {
  630. $where .= ' `file_target` = ?';
  631. $item = \OC\Files\Filesystem::normalizePath($item);
  632. } else {
  633. $where .= ' `item_target` = ?';
  634. }
  635. }
  636. $queryArgs[] = $item;
  637. if ($includeCollections && $collectionTypes) {
  638. $placeholders = join(',', array_fill(0, count($collectionTypes), '?'));
  639. $where .= ' OR `item_type` IN ('.$placeholders.'))';
  640. $queryArgs = array_merge($queryArgs, $collectionTypes);
  641. }
  642. }
  643. if ($limit != -1 && !$includeCollections) {
  644. if ($shareType == self::$shareTypeUserAndGroups) {
  645. // Make sure the unique user target is returned if it exists, unique targets should follow the group share in the database
  646. // If the limit is not 1, the filtering can be done later
  647. $where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
  648. }
  649. // The limit must be at least 3, because filtering needs to be done
  650. if ($limit < 3) {
  651. $queryLimit = 3;
  652. } else {
  653. $queryLimit = $limit;
  654. }
  655. } else {
  656. $queryLimit = null;
  657. }
  658. // TODO Optimize selects
  659. if ($format == self::FORMAT_STATUSES) {
  660. if ($itemType == 'file' || $itemType == 'folder') {
  661. $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `share_type`, `file_source`, `path`, `expiration`';
  662. } else {
  663. $select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `expiration`';
  664. }
  665. } else {
  666. if (isset($uidOwner)) {
  667. if ($itemType == 'file' || $itemType == 'folder') {
  668. $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `share_type`, `share_with`, `file_source`, `path`, `permissions`, `stime`, `expiration`, `token`';
  669. } else {
  670. $select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `permissions`, `stime`, `file_source`, `expiration`, `token`';
  671. }
  672. } else {
  673. if ($fileDependent) {
  674. if (($itemType == 'file' || $itemType == 'folder')
  675. && $format == \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS
  676. || $format == \OC_Share_Backend_File::FORMAT_FILE_APP_ROOT
  677. ) {
  678. $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `uid_owner`, '
  679. .'`share_type`, `share_with`, `file_source`, `path`, `file_target`, '
  680. .'`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
  681. .'`name` `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`';
  682. } else {
  683. $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`, `*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`, `file_source`, `path`, `file_target`, `permissions`, `stime`, `expiration`, `token`';
  684. }
  685. } else {
  686. $select = '*';
  687. }
  688. }
  689. }
  690. $root = strlen($root);
  691. $query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
  692. $result = $query->execute($queryArgs);
  693. if (\OC_DB::isError($result)) {
  694. \OC_Log::write('OCP\Share', \OC_DB::getErrorMessage($result) . ', select=' . $select . ' where=' . $where, \OC_Log::ERROR);
  695. }
  696. $items = array();
  697. $targets = array();
  698. $switchedItems = array();
  699. while ($row = $result->fetchRow()) {
  700. // Filter out duplicate group shares for users with unique targets
  701. if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
  702. $row['share_type'] = self::SHARE_TYPE_GROUP;
  703. $row['share_with'] = $items[$row['parent']]['share_with'];
  704. // Remove the parent group share
  705. unset($items[$row['parent']]);
  706. if ($row['permissions'] == 0) {
  707. continue;
  708. }
  709. } else if (!isset($uidOwner)) {
  710. // Check if the same target already exists
  711. if (isset($targets[$row[$column]])) {
  712. // Check if the same owner shared with the user twice through a group and user share - this is allowed
  713. $id = $targets[$row[$column]];
  714. if ($items[$id]['uid_owner'] == $row['uid_owner']) {
  715. // Switch to group share type to ensure resharing conditions aren't bypassed
  716. if ($items[$id]['share_type'] != self::SHARE_TYPE_GROUP) {
  717. $items[$id]['share_type'] = self::SHARE_TYPE_GROUP;
  718. $items[$id]['share_with'] = $row['share_with'];
  719. }
  720. // Switch ids if sharing permission is granted on only one share to ensure correct parent is used if resharing
  721. if (~(int)$items[$id]['permissions'] & PERMISSION_SHARE && (int)$row['permissions'] & PERMISSION_SHARE) {
  722. $items[$row['id']] = $items[$id];
  723. $switchedItems[$id] = $row['id'];
  724. unset($items[$id]);
  725. $id = $row['id'];
  726. }
  727. // Combine the permissions for the item
  728. $items[$id]['permissions'] |= (int)$row['permissions'];
  729. continue;
  730. }
  731. } else {
  732. $targets[$row[$column]] = $row['id'];
  733. }
  734. }
  735. // Remove root from file source paths if retrieving own shared items
  736. if (isset($uidOwner) && isset($row['path'])) {
  737. if (isset($row['parent'])) {
  738. $row['path'] = '/Shared/'.basename($row['path']);
  739. } else {
  740. // Strip 'files' from path
  741. $row['path'] = substr($row['path'], 5);
  742. }
  743. }
  744. if (isset($row['expiration'])) {
  745. $time = new \DateTime();
  746. if ($row['expiration'] < date('Y-m-d H:i', $time->format('U') - $time->getOffset())) {
  747. self::delete($row['id']);
  748. continue;
  749. }
  750. }
  751. // Add display names to result
  752. if ( isset($row['share_with']) && $row['share_with'] != '') {
  753. $row['share_with_displayname'] = \OCP\User::getDisplayName($row['share_with']);
  754. }
  755. if ( isset($row['uid_owner']) && $row['uid_owner'] != '') {
  756. $row['displayname_owner'] = \OCP\User::getDisplayName($row['uid_owner']);
  757. }
  758. $items[$row['id']] = $row;
  759. }
  760. if (!empty($items)) {
  761. $collectionItems = array();
  762. foreach ($items as &$row) {
  763. // Return only the item instead of a 2-dimensional array
  764. if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) {
  765. if ($format == self::FORMAT_NONE) {
  766. return $row;
  767. } else {
  768. break;
  769. }
  770. }
  771. // Check if this is a collection of the requested item type
  772. if ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
  773. if (($collectionBackend = self::getBackend($row['item_type'])) && $collectionBackend instanceof Share_Backend_Collection) {
  774. // Collections can be inside collections, check if the item is a collection
  775. if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
  776. $collectionItems[] = $row;
  777. } else {
  778. $collection = array();
  779. $collection['item_type'] = $row['item_type'];
  780. if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
  781. $collection['path'] = basename($row['path']);
  782. }
  783. $row['collection'] = $collection;
  784. // Fetch all of the children sources
  785. $children = $collectionBackend->getChildren($row[$column]);
  786. foreach ($children as $child) {
  787. $childItem = $row;
  788. $childItem['item_type'] = $itemType;
  789. if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
  790. $childItem['item_source'] = $child['source'];
  791. $childItem['item_target'] = $child['target'];
  792. }
  793. if ($backend instanceof Share_Backend_File_Dependent) {
  794. if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
  795. $childItem['file_source'] = $child['source'];
  796. } else {
  797. $meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
  798. $childItem['file_source'] = $meta['fileid'];
  799. }
  800. $childItem['file_target'] = \OC\Files\Filesystem::normalizePath($child['file_path']);
  801. }
  802. if (isset($item)) {
  803. if ($childItem[$column] == $item) {
  804. // Return only the item instead of a 2-dimensional array
  805. if ($limit == 1) {
  806. if ($format == self::FORMAT_NONE) {
  807. return $childItem;
  808. } else {
  809. // Unset the items array and break out of both loops
  810. $items = array();
  811. $items[] = $childItem;
  812. break 2;
  813. }
  814. } else {
  815. $collectionItems[] = $childItem;
  816. }
  817. }
  818. } else {
  819. $collectionItems[] = $childItem;
  820. }
  821. }
  822. }
  823. }
  824. // Remove collection item
  825. $toRemove = $row['id'];
  826. if (array_key_exists($toRemove, $switchedItems)) {
  827. $toRemove = $switchedItems[$toRemove];
  828. }
  829. unset($items[$toRemove]);
  830. }
  831. }
  832. if (!empty($collectionItems)) {
  833. $items = array_merge($items, $collectionItems);
  834. }
  835. if (empty($items) && $limit == 1) {
  836. return false;
  837. }
  838. if ($format == self::FORMAT_NONE) {
  839. return $items;
  840. } else if ($format == self::FORMAT_STATUSES) {
  841. $statuses = array();
  842. // Switch column to path for files and folders, used for determining statuses inside of folders
  843. if ($itemType == 'file' || $itemType == 'folder') {
  844. $column = 'path';
  845. }
  846. foreach ($items as $item) {
  847. if ($item['share_type'] == self::SHARE_TYPE_LINK) {
  848. $statuses[$item[$column]] = true;
  849. } else if (!isset($statuses[$item[$column]])) {
  850. $statuses[$item[$column]] = false;
  851. }
  852. }
  853. return $statuses;
  854. } else {
  855. return $backend->formatItems($items, $format, $parameters);
  856. }
  857. } else if ($limit == 1 || (isset($uidOwner) && isset($item))) {
  858. return false;
  859. }
  860. return array();
  861. }
  862. /**
  863. * @brief Put shared item into the database
  864. * @param string Item type
  865. * @param string Item source
  866. * @param int SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
  867. * @param string User or group the item is being shared with
  868. * @param int CRUDS permissions
  869. * @param bool|array Parent folder target (optional)
  870. * @return bool Returns true on success or false on failure
  871. */
  872. private static function put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $parentFolder = null, $token = null) {
  873. $backend = self::getBackend($itemType);
  874. // Check if this is a reshare
  875. if ($checkReshare = self::getItemSharedWithBySource($itemType, $itemSource, self::FORMAT_NONE, null, true)) {
  876. // Check if attempting to share back to owner
  877. if ($checkReshare['uid_owner'] == $shareWith && $shareType == self::SHARE_TYPE_USER) {
  878. $message = 'Sharing '.$itemSource.' failed, because the user '.$shareWith.' is the original sharer';
  879. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  880. throw new \Exception($message);
  881. }
  882. // Check if share permissions is granted
  883. if ((int)$checkReshare['permissions'] & PERMISSION_SHARE) {
  884. if (~(int)$checkReshare['permissions'] & $permissions) {
  885. $message = 'Sharing '.$itemSource.' failed, because the permissions exceed permissions granted to '.$uidOwner;
  886. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  887. throw new \Exception($message);
  888. } else {
  889. // TODO Don't check if inside folder
  890. $parent = $checkReshare['id'];
  891. $itemSource = $checkReshare['item_source'];
  892. $fileSource = $checkReshare['file_source'];
  893. $suggestedItemTarget = $checkReshare['item_target'];
  894. $suggestedFileTarget = $checkReshare['file_target'];
  895. $filePath = $checkReshare['file_target'];
  896. }
  897. } else {
  898. $message = 'Sharing '.$itemSource.' failed, because resharing is not allowed';
  899. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  900. throw new \Exception($message);
  901. }
  902. } else {
  903. $parent = null;
  904. $suggestedItemTarget = null;
  905. $suggestedFileTarget = null;
  906. if (!$backend->isValidSource($itemSource, $uidOwner)) {
  907. $message = 'Sharing '.$itemSource.' failed, because the sharing backend for '.$itemType.' could not find its source';
  908. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  909. throw new \Exception($message);
  910. }
  911. $parent = null;
  912. if ($backend instanceof Share_Backend_File_Dependent) {
  913. $filePath = $backend->getFilePath($itemSource, $uidOwner);
  914. if ($itemType == 'file' || $itemType == 'folder') {
  915. $fileSource = $itemSource;
  916. } else {
  917. $meta = \OC\Files\Filesystem::getFileInfo($filePath);
  918. $fileSource = $meta['fileid'];
  919. }
  920. if ($fileSource == -1) {
  921. $message = 'Sharing '.$itemSource.' failed, because the file could not be found in the file cache';
  922. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  923. throw new \Exception($message);
  924. }
  925. } else {
  926. $filePath = null;
  927. $fileSource = null;
  928. }
  929. }
  930. $query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (`item_type`, `item_source`, `item_target`, `parent`, `share_type`, `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`, `file_target`, `token`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)');
  931. // Share with a group
  932. if ($shareType == self::SHARE_TYPE_GROUP) {
  933. $groupItemTarget = self::generateTarget($itemType, $itemSource, $shareType, $shareWith['group'], $uidOwner, $suggestedItemTarget);
  934. if (isset($fileSource)) {
  935. if ($parentFolder) {
  936. if ($parentFolder === true) {
  937. $groupFileTarget = self::generateTarget('file', $filePath, $shareType, $shareWith['group'], $uidOwner, $suggestedFileTarget);
  938. // Set group default file target for future use
  939. $parentFolders[0]['folder'] = $groupFileTarget;
  940. } else {
  941. // Get group default file target
  942. $groupFileTarget = $parentFolder[0]['folder'].$itemSource;
  943. $parent = $parentFolder[0]['id'];
  944. }
  945. } else {
  946. $groupFileTarget = self::generateTarget('file', $filePath, $shareType, $shareWith['group'], $uidOwner, $suggestedFileTarget);
  947. }
  948. } else {
  949. $groupFileTarget = null;
  950. }
  951. $query->execute(array($itemType, $itemSource, $groupItemTarget, $parent, $shareType, $shareWith['group'], $uidOwner, $permissions, time(), $fileSource, $groupFileTarget, $token));
  952. // Save this id, any extra rows for this group share will need to reference it
  953. $parent = \OC_DB::insertid('*PREFIX*share');
  954. // Loop through all users of this group in case we need to add an extra row
  955. foreach ($shareWith['users'] as $uid) {
  956. $itemTarget = self::generateTarget($itemType, $itemSource, self::SHARE_TYPE_USER, $uid, $uidOwner, $suggestedItemTarget, $parent);
  957. if (isset($fileSource)) {
  958. if ($parentFolder) {
  959. if ($parentFolder === true) {
  960. $fileTarget = self::generateTarget('file', $filePath, self::SHARE_TYPE_USER, $uid, $uidOwner, $suggestedFileTarget, $parent);
  961. if ($fileTarget != $groupFileTarget) {
  962. $parentFolders[$uid]['folder'] = $fileTarget;
  963. }
  964. } else if (isset($parentFolder[$uid])) {
  965. $fileTarget = $parentFolder[$uid]['folder'].$itemSource;
  966. $parent = $parentFolder[$uid]['id'];
  967. }
  968. } else {
  969. $fileTarget = self::generateTarget('file', $filePath, self::SHARE_TYPE_USER, $uid, $uidOwner, $suggestedFileTarget, $parent);
  970. }
  971. } else {
  972. $fileTarget = null;
  973. }
  974. \OC_Hook::emit('OCP\Share', 'post_shared', array(
  975. 'itemType' => $itemType,
  976. 'itemSource' => $itemSource,
  977. 'itemTarget' => $itemTarget,
  978. 'parent' => $parent,
  979. 'shareType' => self::$shareTypeGroupUserUnique,
  980. 'shareWith' => $uid,
  981. 'uidOwner' => $uidOwner,
  982. 'permissions' => $permissions,
  983. 'fileSource' => $fileSource,
  984. 'fileTarget' => $fileTarget,
  985. 'id' => $parent,
  986. 'token' => $token
  987. ));
  988. // Insert an extra row for the group share if the item or file target is unique for this user
  989. if ($itemTarget != $groupItemTarget || (isset($fileSource) && $fileTarget != $groupFileTarget)) {
  990. $query->execute(array($itemType, $itemSource, $itemTarget, $parent, self::$shareTypeGroupUserUnique, $uid, $uidOwner, $permissions, time(), $fileSource, $fileTarget, $token));
  991. $id = \OC_DB::insertid('*PREFIX*share');
  992. }
  993. }
  994. if ($parentFolder === true) {
  995. // Return parent folders to preserve file target paths for potential children
  996. return $parentFolders;
  997. }
  998. } else {
  999. $itemTarget = self::generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $suggestedItemTarget);
  1000. if (isset($fileSource)) {
  1001. if ($parentFolder) {
  1002. if ($parentFolder === true) {
  1003. $fileTarget = self::generateTarget('file', $filePath, $shareType, $shareWith, $uidOwner, $suggestedFileTarget);
  1004. $parentFolders['folder'] = $fileTarget;
  1005. } else {
  1006. $fileTarget = $parentFolder['folder'].$itemSource;
  1007. $parent = $parentFolder['id'];
  1008. }
  1009. } else {
  1010. $fileTarget = self::generateTarget('file', $filePath, $shareType, $shareWith, $uidOwner, $suggestedFileTarget);
  1011. }
  1012. } else {
  1013. $fileTarget = null;
  1014. }
  1015. $query->execute(array($itemType, $itemSource, $itemTarget, $parent, $shareType, $shareWith, $uidOwner, $permissions, time(), $fileSource, $fileTarget, $token));
  1016. $id = \OC_DB::insertid('*PREFIX*share');
  1017. \OC_Hook::emit('OCP\Share', 'post_shared', array(
  1018. 'itemType' => $itemType,
  1019. 'itemSource' => $itemSource,
  1020. 'itemTarget' => $itemTarget,
  1021. 'parent' => $parent,
  1022. 'shareType' => $shareType,
  1023. 'shareWith' => $shareWith,
  1024. 'uidOwner' => $uidOwner,
  1025. 'permissions' => $permissions,
  1026. 'fileSource' => $fileSource,
  1027. 'fileTarget' => $fileTarget,
  1028. 'id' => $id,
  1029. 'token' => $token
  1030. ));
  1031. if ($parentFolder === true) {
  1032. $parentFolders['id'] = $id;
  1033. // Return parent folder to preserve file target paths for potential children
  1034. return $parentFolders;
  1035. }
  1036. }
  1037. return true;
  1038. }
  1039. /**
  1040. * @brief Generate a unique target for the item
  1041. * @param string Item type
  1042. * @param string Item source
  1043. * @param int SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
  1044. * @param string User or group the item is being shared with
  1045. * @param string The suggested target originating from a reshare (optional)
  1046. * @param int The id of the parent group share (optional)
  1047. * @return string Item target
  1048. */
  1049. private static function generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $suggestedTarget = null, $groupParent = null) {
  1050. $backend = self::getBackend($itemType);
  1051. if ($shareType == self::SHARE_TYPE_LINK) {
  1052. if (isset($suggestedTarget)) {
  1053. return $suggestedTarget;
  1054. }
  1055. return $backend->generateTarget($itemSource, false);
  1056. } else {
  1057. if ($itemType == 'file' || $itemType == 'folder') {
  1058. $column = 'file_target';
  1059. $columnSource = 'file_source';
  1060. } else {
  1061. $column = 'item_target';
  1062. $columnSource = 'item_source';
  1063. }
  1064. if ($shareType == self::SHARE_TYPE_USER) {
  1065. // Share with is a user, so set share type to user and groups
  1066. $shareType = self::$shareTypeUserAndGroups;
  1067. $userAndGroups = array_merge(array($shareWith), \OC_Group::getUserGroups($shareWith));
  1068. } else {
  1069. $userAndGroups = false;
  1070. }
  1071. $exclude = null;
  1072. // Backend has 3 opportunities to generate a unique target
  1073. for ($i = 0; $i < 2; $i++) {
  1074. // Check if suggested target exists first
  1075. if ($i == 0 && isset($suggestedTarget)) {
  1076. $target = $suggestedTarget;
  1077. } else {
  1078. if ($shareType == self::SHARE_TYPE_GROUP) {
  1079. $target = $backend->generateTarget($itemSource, false, $exclude);
  1080. } else {
  1081. $target = $backend->generateTarget($itemSource, $shareWith, $exclude);
  1082. }
  1083. if (is_array($exclude) && in_array($target, $exclude)) {
  1084. break;
  1085. }
  1086. }
  1087. // Check if target already exists
  1088. $checkTarget = self::getItems($itemType, $target, $shareType, $shareWith);
  1089. if (!empty($checkTarget)) {
  1090. foreach ($checkTarget as $item) {
  1091. // Skip item if it is the group parent row
  1092. if (isset($groupParent) && $item['id'] == $groupParent) {
  1093. if (count($checkTarget) == 1) {
  1094. return $target;
  1095. } else {
  1096. continue;
  1097. }
  1098. }
  1099. if ($item['uid_owner'] == $uidOwner) {
  1100. if ($itemType == 'file' || $itemType == 'folder') {
  1101. $meta = \OC\Files\Filesystem::getFileInfo($itemSource);
  1102. if ($item['file_source'] == $meta['fileid']) {
  1103. return $target;
  1104. }
  1105. } else if ($item['item_source'] == $itemSource) {
  1106. return $target;
  1107. }
  1108. }
  1109. }
  1110. if (!isset($exclude)) {
  1111. $exclude = array();
  1112. }
  1113. // Find similar targets to improve backend's chances to generate a unqiue target
  1114. if ($userAndGroups) {
  1115. if ($column == 'file_target') {
  1116. $checkTargets = \OC_DB::prepare('SELECT `'.$column.'` FROM `*PREFIX*share` WHERE `item_type` IN (\'file\', \'folder\') AND `share_type` IN (?,?,?) AND `share_with` IN (\''.implode('\',\'', $userAndGroups).'\')');
  1117. $result = $checkTargets->execute(array(self::SHARE_TYPE_USER, self::SHARE_TYPE_GROUP, self::$shareTypeGroupUserUnique));
  1118. } else {
  1119. $checkTargets = \OC_DB::prepare('SELECT `'.$column.'` FROM `*PREFIX*share` WHERE `item_type` = ? AND `share_type` IN (?,?,?) AND `share_with` IN (\''.implode('\',\'', $userAndGroups).'\')');
  1120. $result = $checkTargets->execute(array($itemType, self::SHARE_TYPE_USER, self::SHARE_TYPE_GROUP, self::$shareTypeGroupUserUnique));
  1121. }
  1122. } else {
  1123. if ($column == 'file_target') {
  1124. $checkTargets = \OC_DB::prepare('SELECT `'.$column.'` FROM `*PREFIX*share` WHERE `item_type` IN (\'file\', \'folder\') AND `share_type` = ? AND `share_with` = ?');
  1125. $result = $checkTargets->execute(array(self::SHARE_TYPE_GROUP, $shareWith));
  1126. } else {
  1127. $checkTargets = \OC_DB::prepare('SELECT `'.$column.'` FROM `*PREFIX*share` WHERE `item_type` = ? AND `share_type` = ? AND `share_with` = ?');
  1128. $result = $checkTargets->execute(array($itemType, self::SHARE_TYPE_GROUP, $shareWith));
  1129. }
  1130. }
  1131. while ($row = $result->fetchRow()) {
  1132. $exclude[] = $row[$column];
  1133. }
  1134. } else {
  1135. return $target;
  1136. }
  1137. }
  1138. }
  1139. $message = 'Sharing backend registered for '.$itemType.' did not generate a unique target for '.$itemSource;
  1140. \OC_Log::write('OCP\Share', $message, \OC_Log::ERROR);
  1141. throw new \Exception($message);
  1142. }
  1143. /**
  1144. * @brief Delete all reshares of an item
  1145. * @param int Id of item to delete
  1146. * @param bool If true, exclude the parent from the delete (optional)
  1147. * @param string The user that the parent was shared with (optinal)
  1148. */
  1149. private static function delete($parent, $excludeParent = false, $uidOwner = null) {
  1150. $ids = array($parent);
  1151. $parents = array($parent);
  1152. while (!empty($parents)) {
  1153. $parents = "'".implode("','", $parents)."'";
  1154. // Check the owner on the first search of reshares, useful for finding and deleting the reshares by a single user of a group share
  1155. if (count($ids) == 1 && isset($uidOwner)) {
  1156. $query = \OC_DB::prepare('SELECT `id`, `uid_owner`, `item_type`, `item_target`, `parent` FROM `*PREFIX*share` WHERE `parent` IN ('.$parents.') AND `uid_owner` = ?');
  1157. $result = $query->execute(array($uidOwner));
  1158. } else {
  1159. $query = \OC_DB::prepare('SELECT `id`, `item_type`, `item_target`, `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `parent` IN ('.$parents.')');
  1160. $result = $query->execute();
  1161. }
  1162. // Reset parents array, only go through loop again if items are found
  1163. $parents = array();
  1164. while ($item = $result->fetchRow()) {
  1165. // Search for a duplicate parent share, this occurs when an item is shared to the same user through a group and user or the same item is shared by different users
  1166. $userAndGroups = array_merge(array($item['uid_owner']), \OC_Group::getUserGroups($item['uid_owner']));
  1167. $query = \OC_DB::prepare('SELECT `id`, `permissions` FROM `*PREFIX*share` WHERE `item_type` = ? AND `item_target` = ? AND `share_type` IN (?,?,?) AND `share_with` IN (\''.implode('\',\'', $userAndGroups).'\') AND `uid_owner` != ? AND `id` != ?');
  1168. $duplicateParent = $query->execute(array($item['item_type'], $item['item_target'], self::SHARE_TYPE_USER, self::SHARE_TYPE_GROUP, self::$shareTypeGroupUserUnique, $item['uid_owner'], $item['parent']))->fetchRow();
  1169. if ($duplicateParent) {
  1170. // Change the parent to the other item id if share permission is granted
  1171. if ($duplicateParent['permissions'] & PERMISSION_SHARE) {
  1172. $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `parent` = ? WHERE `id` = ?');
  1173. $query->execute(array($duplicateParent['id'], $item['id']));
  1174. continue;
  1175. }
  1176. }
  1177. $ids[] = $item['id'];
  1178. $parents[] = $item['id'];
  1179. }
  1180. }
  1181. if ($excludeParent) {
  1182. unset($ids[0]);
  1183. }
  1184. if (!empty($ids)) {
  1185. $ids = "'".implode("','", $ids)."'";
  1186. $query = \OC_DB::prepare('DELETE FROM `*PREFIX*share` WHERE `id` IN ('.$ids.')');
  1187. $query->execute();
  1188. }
  1189. }
  1190. /**
  1191. * Hook Listeners
  1192. */
  1193. public static function post_deleteUser($arguments) {
  1194. // Delete any items shared with the deleted user
  1195. $query = \OC_DB::prepare('DELETE FROM `*PREFIX*share` WHERE `share_with` = ? AND `share_type` = ? OR `share_type` = ?');
  1196. $result = $query->execute(array($arguments['uid'], self::SHARE_TYPE_USER, self::$shareTypeGroupUserUnique));
  1197. // Delete any items the deleted user shared
  1198. $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*share` WHERE `uid_owner` = ?');
  1199. $result = $query->execute(array($arguments['uid']));
  1200. while ($item = $result->fetchRow()) {
  1201. self::delete($item['id']);
  1202. }
  1203. }
  1204. public static function post_addToGroup($arguments) {
  1205. // Find the group shares and check if the user needs a unique target
  1206. $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `share_type` = ? AND `share_with` = ?');
  1207. $result = $query->execute(array(self::SHARE_TYPE_GROUP, $arguments['gid']));
  1208. $query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (`item_type`, `item_source`, `item_target`, `parent`, `share_type`, `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`, `file_target`) VALUES (?,?,?,?,?,?,?,?,?,?,?)');
  1209. while ($item = $result->fetchRow()) {
  1210. if ($item['item_type'] == 'file' || $item['item_type'] == 'file') {
  1211. $itemTarget = null;
  1212. } else {
  1213. $itemTarget = self::generateTarget($item['item_type'], $item['item_source'], self::SHARE_TYPE_USER, $arguments['uid'], $item['uid_owner'], $item['item_target'], $item['id']);
  1214. }
  1215. if (isset($item['file_source'])) {
  1216. $fileTarget = self::generateTarget($item['item_type'], $item['item_source'], self::SHARE_TYPE_USER, $arguments['uid'], $item['uid_owner'], $item['file_target'], $item['id']);
  1217. } else {
  1218. $fileTarget = null;
  1219. }
  1220. // Insert an extra row for the group share if the item or file target is unique for this user
  1221. if ($itemTarget != $item['item_target'] || $fileTarget != $item['file_target']) {
  1222. $query->execute(array($item['item_type'], $item['item_source'], $itemTarget, $item['id'], self::$shareTypeGroupUserUnique, $arguments['uid'], $item['uid_owner'], $item['permissions'], $item['stime'], $item['file_source'], $fileTarget));
  1223. \OC_DB::insertid('*PREFIX*share');
  1224. }
  1225. }
  1226. }
  1227. public static function post_removeFromGroup($arguments) {
  1228. // TODO Don't call if user deleted?
  1229. $query = \OC_DB::prepare('SELECT `id`, `share_type` FROM `*PREFIX*share` WHERE (`share_type` = ? AND `share_with` = ?) OR (`share_type` = ? AND `share_with` = ?)');
  1230. $result = $query->execute(array(self::SHARE_TYPE_GROUP, $arguments['gid'], self::$shareTypeGroupUserUnique, $arguments['uid']));
  1231. while ($item = $result->fetchRow()) {
  1232. if ($item['share_type'] == self::SHARE_TYPE_GROUP) {
  1233. // Delete all reshares by this user of the group share
  1234. self::delete($item['id'], true, $arguments['uid']);
  1235. } else {
  1236. self::delete($item['id']);
  1237. }
  1238. }
  1239. }
  1240. public static function post_deleteGroup($arguments) {
  1241. $query = \OC_DB::prepare('SELECT id FROM `*PREFIX*share` WHERE `share_type` = ? AND `share_with` = ?');
  1242. $result = $query->execute(array(self::SHARE_TYPE_GROUP, $arguments['gid']));
  1243. while ($item = $result->fetchRow()) {
  1244. self::delete($item['id']);
  1245. }
  1246. }
  1247. }
  1248. /**
  1249. * Interface that apps must implement to share content.
  1250. */
  1251. interface Share_Backend {
  1252. /**
  1253. * @brief Get the source of the item to be stored in the database
  1254. * @param string Item source
  1255. * @param string Owner of the item
  1256. * @return mixed|array|false Source
  1257. *
  1258. * Return an array if the item is file dependent, the array needs two keys: 'item' and 'file'
  1259. * Return false if the item does not exist for the user
  1260. *
  1261. * The formatItems() function will translate the source returned back into the item
  1262. */
  1263. public function isValidSource($itemSource, $uidOwner);
  1264. /**
  1265. * @brief Get a unique name of the item for the specified user
  1266. * @param string Item source
  1267. * @param string|false User the item is being shared with
  1268. * @param array|null List of similar item names already existing as shared items
  1269. * @return string Target name
  1270. *
  1271. * This function needs to verify that the user does not already have an item with this name.
  1272. * If it does generate a new name e.g. name_#
  1273. */
  1274. public function generateTarget($itemSource, $shareWith, $exclude = null);
  1275. /**
  1276. * @brief Converts the shared item sources back into the item in the specified format
  1277. * @param array Shared items
  1278. * @param int Format
  1279. * @return ?
  1280. *
  1281. * The items array is a 3-dimensional array with the item_source as the first key and the share id as the second key to an array with the share info.
  1282. * The key/value pairs included in the share info depend on the function originally called:
  1283. * If called by getItem(s)Shared: id, item_type, item, item_source, share_type, share_with, permissions, stime, file_source
  1284. * If called by getItem(s)SharedWith: id, item_type, item, item_source, item_target, share_type, share_with, permissions, stime, file_source, file_target
  1285. * This function allows the backend to control the output of shared items with custom formats.
  1286. * It is only called through calls to the public getItem(s)Shared(With) functions.
  1287. */
  1288. public function formatItems($items, $format, $parameters = null);
  1289. }
  1290. /**
  1291. * Interface for share backends that share content that is dependent on files.
  1292. * Extends the Share_Backend interface.
  1293. */
  1294. interface Share_Backend_File_Dependent extends Share_Backend {
  1295. /**
  1296. * @brief Get the file path of the item
  1297. * @param
  1298. * @param
  1299. * @return
  1300. */
  1301. public function getFilePath($itemSource, $uidOwner);
  1302. }
  1303. /**
  1304. * Interface for collections of of items implemented by another share backend.
  1305. * Extends the Share_Backend interface.
  1306. */
  1307. interface Share_Backend_Collection extends Share_Backend {
  1308. /**
  1309. * @brief Get the sources of the children of the item
  1310. * @param string Item source
  1311. * @return array Returns an array of children each inside an array with the keys: source, target, and file_path if applicable
  1312. */
  1313. public function getChildren($itemSource);
  1314. }