Completed
Pull Request — master (#26608)
by
unknown
09:52
created

Share::unshareItem()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 2
dl 0
loc 24
rs 9.536
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Bernhard Reiter <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Christopher Schäpers <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Daniel Hansson <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Michael Kuhn <[email protected]>
14
 * @author Morris Jobke <[email protected]>
15
 * @author Robin Appelman <[email protected]>
16
 * @author Robin McCorkell <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 * @author Sebastian Döll <[email protected]>
19
 * @author Stefan Weil <[email protected]>
20
 * @author Thomas Müller <[email protected]>
21
 * @author Torben Dannhauer <[email protected]>
22
 * @author Vincent Petry <[email protected]>
23
 * @author Volkan Gezer <[email protected]>
24
 *
25
 * @copyright Copyright (c) 2018, ownCloud GmbH
26
 * @license AGPL-3.0
27
 *
28
 * This code is free software: you can redistribute it and/or modify
29
 * it under the terms of the GNU Affero General Public License, version 3,
30
 * as published by the Free Software Foundation.
31
 *
32
 * This program is distributed in the hope that it will be useful,
33
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
 * GNU Affero General Public License for more details.
36
 *
37
 * You should have received a copy of the GNU Affero General Public License, version 3,
38
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
39
 *
40
 */
41
42
namespace OC\Share;
43
44
use OC\Files\Filesystem;
45
use OC\Group\Group;
46
use OCA\FederatedFileSharing\DiscoveryManager;
47
use OCP\DB\QueryBuilder\IQueryBuilder;
48
use OCP\IUser;
49
use OCP\IUserSession;
50
use OCP\IDBConnection;
51
use OCP\IConfig;
52
use Symfony\Component\EventDispatcher\GenericEvent;
53
54
/**
55
 * This class provides the ability for apps to share their content between users.
56
 *
57
 * It provides the following hooks:
58
 *  - post_shared
59
 */
60
class Share extends Constants {
61
62
	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
63
	 * Construct permissions for share() and setPermissions with Or (|) e.g.
64
	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
65
	 *
66
	 * Check if permission is granted with And (&) e.g. Check if delete is
67
	 * granted: if ($permissions & PERMISSION_DELETE)
68
	 *
69
	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
70
	 * permission: $permissions &= ~PERMISSION_UPDATE
71
	 *
72
	 * Apps are required to handle permissions on their own, this class only
73
	 * stores and manages the permissions of shares
74
	 * @see lib/public/constants.php
75
	 */
76
77
	/**
78
	 * Check if the Share API is enabled
79
	 * @return boolean true if enabled or false
80
	 *
81
	 * The Share API is enabled by default if not configured
82
	 */
83
	public static function isEnabled() {
84
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_enabled', 'yes') == 'yes') {
85
			return true;
86
		}
87
		return false;
88
	}
89
90
	/**
91
	 * Find which users can access a shared item
92
	 * @param string $path to the file
93
	 * @param string $ownerUser owner of the file
94
	 * @param boolean $includeOwner include owner to the list of users with access to the file
95
	 * @param boolean $returnUserPaths Return an array with the user => path map
96
	 * @param boolean $recursive take all parent folders into account (default true)
97
	 * @return array
98
	 * @note $path needs to be relative to user data dir, e.g. 'file.txt'
99
	 *       not '/admin/data/file.txt'
100
	 */
101
	public static function getUsersSharingFile($path, $ownerUser, $includeOwner = false, $returnUserPaths = false, $recursive = true) {
102
		// FIXME: make ths use IShareProvider::getSharesByPath and extract users
103
		$userManager = \OC::$server->getUserManager();
104
		$userObject = $userManager->get($ownerUser);
105
106 View Code Duplication
		if ($userObject === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
			$msg = "Backends provided no user object for $ownerUser";
108
			\OC::$server->getLogger()->error($msg, ['app' => __CLASS__]);
109
			throw new \OC\User\NoUserException($msg);
110
		}
111
112
		$ownerUser = $userObject->getUID();
113
114
		Filesystem::initMountPoints($ownerUser);
115
		$shares = $sharePaths = $fileTargets = [];
116
		$publicShare = false;
117
		$remoteShare = false;
118
		$source = -1;
119
		$cache = $mountPath = false;
120
121
		$view = new \OC\Files\View('/' . $ownerUser . '/files');
122
		$meta = $view->getFileInfo($path);
123
		if ($meta) {
124
			$path = \substr($meta->getPath(), \strlen('/' . $ownerUser . '/files'));
125
		} else {
126
			// if the file doesn't exists yet we start with the parent folder
127
			$meta = $view->getFileInfo(\dirname($path));
128
		}
129
130
		if ($meta !== false) {
131
			$source = $meta['fileid'];
132
			$cache = new \OC\Files\Cache\Cache($meta['storage']);
133
134
			$mountPath = $meta->getMountPoint()->getMountPoint();
135
			if ($mountPath !== false) {
136
				$mountPath = \substr($mountPath, \strlen('/' . $ownerUser . '/files'));
137
			}
138
		}
139
140
		$paths = [];
141
		while ($source !== -1) {
142
			// Fetch all shares with another user
143
			if (!$returnUserPaths) {
144
				$query = \OC_DB::prepare(
145
					'SELECT `share_with`, `file_source`, `file_target`
146
					FROM
147
					`*PREFIX*share`
148
					WHERE
149
					`item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')'
150
				);
151
				$result = $query->execute([$source, self::SHARE_TYPE_USER]);
152
			} else {
153
				$query = \OC_DB::prepare(
154
					'SELECT `share_with`, `file_source`, `file_target`
155
				FROM
156
				`*PREFIX*share`
157
				WHERE
158
				`item_source` = ? AND `share_type` IN (?, ?) AND `item_type` IN (\'file\', \'folder\')'
159
				);
160
				$result = $query->execute([$source, self::SHARE_TYPE_USER, self::$shareTypeGroupUserUnique]);
161
			}
162
163
			if (\OCP\DB::isError($result)) {
164
				\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
165
			} else {
166
				while ($row = $result->fetchRow()) {
167
					$shares[] = $row['share_with'];
168
					if ($returnUserPaths) {
169
						$fileTargets[(int) $row['file_source']][$row['share_with']] = $row;
170
					}
171
				}
172
			}
173
174
			// We also need to take group shares into account
175
			$query = \OC_DB::prepare(
176
				'SELECT `share_with`, `file_source`, `file_target`
177
				FROM
178
				`*PREFIX*share`
179
				WHERE
180
				`item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')'
181
			);
182
183
			$result = $query->execute([$source, self::SHARE_TYPE_GROUP]);
184
185
			if (\OCP\DB::isError($result)) {
186
				\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
187
			} else {
188
				while ($row = $result->fetchRow()) {
189
					$usersInGroup = self::usersInGroup($row['share_with']);
190
					$shares = \array_merge($shares, $usersInGroup);
191
					if ($returnUserPaths) {
192
						foreach ($usersInGroup as $user) {
193
							if (!isset($fileTargets[(int) $row['file_source']][$user])) {
194
								// When the user already has an entry for this file source
195
								// the file is either shared directly with him as well, or
196
								// he has an exception entry (because of naming conflict).
197
								$fileTargets[(int) $row['file_source']][$user] = $row;
198
							}
199
						}
200
					}
201
				}
202
			}
203
204
			//check for public link shares
205 View Code Duplication
			if (!$publicShare) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206
				$query = \OC_DB::prepare('
207
					SELECT `share_with`
208
					FROM `*PREFIX*share`
209
					WHERE `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')', 1
210
				);
211
212
				$result = $query->execute([$source, self::SHARE_TYPE_LINK]);
213
214
				if (\OCP\DB::isError($result)) {
215
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
216
				} else {
217
					if ($result->fetchRow()) {
218
						$publicShare = true;
219
					}
220
				}
221
			}
222
223
			//check for remote share
224 View Code Duplication
			if (!$remoteShare) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
225
				$query = \OC_DB::prepare('
226
					SELECT `share_with`
227
					FROM `*PREFIX*share`
228
					WHERE `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')', 1
229
				);
230
231
				$result = $query->execute([$source, self::SHARE_TYPE_REMOTE]);
232
233
				if (\OCP\DB::isError($result)) {
234
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
235
				} else {
236
					if ($result->fetchRow()) {
237
						$remoteShare = true;
238
					}
239
				}
240
			}
241
242
			// let's get the parent for the next round
243
			$meta = $cache->get((int)$source);
244
			if ($recursive === true && $meta !== false) {
245
				$paths[$source] = $meta['path'];
246
				$source = (int)$meta['parent'];
247
			} else {
248
				$source = -1;
249
			}
250
		}
251
252
		// Include owner in list of users, if requested
253
		if ($includeOwner) {
254
			$shares[] = $ownerUser;
255
		}
256
257
		if ($returnUserPaths) {
258
			$fileTargetIDs = \array_keys($fileTargets);
259
			$fileTargetIDs = \array_unique($fileTargetIDs);
260
261
			if (!empty($fileTargetIDs)) {
262
				$query = \OC_DB::prepare(
263
					'SELECT `fileid`, `path`
264
					FROM `*PREFIX*filecache`
265
					WHERE `fileid` IN (' . \implode(',', $fileTargetIDs) . ')'
266
				);
267
				$result = $query->execute();
268
269
				if (\OCP\DB::isError($result)) {
270
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
271
				} else {
272
					while ($row = $result->fetchRow()) {
273
						foreach ($fileTargets[$row['fileid']] as $uid => $shareData) {
274
							if ($mountPath !== false) {
275
								$sharedPath = $shareData['file_target'];
276
								$sharedPath .= \substr($path, \strlen($mountPath) + \strlen($paths[$row['fileid']]));
277
								$sharePaths[$uid] = $sharedPath;
278
							} else {
279
								$sharedPath = $shareData['file_target'];
280
								$sharedPath .= \substr($path, \strlen($row['path']) -5);
281
								$sharePaths[$uid] = $sharedPath;
282
							}
283
						}
284
					}
285
				}
286
			}
287
288
			if ($includeOwner) {
289
				$sharePaths[$ownerUser] = $path;
290
			} else {
291
				unset($sharePaths[$ownerUser]);
292
			}
293
294
			return $sharePaths;
295
		}
296
297
		return ['users' => \array_unique($shares), 'public' => $publicShare, 'remote' => $remoteShare];
298
	}
299
300
	/**
301
	 * Based on the given token the share information will be returned - password protected shares will be verified
302
	 * @param string $token
303
	 * @param bool $checkPasswordProtection
304
	 * @return array|boolean false will be returned in case the token is unknown or unauthorized
305
	 */
306
	public static function getShareByToken($token, $checkPasswordProtection = true) {
307
		$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?', 1);
308
		$result = $query->execute([$token]);
309
		if ($result === false) {
310
			\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, \OCP\Util::ERROR);
311
		}
312
		$row = $result->fetchRow();
313
		if ($row === false) {
314
			return false;
315
		}
316
		if (\is_array($row) and self::expireItem($row)) {
317
			return false;
318
		}
319
320
		// password protected shares need to be authenticated
321
		if ($checkPasswordProtection && !\OCP\Share::checkPasswordProtectedShare($row)) {
322
			return false;
323
		}
324
325
		return $row;
326
	}
327
328
	/**
329
	 * resolves reshares down to the last real share
330
	 * @param array $linkItem
331
	 * @return array item owner
332
	 */
333
	public static function resolveReShare($linkItem) {
334
		if (isset($linkItem['parent'])) {
335
			$parent = $linkItem['parent'];
336
			while (isset($parent)) {
337
				$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `id` = ?', 1);
338
				$item = $query->execute([$parent])->fetchRow();
339
				if (isset($item['parent'])) {
340
					$parent = $item['parent'];
341
				} else {
342
					return $item;
343
				}
344
			}
345
		}
346
		return $linkItem;
347
	}
348
349
	/**
350
	 * sent status if users got informed by mail about share
351
	 * @param string $itemType
352
	 * @param string $itemSource
353
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
354
	 * @param string $recipient with whom was the file shared
355
	 * @param boolean $status
356
	 */
357
	public static function setSendMailStatus($itemType, $itemSource, $shareType, $recipient, $status) {
358
		$status = $status ? 1 : 0;
359
360
		$query = \OC_DB::prepare(
361
			'UPDATE `*PREFIX*share`
362
					SET `mail_send` = ?
363
					WHERE `item_type` = ? AND `item_source` = ? AND `share_type` = ? AND `share_with` = ?');
364
365
		$result = $query->execute([$status, $itemType, $itemSource, $shareType, $recipient]);
366
367
		if ($result === false) {
368
			\OCP\Util::writeLog('OCP\Share', 'Couldn\'t set send mail status', \OCP\Util::ERROR);
369
		}
370
	}
371
372
	/**
373
	 * Checks whether a share has expired, calls unshareItem() if yes.
374
	 * @param array $item Share data (usually database row)
375
	 * @return boolean True if item was expired, false otherwise.
376
	 */
377
	protected static function expireItem(array $item) {
378
		$result = false;
379
380
		// only use default expiration date for link shares
381
		if ((int) $item['share_type'] === self::SHARE_TYPE_LINK) {
382
383
			// calculate expiration date
384
			if (!empty($item['expiration'])) {
385
				$userDefinedExpire = new \DateTime($item['expiration']);
386
				$expires = $userDefinedExpire->getTimestamp();
387
			} else {
388
				$expires = null;
389
			}
390
391
			// get default expiration settings
392
			$defaultSettings = Helper::getDefaultExpireSetting();
393
			$expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
394
395
			if (\is_int($expires)) {
396
				$now = \time();
397
				if ($now > $expires) {
398
					self::unshareItem($item);
399
					$result = true;
400
				}
401
			}
402
		}
403
		return $result;
404
	}
405
406
	/**
407
	 * Unshares a share given a share data array
408
	 * @param array $item Share data (usually database row)
409
	 * @param int $newParent parent ID
410
	 * @return null
411
	 */
412
	protected static function unshareItem(array $item, $newParent = null) {
413
		$shareType = (int)$item['share_type'];
414
		$shareWith = null;
415
		if ($shareType !== \OCP\Share::SHARE_TYPE_LINK) {
416
			$shareWith = $item['share_with'];
417
		}
418
419
		// Pass all the vars we have for now, they may be useful
420
		$hookParams = [
421
			'id'            => $item['id'],
422
			'itemType'      => $item['item_type'],
423
			'itemSource'    => $item['item_source'],
424
			'shareType'     => $shareType,
425
			'shareWith'     => $shareWith,
426
			'itemParent'    => $item['parent'],
427
			'uidOwner'      => $item['uid_owner'],
428
		];
429
430
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
431
		$deletedShares = Helper::delete($item['id'], false, null, $newParent);
432
		$deletedShares[] = $hookParams;
433
		$hookParams['deletedShares'] = $deletedShares;
434
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
435
	}
436
437
	/**
438
	 * Check if resharing is allowed
439
	 * @return boolean true if allowed or false
440
	 *
441
	 * Resharing is allowed by default if not configured
442
	 */
443
	public static function isResharingAllowed() {
444
		if (!isset(self::$isResharingAllowed)) {
445
			if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
446
				self::$isResharingAllowed = true;
447
			} else {
448
				self::$isResharingAllowed = false;
449
			}
450
		}
451
		return self::$isResharingAllowed;
452
	}
453
454
	/**
455
	 * In case a password protected link is not yet authenticated this function will return false
456
	 *
457
	 * @param array $linkItem
458
	 * @return boolean
459
	 */
460
	public static function checkPasswordProtectedShare(array $linkItem) {
461
		if (!isset($linkItem['share_with'])) {
462
			return true;
463
		}
464
		if (!isset($linkItem['share_type'])) {
465
			return true;
466
		}
467
		if (!isset($linkItem['id'])) {
468
			return true;
469
		}
470
471
		if ($linkItem['share_type'] != \OCP\Share::SHARE_TYPE_LINK) {
472
			return true;
473
		}
474
475 View Code Duplication
		if (\OC::$server->getSession()->exists('public_link_authenticated')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
476
			&& \OC::$server->getSession()->get('public_link_authenticated') === (string)$linkItem['id']) {
477
			return true;
478
		}
479
480
		return false;
481
	}
482
483
	/**
484
	 * remove protocol from URL
485
	 *
486
	 * @param string $url
487
	 * @return string
488
	 */
489 View Code Duplication
	public static function removeProtocolFromUrl($url) {
490
		if (\strpos($url, 'https://') === 0) {
491
			return \substr($url, \strlen('https://'));
492
		} elseif (\strpos($url, 'http://') === 0) {
493
			return \substr($url, \strlen('http://'));
494
		}
495
496
		return $url;
497
	}
498
499
	/**
500
	 * @param $group
501
	 * @return mixed
502
	 */
503
	private static function usersInGroup($group) {
504
		$g = \OC::$server->getGroupManager()->get($group);
505
		if ($g === null) {
506
			return [];
507
		}
508
		return \array_values(\array_map(function (IUser $u) {
509
			return $u->getUID();
510
		}, $g->getUsers()));
511
	}
512
}
513