Completed
Pull Request — master (#26608)
by
unknown
14:04
created

Share::setPermissions()   D

Complexity

Conditions 16
Paths 86

Size

Total Lines 147

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
nc 86
nop 5
dl 0
loc 147
rs 4.4532
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
 * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
57
 *
58
 * It provides the following hooks:
59
 *  - post_shared
60
 */
61
class Share extends Constants {
62
63
	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
64
	 * Construct permissions for share() and setPermissions with Or (|) e.g.
65
	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
66
	 *
67
	 * Check if permission is granted with And (&) e.g. Check if delete is
68
	 * granted: if ($permissions & PERMISSION_DELETE)
69
	 *
70
	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
71
	 * permission: $permissions &= ~PERMISSION_UPDATE
72
	 *
73
	 * Apps are required to handle permissions on their own, this class only
74
	 * stores and manages the permissions of shares
75
	 * @see lib/public/constants.php
76
	 */
77
78
	/**
79
	 * Register a sharing backend class that implements OCP\Share_Backend for an item type
80
	 * @param string $itemType Item type
81
	 * @param string $class Backend class
82
	 * @param string $collectionOf (optional) Depends on item type
83
	 * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
84
	 * @return boolean true if backend is registered or false if error
85
	 */
86
	public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
87
		if (self::isEnabled()) {
88 View Code Duplication
			if ($itemType === 'file' || $itemType === 'folder') {
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...
89
				throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
90
			}
91
			if (!isset(self::$backendTypes[$itemType])) {
92
				self::$backendTypes[$itemType] = [
93
					'class' => $class,
94
					'collectionOf' => $collectionOf,
95
					'supportedFileExtensions' => $supportedFileExtensions
96
				];
97
				return true;
98
			}
99
			\OCP\Util::writeLog('OCP\Share',
100
				'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
101
				.' is already registered for '.$itemType,
102
				\OCP\Util::WARN);
103
		}
104
		return false;
105
	}
106
107
	/**
108
	 * Check if the Share API is enabled
109
	 * @return boolean true if enabled or false
110
	 *
111
	 * The Share API is enabled by default if not configured
112
	 */
113
	public static function isEnabled() {
114
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_enabled', 'yes') == 'yes') {
115
			return true;
116
		}
117
		return false;
118
	}
119
120
	/**
121
	 * Find which users can access a shared item
122
	 * @param string $path to the file
123
	 * @param string $ownerUser owner of the file
124
	 * @param boolean $includeOwner include owner to the list of users with access to the file
125
	 * @param boolean $returnUserPaths Return an array with the user => path map
126
	 * @param boolean $recursive take all parent folders into account (default true)
127
	 * @return array
128
	 * @note $path needs to be relative to user data dir, e.g. 'file.txt'
129
	 *       not '/admin/data/file.txt'
130
	 */
131
	public static function getUsersSharingFile($path, $ownerUser, $includeOwner = false, $returnUserPaths = false, $recursive = true) {
132
		// FIXME: make ths use IShareProvider::getSharesByPath and extract users
133
		$userManager = \OC::$server->getUserManager();
134
		$userObject = $userManager->get($ownerUser);
135
136 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...
137
			$msg = "Backends provided no user object for $ownerUser";
138
			\OC::$server->getLogger()->error($msg, ['app' => __CLASS__]);
139
			throw new \OC\User\NoUserException($msg);
140
		}
141
142
		$ownerUser = $userObject->getUID();
143
144
		Filesystem::initMountPoints($ownerUser);
145
		$shares = $sharePaths = $fileTargets = [];
146
		$publicShare = false;
147
		$remoteShare = false;
148
		$source = -1;
149
		$cache = $mountPath = false;
150
151
		$view = new \OC\Files\View('/' . $ownerUser . '/files');
152
		$meta = $view->getFileInfo($path);
153
		if ($meta) {
154
			$path = \substr($meta->getPath(), \strlen('/' . $ownerUser . '/files'));
155
		} else {
156
			// if the file doesn't exists yet we start with the parent folder
157
			$meta = $view->getFileInfo(\dirname($path));
158
		}
159
160
		if ($meta !== false) {
161
			$source = $meta['fileid'];
162
			$cache = new \OC\Files\Cache\Cache($meta['storage']);
163
164
			$mountPath = $meta->getMountPoint()->getMountPoint();
165
			if ($mountPath !== false) {
166
				$mountPath = \substr($mountPath, \strlen('/' . $ownerUser . '/files'));
167
			}
168
		}
169
170
		$paths = [];
171
		while ($source !== -1) {
172
			// Fetch all shares with another user
173
			if (!$returnUserPaths) {
174
				$query = \OC_DB::prepare(
175
					'SELECT `share_with`, `file_source`, `file_target`
176
					FROM
177
					`*PREFIX*share`
178
					WHERE
179
					`item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')'
180
				);
181
				$result = $query->execute([$source, self::SHARE_TYPE_USER]);
182
			} else {
183
				$query = \OC_DB::prepare(
184
					'SELECT `share_with`, `file_source`, `file_target`
185
				FROM
186
				`*PREFIX*share`
187
				WHERE
188
				`item_source` = ? AND `share_type` IN (?, ?) AND `item_type` IN (\'file\', \'folder\')'
189
				);
190
				$result = $query->execute([$source, self::SHARE_TYPE_USER, self::$shareTypeGroupUserUnique]);
191
			}
192
193
			if (\OCP\DB::isError($result)) {
194
				\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
195
			} else {
196
				while ($row = $result->fetchRow()) {
197
					$shares[] = $row['share_with'];
198
					if ($returnUserPaths) {
199
						$fileTargets[(int) $row['file_source']][$row['share_with']] = $row;
200
					}
201
				}
202
			}
203
204
			// We also need to take group shares into account
205
			$query = \OC_DB::prepare(
206
				'SELECT `share_with`, `file_source`, `file_target`
207
				FROM
208
				`*PREFIX*share`
209
				WHERE
210
				`item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')'
211
			);
212
213
			$result = $query->execute([$source, self::SHARE_TYPE_GROUP]);
214
215
			if (\OCP\DB::isError($result)) {
216
				\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
217
			} else {
218
				while ($row = $result->fetchRow()) {
219
					$usersInGroup = self::usersInGroup($row['share_with']);
220
					$shares = \array_merge($shares, $usersInGroup);
221
					if ($returnUserPaths) {
222
						foreach ($usersInGroup as $user) {
223
							if (!isset($fileTargets[(int) $row['file_source']][$user])) {
224
								// When the user already has an entry for this file source
225
								// the file is either shared directly with him as well, or
226
								// he has an exception entry (because of naming conflict).
227
								$fileTargets[(int) $row['file_source']][$user] = $row;
228
							}
229
						}
230
					}
231
				}
232
			}
233
234
			//check for public link shares
235 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...
236
				$query = \OC_DB::prepare('
237
					SELECT `share_with`
238
					FROM `*PREFIX*share`
239
					WHERE `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')', 1
240
				);
241
242
				$result = $query->execute([$source, self::SHARE_TYPE_LINK]);
243
244
				if (\OCP\DB::isError($result)) {
245
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
246
				} else {
247
					if ($result->fetchRow()) {
248
						$publicShare = true;
249
					}
250
				}
251
			}
252
253
			//check for remote share
254 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...
255
				$query = \OC_DB::prepare('
256
					SELECT `share_with`
257
					FROM `*PREFIX*share`
258
					WHERE `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')', 1
259
				);
260
261
				$result = $query->execute([$source, self::SHARE_TYPE_REMOTE]);
262
263
				if (\OCP\DB::isError($result)) {
264
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
265
				} else {
266
					if ($result->fetchRow()) {
267
						$remoteShare = true;
268
					}
269
				}
270
			}
271
272
			// let's get the parent for the next round
273
			$meta = $cache->get((int)$source);
274
			if ($recursive === true && $meta !== false) {
275
				$paths[$source] = $meta['path'];
276
				$source = (int)$meta['parent'];
277
			} else {
278
				$source = -1;
279
			}
280
		}
281
282
		// Include owner in list of users, if requested
283
		if ($includeOwner) {
284
			$shares[] = $ownerUser;
285
		}
286
287
		if ($returnUserPaths) {
288
			$fileTargetIDs = \array_keys($fileTargets);
289
			$fileTargetIDs = \array_unique($fileTargetIDs);
290
291
			if (!empty($fileTargetIDs)) {
292
				$query = \OC_DB::prepare(
293
					'SELECT `fileid`, `path`
294
					FROM `*PREFIX*filecache`
295
					WHERE `fileid` IN (' . \implode(',', $fileTargetIDs) . ')'
296
				);
297
				$result = $query->execute();
298
299
				if (\OCP\DB::isError($result)) {
300
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
301
				} else {
302
					while ($row = $result->fetchRow()) {
303
						foreach ($fileTargets[$row['fileid']] as $uid => $shareData) {
304
							if ($mountPath !== false) {
305
								$sharedPath = $shareData['file_target'];
306
								$sharedPath .= \substr($path, \strlen($mountPath) + \strlen($paths[$row['fileid']]));
307
								$sharePaths[$uid] = $sharedPath;
308
							} else {
309
								$sharedPath = $shareData['file_target'];
310
								$sharedPath .= \substr($path, \strlen($row['path']) -5);
311
								$sharePaths[$uid] = $sharedPath;
312
							}
313
						}
314
					}
315
				}
316
			}
317
318
			if ($includeOwner) {
319
				$sharePaths[$ownerUser] = $path;
320
			} else {
321
				unset($sharePaths[$ownerUser]);
322
			}
323
324
			return $sharePaths;
325
		}
326
327
		return ['users' => \array_unique($shares), 'public' => $publicShare, 'remote' => $remoteShare];
328
	}
329
330
	/**
331
	 * Get the items of item type shared with the current user
332
	 * @param string $itemType
333
	 * @param int $format (optional) Format type must be defined by the backend
334
	 * @param mixed $parameters (optional)
335
	 * @param int $limit Number of items to return (optional) Returns all by default
336
	 * @param boolean $includeCollections (optional)
337
	 * @return mixed Return depends on format
338
	 */
339
	public static function getItemsSharedWith($itemType, $format = self::FORMAT_NONE,
340
											  $parameters = null, $limit = -1, $includeCollections = false) {
341
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format,
0 ignored issues
show
Bug introduced by
It seems like \OC_User::getUser() targeting OC_User::getUser() can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
342
			$parameters, $limit, $includeCollections);
343
	}
344
345
	/**
346
	 * Get the items of item type shared with a user
347
	 * @param string $itemType
348
	 * @param string $user id for which user we want the shares
349
	 * @param int $format (optional) Format type must be defined by the backend
350
	 * @param mixed $parameters (optional)
351
	 * @param int $limit Number of items to return (optional) Returns all by default
352
	 * @param boolean $includeCollections (optional)
353
	 * @return mixed Return depends on format
354
	 */
355
	public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
356
												  $parameters = null, $limit = -1, $includeCollections = false) {
357
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
358
			$parameters, $limit, $includeCollections);
359
	}
360
361
	/**
362
	 * Get the item of item type shared with the current user
363
	 * @param string $itemType
364
	 * @param string $itemTarget
365
	 * @param int $format (optional) Format type must be defined by the backend
366
	 * @param mixed $parameters (optional)
367
	 * @param boolean $includeCollections (optional)
368
	 * @return mixed Return depends on format
369
	 */
370
	public static function getItemSharedWith($itemType, $itemTarget, $format = self::FORMAT_NONE,
371
											 $parameters = null, $includeCollections = false) {
372
		return self::getItems($itemType, $itemTarget, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format,
0 ignored issues
show
Bug introduced by
It seems like \OC_User::getUser() targeting OC_User::getUser() can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
373
			$parameters, 1, $includeCollections);
374
	}
375
376
	/**
377
	 * Get the item of item type shared with a given user by source
378
	 * @param string $itemType
379
	 * @param string $itemSource
380
	 * @param string $user User to whom the item was shared
381
	 * @param string $owner Owner of the share
382
	 * @param int $shareType only look for a specific share type
383
	 * @return array Return list of items with item_target, permissions and expiration
384
	 */
385
	public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
386 View Code Duplication
		if ($itemType === 'file' || $itemType === 'folder') {
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...
387
			throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
388
		}
389
		$shares = [];
390
391
		$where = 'WHERE';
392
		$column = 'item_source';
393
394
		$select = self::createSelectStatement(self::FORMAT_NONE);
395
396
		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
397
		$arguments = [$itemSource, $itemType];
398
		// for link shares $user === null
399
		if ($user !== null) {
400
			$where .= ' AND `share_with` = ? ';
401
			$arguments[] = $user;
402
		}
403
404
		if ($shareType !== null) {
405
			$where .= ' AND `share_type` = ? ';
406
			$arguments[] = $shareType;
407
		}
408
409
		if ($owner !== null) {
410
			$where .= ' AND `uid_owner` = ? ';
411
			$arguments[] = $owner;
412
		}
413
414
		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where);
415
416
		$result = \OC_DB::executeAudited($query, $arguments);
417
418
		while ($row = $result->fetchRow()) {
419
			$shares[] = $row;
420
		}
421
422
		//if didn't found a result than let's look for a group share.
423
		if (empty($shares) && $user !== null) {
424
			$groups = self::getGroupsForUser($user);
425
426
			if (!empty($groups)) {
427
				$where = ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
428
				$arguments = [$itemSource, $itemType, $groups];
429
				$types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
430
431
				if ($owner !== null) {
432
					$where .= ' AND `uid_owner` = ?';
433
					$arguments[] = $owner;
434
					$types[] = null;
435
				}
436
437
				// TODO: inject connection, hopefully one day in the future when this
438
				// class isn't static anymore...
439
				$conn = \OC::$server->getDatabaseConnection();
440
				$result = $conn->executeQuery(
441
					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
442
					$arguments,
0 ignored issues
show
Documentation introduced by
$arguments is of type array<integer,string|arr...bject<OC\Group\Group>>>, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
443
					$types
444
				);
445
446
				while ($row = $result->fetch()) {
447
					$shares[] = $row;
448
				}
449
			}
450
		}
451
452
		return $shares;
453
	}
454
455
	/**
456
	 * Get the item of item type shared with the current user by source
457
	 * @param string $itemType
458
	 * @param string $itemSource
459
	 * @param int $format (optional) Format type must be defined by the backend
460
	 * @param mixed $parameters
461
	 * @param boolean $includeCollections
462
	 * @param string $shareWith (optional) define against which user should be checked, default: current user
463
	 * @return array
464
	 */
465
	public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE,
466
													 $parameters = null, $includeCollections = false, $shareWith = null) {
467
		$shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith;
468
		return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format,
0 ignored issues
show
Bug introduced by
It seems like $shareWith defined by $shareWith === null ? \O...:getUser() : $shareWith on line 467 can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
469
			$parameters, 1, $includeCollections, true);
470
	}
471
472
	/**
473
	 * Get the item of item type shared by a link
474
	 * @param string $itemType
475
	 * @param string $itemSource
476
	 * @param string $uidOwner Owner of link
477
	 * @return array
478
	 */
479
	public static function getItemSharedWithByLink($itemType, $itemSource, $uidOwner) {
480
		return self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null, $uidOwner, self::FORMAT_NONE,
481
			null, 1);
482
	}
483
484
	/**
485
	 * Based on the given token the share information will be returned - password protected shares will be verified
486
	 * @param string $token
487
	 * @param bool $checkPasswordProtection
488
	 * @return array|boolean false will be returned in case the token is unknown or unauthorized
489
	 */
490
	public static function getShareByToken($token, $checkPasswordProtection = true) {
491
		$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?', 1);
492
		$result = $query->execute([$token]);
493 View Code Duplication
		if ($result === false) {
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...
494
			\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, \OCP\Util::ERROR);
495
		}
496
		$row = $result->fetchRow();
497
		if ($row === false) {
498
			return false;
499
		}
500
		if (\is_array($row) and self::expireItem($row)) {
501
			return false;
502
		}
503
504
		// password protected shares need to be authenticated
505
		if ($checkPasswordProtection && !\OCP\Share::checkPasswordProtectedShare($row)) {
506
			return false;
507
		}
508
509
		return $row;
510
	}
511
512
	/**
513
	 * resolves reshares down to the last real share
514
	 * @param array $linkItem
515
	 * @return array item owner
516
	 */
517
	public static function resolveReShare($linkItem) {
518
		if (isset($linkItem['parent'])) {
519
			$parent = $linkItem['parent'];
520
			while (isset($parent)) {
521
				$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `id` = ?', 1);
522
				$item = $query->execute([$parent])->fetchRow();
523
				if (isset($item['parent'])) {
524
					$parent = $item['parent'];
525
				} else {
526
					return $item;
527
				}
528
			}
529
		}
530
		return $linkItem;
531
	}
532
533
	/**
534
	 * Get the shared items of item type owned by the current user
535
	 * @param string $itemType
536
	 * @param int $format (optional) Format type must be defined by the backend
537
	 * @param mixed $parameters
538
	 * @param int $limit Number of items to return (optional) Returns all by default
539
	 * @param boolean $includeCollections
540
	 * @return mixed Return depends on format
541
	 */
542
	public static function getItemsShared($itemType, $format = self::FORMAT_NONE, $parameters = null,
543
										  $limit = -1, $includeCollections = false) {
544
		return self::getItems($itemType, null, null, null, \OC_User::getUser(), $format,
0 ignored issues
show
Bug introduced by
It seems like \OC_User::getUser() targeting OC_User::getUser() can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
545
			$parameters, $limit, $includeCollections);
546
	}
547
548
	/**
549
	 * Get the shared item of item type owned by the current user
550
	 * @param string $itemType
551
	 * @param string $itemSource
552
	 * @param int $format (optional) Format type must be defined by the backend
553
	 * @param mixed $parameters
554
	 * @param boolean $includeCollections
555
	 * @return mixed Return depends on format
556
	 */
557
	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
558
										 $parameters = null, $includeCollections = false) {
559
		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
0 ignored issues
show
Bug introduced by
It seems like \OC_User::getUser() targeting OC_User::getUser() can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
560
			$parameters, -1, $includeCollections);
561
	}
562
563
	/**
564
	 * Get all users an item is shared with
565
	 * @param string $itemType
566
	 * @param string $itemSource
567
	 * @param string $uidOwner
568
	 * @param boolean $includeCollections
569
	 * @param boolean $checkExpireDate
570
	 * @return array Return array of users
571
	 */
572
	public static function getUsersItemShared($itemType, $itemSource, $uidOwner, $includeCollections = false, $checkExpireDate = true) {
573
		$users = [];
574
		$items = self::getItems($itemType, $itemSource, null, null, $uidOwner, self::FORMAT_NONE, null, -1, $includeCollections, false, $checkExpireDate);
575
		if ($items) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $items of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
576
			foreach ($items as $item) {
577
				if ((int)$item['share_type'] === self::SHARE_TYPE_USER) {
578
					$users[] = $item['share_with'];
579
				} elseif ((int)$item['share_type'] === self::SHARE_TYPE_GROUP) {
580
					$users = \array_merge($users, self::usersInGroup($item['share_with']));
581
				}
582
			}
583
		}
584
		return $users;
585
	}
586
587
	/**
588
	 * Share an item with a user, group, or via private link
589
	 * @param string $itemType
590
	 * @param string $itemSource
591
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
592
	 * @param string $shareWith User or group the item is being shared with
593
	 * @param int $permissions CRUDS
594
	 * @param string $itemSourceName
595
	 * @param \DateTime $expirationDate
596
	 * @param bool $passwordChanged
597
	 * @return boolean|string Returns true on success or false on failure, Returns token on success for links
598
	 * @throws \OC\HintException when the share type is remote and the shareWith is invalid
599
	 * @throws \Exception
600
	 */
601
	public static function shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions, $itemSourceName = null, \DateTime $expirationDate = null, $passwordChanged = null) {
602
		$backend = self::getBackend($itemType);
603
		$l = \OC::$server->getL10N('lib');
604
605
		if ($backend->isShareTypeAllowed($shareType) === false) {
606
			$message = 'Sharing %s failed, because the backend does not allow shares from type %i';
607
			$message_t = $l->t('Sharing %s failed, because the backend does not allow shares from type %i', [$itemSourceName, $shareType]);
608
			\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $shareType), \OCP\Util::DEBUG);
609
			throw new \Exception($message_t);
610
		}
611
612
		$uidOwner = \OC_User::getUser();
613
		$shareWithinGroupOnly = self::shareWithGroupMembersOnly();
614
		$shareWithMembershipGroupOnly = self::shareWithMembershipGroupOnly();
615
616
		if ($itemSourceName === null) {
617
			$itemSourceName = $itemSource;
618
		}
619
		$itemName = $itemSourceName;
620
621
		//Validate expirationDate
622
		if ($expirationDate !== null) {
623
			try {
624
				/*
625
				 * Reuse the validateExpireDate.
626
				 * We have to pass time() since the second arg is the time
627
				 * the item was shared, since it is not shared yet we just use
628
				 * the current time.
629
				 */
630
				$expirationDate = self::validateExpireDate($expirationDate->format('Y-m-d'), \time(), $itemType, $itemSource);
631
			} catch (\Exception $e) {
632
				throw new \OC\HintException($e->getMessage(), $e->getMessage(), 404);
633
			}
634
		}
635
636
		// Verify share type and sharing conditions are met
637
		if ($shareType === self::SHARE_TYPE_USER) {
638 View Code Duplication
			if ($shareWith == $uidOwner) {
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...
639
				$message = 'Sharing %s failed, because you can not share with yourself';
640
				$message_t = $l->t('Sharing %s failed, because you can not share with yourself', [$itemName]);
641
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
642
				throw new \Exception($message_t);
643
			}
644 View Code Duplication
			if (!\OC_User::userExists($shareWith)) {
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...
645
				$message = 'Sharing %s failed, because the user %s does not exist';
646
				$message_t = $l->t('Sharing %s failed, because the user %s does not exist', [$itemSourceName, $shareWith]);
647
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
648
				throw new \Exception($message_t);
649
			}
650
			if ($shareWithinGroupOnly) {
651
				$inGroup = \array_intersect(self::getGroupsForUser($uidOwner), self::getGroupsForUser($shareWith));
652 View Code Duplication
				if (empty($inGroup)) {
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...
653
					$message = 'Sharing %s failed, because the user '
654
						.'%s is not a member of any groups that %s is a member of';
655
					$message_t = $l->t('Sharing %s failed, because the user %s is not a member of any groups that %s is a member of', [$itemName, $shareWith, $uidOwner]);
656
					\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemName, $shareWith, $uidOwner), \OCP\Util::DEBUG);
657
					throw new \Exception($message_t);
658
				}
659
			}
660
			// Check if the item source is already shared with the user, either from the same owner or a different user
661 View Code Duplication
			if ($checkExists = self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups,
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...
662
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
663
				// Only allow the same share to occur again if it is the same
664
				// owner and is not a user share, this use case is for increasing
665
				// permissions for a specific user
666
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
667
					$message = 'Sharing %s failed, because this item is already shared with %s';
668
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', [$itemSourceName, $shareWith]);
669
					\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
670
					throw new \Exception($message_t);
671
				}
672
			}
673 View Code Duplication
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_USER,
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...
674
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
675
				// Only allow the same share to occur again if it is the same
676
				// owner and is not a user share, this use case is for increasing
677
				// permissions for a specific user
678
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
679
					$message = 'Sharing %s failed, because this item is already shared with user %s';
680
					$message_t = $l->t('Sharing %s failed, because this item is already shared with user %s', [$itemSourceName, $shareWith]);
681
					\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $shareWith), \OCP\Util::ERROR);
682
					throw new \Exception($message_t);
683
				}
684
			}
685
		} elseif ($shareType === self::SHARE_TYPE_GROUP) {
686
			if (!\OC::$server->getGroupManager()->groupExists($shareWith)) {
687
				$message = 'Sharing %s failed, because the group %s does not exist';
688
				$message_t = $l->t('Sharing %s failed, because the group %s does not exist', [$itemSourceName, $shareWith]);
689
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
690
				throw new \Exception($message_t);
691
			}
692 View Code Duplication
			if ($shareWithMembershipGroupOnly && !\OC::$server->getGroupManager()->inGroup($uidOwner, $shareWith)) {
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...
693
				$message = 'Sharing %s failed, because '
694
					.'%s is not a member of the group %s';
695
				$message_t = $l->t('Sharing %s failed, because %s is not a member of the group %s', [$itemSourceName, $uidOwner, $shareWith]);
696
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $uidOwner, $shareWith), \OCP\Util::DEBUG);
697
				throw new \Exception($message_t);
698
			}
699
			// Check if the item source is already shared with the group, either from the same owner or a different user
700
			// The check for each user in the group is done inside the put() function
701
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_GROUP, $shareWith,
702
				null, self::FORMAT_NONE, null, 1, true, true)) {
703
				if ($checkExists['share_with'] === $shareWith && $checkExists['share_type'] === \OCP\Share::SHARE_TYPE_GROUP) {
704
					$message = 'Sharing %s failed, because this item is already shared with %s';
705
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', [$itemSourceName, $shareWith]);
706
					\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
707
					throw new \Exception($message_t);
708
				}
709
			}
710
			// Convert share with into an array with the keys group and users
711
			$group = $shareWith;
712
			$usersInGroup = \OC::$server->getGroupManager()->get($group)->getUsers();
713
			$usersInGroup = \array_values(\array_map(function (IUser $u) {
714
				return $u->getUID();
715
			}, $usersInGroup));
716
			$shareWith = [];
717
			$shareWith['group'] = $group;
718
			$shareWith['users'] = \array_diff($usersInGroup, [$uidOwner]);
719
		} elseif ($shareType === self::SHARE_TYPE_LINK) {
720
			$updateExistingShare = false;
721
			if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_links', 'yes') == 'yes') {
722
723
				// IF the password is changed via the old ajax endpoint verify it before deleting the old share
724
				if ($passwordChanged === true) {
725
					self::verifyPassword($shareWith);
726
				}
727
728
				// when updating a link share
729
				// FIXME Don't delete link if we update it
730
				if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null,
731
					$uidOwner, self::FORMAT_NONE, null, 1)) {
0 ignored issues
show
Bug introduced by
It seems like $uidOwner defined by \OC_User::getUser() on line 612 can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
732
					// remember old token
733
					$oldToken = $checkExists['token'];
734
					$oldPermissions = $checkExists['permissions'];
735
					//delete the old share
736
					Helper::delete($checkExists['id']);
737
					$updateExistingShare = true;
738
				}
739
740
				if ($passwordChanged === null) {
741
					// Generate hash of password - same method as user passwords
742
					if (\is_string($shareWith) && $shareWith !== '') {
743
						self::verifyPassword($shareWith);
744
						$shareWith = \OC::$server->getHasher()->hash($shareWith);
745
					} else {
746
						// reuse the already set password, but only if we change permissions
747
						// otherwise the user disabled the password protection
748
						if ($checkExists && (int)$permissions !== (int)$oldPermissions) {
0 ignored issues
show
Bug introduced by
The variable $oldPermissions does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug Best Practice introduced by
The expression $checkExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
749
							$shareWith = $checkExists['share_with'];
750
						}
751
					}
752
				} else {
753
					if ($passwordChanged === true) {
754
						if (\is_string($shareWith) && $shareWith !== '') {
755
							self::verifyPassword($shareWith);
756
							$shareWith = \OC::$server->getHasher()->hash($shareWith);
757
						}
758
					} elseif ($updateExistingShare) {
759
						$shareWith = $checkExists['share_with'];
760
					}
761
				}
762
763 View Code Duplication
				if (\OCP\Util::isPublicLinkPasswordRequired() && empty($shareWith)) {
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Util::isPublicLinkPasswordRequired() has been deprecated.

This method has been deprecated.

Loading history...
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...
764
					$message = 'You need to provide a password to create a public link, only protected links are allowed';
765
					$message_t = $l->t('You need to provide a password to create a public link, only protected links are allowed');
766
					\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
767
					throw new \Exception($message_t);
768
				}
769
770
				if ($updateExistingShare === false &&
771
					self::isDefaultExpireDateEnabled() &&
772
					empty($expirationDate)) {
773
					$expirationDate = Helper::calcExpireDate();
774
				}
775
776
				// Generate token
777
				if (isset($oldToken)) {
778
					$token = $oldToken;
779
				} else {
780
					$token = \OC::$server->getSecureRandom()->generate(self::TOKEN_LENGTH,
781
						\OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_UPPER.
782
						\OCP\Security\ISecureRandom::CHAR_DIGITS
783
					);
784
				}
785
				$result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions,
0 ignored issues
show
Bug introduced by
It seems like $uidOwner defined by \OC_User::getUser() on line 612 can also be of type boolean; however, OC\Share\Share::put() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
786
					null, $token, $itemSourceName, $expirationDate);
787
				if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
788
					return $token;
789
				} else {
790
					return false;
791
				}
792
			}
793
			$message = 'Sharing %s failed, because sharing with links is not allowed';
794
			$message_t = $l->t('Sharing %s failed, because sharing with links is not allowed', [$itemSourceName]);
795
			\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
796
			throw new \Exception($message_t);
797
		} elseif ($shareType === self::SHARE_TYPE_REMOTE) {
798
799
			/*
800
			 * Check if item is not already shared with the remote user
801
			 */
802
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_REMOTE,
803
				$shareWith, $uidOwner, self::FORMAT_NONE, null, 1, true, true)) {
0 ignored issues
show
Bug introduced by
It seems like $uidOwner defined by \OC_User::getUser() on line 612 can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
804
				$message = 'Sharing %s failed, because this item is already shared with %s';
805
				$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', [$itemSourceName, $shareWith]);
806
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
807
				throw new \Exception($message_t);
808
			}
809
810
			// don't allow federated shares if source and target server are the same
811
			list($user, $remote) = Helper::splitUserRemote($shareWith);
812
			$currentServer = self::removeProtocolFromUrl(\OC::$server->getURLGenerator()->getAbsoluteURL('/'));
813
			$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
814 View Code Duplication
			if (Helper::isSameUserOnSameServer($user, $remote, $currentUser, $currentServer)) {
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...
815
				$message = 'Not allowed to create a federated share with the same user.';
816
				$message_t = $l->t('Not allowed to create a federated share with the same user');
817
				\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
818
				throw new \Exception($message_t);
819
			}
820
821
			$token = \OC::$server->getSecureRandom()->generate(self::TOKEN_LENGTH, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_UPPER .
822
				\OCP\Security\ISecureRandom::CHAR_DIGITS);
823
824
			$shareWith = $user . '@' . $remote;
825
			$shareId = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, $token, $itemSourceName);
0 ignored issues
show
Bug introduced by
It seems like $uidOwner defined by \OC_User::getUser() on line 612 can also be of type boolean; however, OC\Share\Share::put() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
826
827
			$send = false;
828
			if ($shareId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $shareId of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
829
				$send = self::sendRemoteShare($token, $shareWith, $itemSourceName, $shareId, $uidOwner);
0 ignored issues
show
Bug introduced by
It seems like $uidOwner defined by \OC_User::getUser() on line 612 can also be of type boolean; however, OC\Share\Share::sendRemoteShare() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
830
			}
831
832
			if ($send === false) {
833
				$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
834
				self::unshare($itemType, $itemSource, $shareType, $shareWith, $currentUser);
835
				$message_t = $l->t('Sharing %s failed, could not find %s, check spelling and server availability.', [$itemSourceName, $shareWith]);
836
				throw new \Exception($message_t);
837
			}
838
839
			return $send;
840 View Code Duplication
		} else {
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...
841
			// Future share types need to include their own conditions
842
			$message = 'Share type %s is not valid for %s';
843
			$message_t = $l->t('Share type %s is not valid for %s', [$shareType, $itemSource]);
844
			\OCP\Util::writeLog('OCP\Share', \sprintf($message, $shareType, $itemSource), \OCP\Util::DEBUG);
845
			throw new \Exception($message_t);
846
		}
847
848
		// Put the item into the database
849
		$result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, null, $itemSourceName, $expirationDate);
0 ignored issues
show
Bug introduced by
It seems like $shareWith defined by array() on line 716 can also be of type array<string,?,{"users":"array<integer,?>"}>; however, OC\Share\Share::put() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $uidOwner defined by \OC_User::getUser() on line 612 can also be of type boolean; however, OC\Share\Share::put() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
850
851
		return $result ? true : false;
852
	}
853
854
	/**
855
	 * Unshare an item from a user, group, or delete a private link
856
	 * @param string $itemType
857
	 * @param string $itemSource
858
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
859
	 * @param string $shareWith User or group the item is being shared with
860
	 * @param string $owner owner of the share, if null the current user is used
861
	 * @return boolean true on success or false on failure
862
	 */
863
	public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) {
864
865
		// check if it is a valid itemType
866
		self::getBackend($itemType);
867
868
		$items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType);
869
870
		$toDelete = [];
871
		$newParent = null;
872
		$currentUser = $owner ? $owner : \OC_User::getUser();
873
		foreach ($items as $item) {
874
			// delete the item with the expected share_type and owner
875
			if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
876
				$toDelete = $item;
877
			// if there is more then one result we don't have to delete the children
878
				// but update their parent. For group shares the new parent should always be
879
				// the original group share and not the db entry with the unique name
880
			} elseif ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
881
				$newParent = $item['parent'];
882
			} else {
883
				$newParent = $item['id'];
884
			}
885
		}
886
887
		if (!empty($toDelete)) {
888
			self::unshareItem($toDelete, $newParent);
889
			return true;
890
		}
891
		return false;
892
	}
893
894
	/**
895
	 * Unshare an item from all users, groups, and remove all links
896
	 * @param string $itemType
897
	 * @param string $itemSource
898
	 * @return boolean true on success or false on failure
899
	 */
900
	public static function unshareAll($itemType, $itemSource) {
901
		// Get all of the owners of shares of this item.
902
		$query = \OC_DB::prepare('SELECT `uid_owner` from `*PREFIX*share` WHERE `item_type`=? AND `item_source`=?');
903
		$result = $query->execute([$itemType, $itemSource]);
904
		$shares = [];
905
		// Add each owner's shares to the array of all shares for this item.
906
		while ($row = $result->fetchRow()) {
907
			$shares = \array_merge($shares, self::getItems($itemType, $itemSource, null, null, $row['uid_owner']));
908
		}
909
		if (!empty($shares)) {
910
			// Pass all the vars we have for now, they may be useful
911
			$hookParams = [
912
				'itemType' => $itemType,
913
				'itemSource' => $itemSource,
914
				'shares' => $shares,
915
			];
916
			\OC_Hook::emit('OCP\Share', 'pre_unshareAll', $hookParams);
917
			foreach ($shares as $share) {
918
				self::unshareItem($share);
919
			}
920
			\OC_Hook::emit('OCP\Share', 'post_unshareAll', $hookParams);
921
			return true;
922
		}
923
		return false;
924
	}
925
926
	/**
927
	 * Unshare an item shared with the current user
928
	 * @param string $itemType
929
	 * @param string $itemOrigin Item target or source
930
	 * @param boolean $originIsSource true if $itemOrigin is the source, false if $itemOrigin is the target (optional)
931
	 * @return boolean true on success or false on failure
932
	 *
933
	 * Unsharing from self is not allowed for items inside collections
934
	 */
935
	public static function unshareFromSelf($itemType, $itemOrigin, $originIsSource = false) {
936 View Code Duplication
		if ($itemType === 'file' || $itemType === 'folder') {
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...
937
			throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
938
		}
939
		$originType = ($originIsSource) ? 'source' : 'target';
940
		$uid = \OCP\User::getUser();
941
942
		$statement = 'SELECT * FROM `*PREFIX*share` WHERE `item_type` = ? and `item_' . $originType . '` = ?';
943
944
		$query = \OCP\DB::prepare($statement);
945
		$result = $query->execute([$itemType, $itemOrigin]);
946
947
		$shares = $result->fetchAll();
948
949
		$listOfUnsharedItems = [];
950
951
		$itemUnshared = false;
952
		foreach ($shares as $share) {
953
			if ((int)$share['share_type'] === \OCP\Share::SHARE_TYPE_USER &&
954
				$share['share_with'] === $uid) {
955
				$deletedShares = Helper::delete($share['id']);
956
				$shareTmp = [
957
					'id' => $share['id'],
958
					'shareWith' => $share['share_with'],
959
					'itemTarget' => $share['item_target'],
960
					'itemType' => $share['item_type'],
961
					'shareType' => (int)$share['share_type'],
962
				];
963
				if (isset($share['file_target'])) {
964
					$shareTmp['fileTarget'] = $share['file_target'];
965
				}
966
				$listOfUnsharedItems = \array_merge($listOfUnsharedItems, $deletedShares, [$shareTmp]);
967
				$itemUnshared = true;
968
				break;
969
			} elseif ((int)$share['share_type'] === \OCP\Share::SHARE_TYPE_GROUP) {
970
				if (\OC::$server->getGroupManager()->inGroup($uid, $share['share_with'])) {
971
					$groupShare = $share;
972
				}
973
			} elseif ((int)$share['share_type'] === self::$shareTypeGroupUserUnique &&
974
				$share['share_with'] === $uid) {
975
				$uniqueGroupShare = $share;
976
			}
977
		}
978
979
		if (!$itemUnshared && isset($groupShare) && !isset($uniqueGroupShare)) {
980
			$query = \OC_DB::prepare('INSERT INTO `*PREFIX*share`'
981
				.' (`item_type`, `item_source`, `item_target`, `parent`, `share_type`,'
982
				.' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`, `file_target`)'
983
				.' VALUES (?,?,?,?,?,?,?,?,?,?,?)');
984
			$query->execute([$groupShare['item_type'], $groupShare['item_source'], $groupShare['item_target'],
985
				$groupShare['id'], self::$shareTypeGroupUserUnique,
986
				\OC_User::getUser(), $groupShare['uid_owner'], 0, $groupShare['stime'], $groupShare['file_source'],
987
				$groupShare['file_target']]);
988
			$shareTmp = [
989
				'id' => $groupShare['id'],
990
				'shareWith' => $groupShare['share_with'],
991
				'itemTarget' => $groupShare['item_target'],
992
				'itemType' => $groupShare['item_type'],
993
				'shareType' => (int)$groupShare['share_type'],
994
			];
995
			if (isset($groupShare['file_target'])) {
996
				$shareTmp['fileTarget'] = $groupShare['file_target'];
997
			}
998
			$listOfUnsharedItems = \array_merge($listOfUnsharedItems, [$shareTmp]);
999
			$itemUnshared = true;
1000
		} elseif (!$itemUnshared && isset($uniqueGroupShare)) {
1001
			$query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = ? WHERE `id` = ?');
1002
			$query->execute([0, $uniqueGroupShare['id']]);
1003
			$shareTmp = [
1004
				'id' => $uniqueGroupShare['id'],
1005
				'shareWith' => $uniqueGroupShare['share_with'],
1006
				'itemTarget' => $uniqueGroupShare['item_target'],
1007
				'itemType' => $uniqueGroupShare['item_type'],
1008
				'shareType' => (int)$uniqueGroupShare['share_type'],
1009
			];
1010
			if (isset($uniqueGroupShare['file_target'])) {
1011
				$shareTmp['fileTarget'] = $uniqueGroupShare['file_target'];
1012
			}
1013
			$listOfUnsharedItems = \array_merge($listOfUnsharedItems, [$shareTmp]);
1014
			$itemUnshared = true;
1015
		}
1016
1017
		if ($itemUnshared) {
1018
			\OC_Hook::emit('OCP\Share', 'post_unshareFromSelf',
1019
				['unsharedItems' => $listOfUnsharedItems, 'itemType' => $itemType]);
1020
		}
1021
1022
		return $itemUnshared;
1023
	}
1024
1025
	/**
1026
	 * sent status if users got informed by mail about share
1027
	 * @param string $itemType
1028
	 * @param string $itemSource
1029
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
1030
	 * @param string $recipient with whom was the file shared
1031
	 * @param boolean $status
1032
	 */
1033
	public static function setSendMailStatus($itemType, $itemSource, $shareType, $recipient, $status) {
1034
		$status = $status ? 1 : 0;
1035
1036
		$query = \OC_DB::prepare(
1037
			'UPDATE `*PREFIX*share`
1038
					SET `mail_send` = ?
1039
					WHERE `item_type` = ? AND `item_source` = ? AND `share_type` = ? AND `share_with` = ?');
1040
1041
		$result = $query->execute([$status, $itemType, $itemSource, $shareType, $recipient]);
1042
1043
		if ($result === false) {
1044
			\OCP\Util::writeLog('OCP\Share', 'Couldn\'t set send mail status', \OCP\Util::ERROR);
1045
		}
1046
	}
1047
1048
	/**
1049
	 * Set the permissions of an item for a specific user or group
1050
	 * @param string $itemType
1051
	 * @param string $itemSource
1052
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
1053
	 * @param string $shareWith User or group the item is being shared with
1054
	 * @param int $permissions CRUDS permissions
1055
	 * @return boolean true on success or false on failure
1056
	 * @throws \Exception when trying to grant more permissions then the user has himself
1057
	 */
1058
	public static function setPermissions($itemType, $itemSource, $shareType, $shareWith, $permissions) {
1059
		$l = \OC::$server->getL10N('lib');
1060
		$connection = \OC::$server->getDatabaseConnection();
1061
1062
		$intArrayToLiteralArray = function ($intArray, $eb) {
1063
			return \array_map(function ($int) use ($eb) {
1064
				return $eb->literal((int)$int, 'integer');
1065
			}, $intArray);
1066
		};
1067
		$sanitizeItem = function ($item) {
1068
			$item['id'] = (int)$item['id'];
1069
			$item['premissions'] = (int)$item['permissions'];
1070
			return $item;
1071
		};
1072
1073
		if ($rootItem = self::getItems($itemType, $itemSource, $shareType, $shareWith,
1074
			\OC_User::getUser(), self::FORMAT_NONE, null, 1, false)) {
0 ignored issues
show
Bug introduced by
It seems like \OC_User::getUser() targeting OC_User::getUser() can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1075
			// Check if this item is a reshare and verify that the permissions
1076
			// granted don't exceed the parent shared item
1077
			if (isset($rootItem['parent'])) {
1078
				$qb = $connection->getQueryBuilder();
1079
				$qb->select('permissions')
1080
					->from('share')
1081
					->where($qb->expr()->eq('id', $qb->createParameter('id')))
1082
					->setParameter(':id', $rootItem['parent']);
1083
				$dbresult = $qb->execute();
1084
1085
				$result = $dbresult->fetch();
1086
				$dbresult->closeCursor();
1087
				if (~(int)$result['permissions'] & $permissions) {
1088
					$message = 'Setting permissions for %s failed,'
1089
						.' because the permissions exceed permissions granted to %s';
1090
					$message_t = $l->t('Setting permissions for %s failed, because the permissions exceed permissions granted to %s', [$itemSource, \OC_User::getUser()]);
1091
					\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSource, \OC_User::getUser()), \OCP\Util::DEBUG);
1092
					throw new \Exception($message_t);
1093
				}
1094
			}
1095
			$qb = $connection->getQueryBuilder();
1096
			$qb->update('share')
1097
				->set('permissions', $qb->createParameter('permissions'))
1098
				->where($qb->expr()->eq('id', $qb->createParameter('id')))
1099
				->setParameter(':id', $rootItem['id'])
1100
				->setParameter(':permissions', $permissions);
1101
			$qb->execute();
1102
1103
			// Share id's to update with the new permissions
1104
			$ids = [];
1105
			$items = [];
1106
1107
			// Check if permissions were removed
1108
			if ((int)$rootItem['permissions'] & ~$permissions) {
1109
				// If share permission is removed all reshares must be deleted
1110
				if (($rootItem['permissions'] & \OCP\Constants::PERMISSION_SHARE) && (~$permissions & \OCP\Constants::PERMISSION_SHARE)) {
1111
					// delete all shares, keep parent and group children
1112
					Helper::delete($rootItem['id'], true, null, null, true);
1113
				}
1114
1115
				// Remove permission from all children
1116
				$parents = [$rootItem['id']];
1117
				while (!empty($parents)) {
1118
					$parents = $intArrayToLiteralArray($parents, $qb->expr());
1119
					$qb = $connection->getQueryBuilder();
1120
					$qb->select('id', 'permissions', 'item_type')
0 ignored issues
show
Unused Code introduced by
The call to IQueryBuilder::select() has too many arguments starting with 'permissions'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1121
						->from('share')
1122
						->where($qb->expr()->in('parent', $parents));
1123
					$result = $qb->execute();
1124
					// Reset parents array, only go through loop again if
1125
					// items are found that need permissions removed
1126
					$parents = [];
1127
					while ($item = $result->fetch()) {
1128
						$item = $sanitizeItem($item);
1129
1130
						$items[] = $item;
1131
						// Check if permissions need to be removed
1132
						if ($item['permissions'] & ~$permissions) {
1133
							// Add to list of items that need permissions removed
1134
							$ids[] = $item['id'];
1135
							$parents[] = $item['id'];
1136
						}
1137
					}
1138
					$result->closeCursor();
1139
				}
1140
1141
				// Remove the permissions for all reshares of this item
1142
				if (!empty($ids)) {
1143
					$ids = "'".\implode("','", $ids)."'";
1144
					// TODO this should be done with Doctrine platform objects
1145
					if (\OC::$server->getConfig()->getSystemValue("dbtype") === 'oci') {
1146
						$andOp = 'BITAND(`permissions`, ?)';
1147
					} else {
1148
						$andOp = '`permissions` & ?';
1149
					}
1150
					$query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = '.$andOp
1151
						.' WHERE `id` IN ('.$ids.')');
1152
					$query->execute([$permissions]);
1153
				}
1154
			}
1155
1156
			/*
1157
			 * Permissions were added
1158
			 * Update all USERGROUP shares. (So group shares where the user moved their mountpoint).
1159
			 */
1160
			if ($permissions & ~(int)$rootItem['permissions']) {
1161
				$qb = $connection->getQueryBuilder();
1162
				$qb->select('id', 'permissions', 'item_type')
0 ignored issues
show
Unused Code introduced by
The call to IQueryBuilder::select() has too many arguments starting with 'permissions'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1163
					->from('share')
1164
					->where($qb->expr()->eq('parent', $qb->createParameter('parent')))
1165
					->andWhere($qb->expr()->eq('share_type', $qb->createParameter('share_type')))
1166
					->andWhere($qb->expr()->neq('permissions', $qb->createParameter('shareDeleted')))
1167
					->setParameter(':parent', (int)$rootItem['id'])
1168
					->setParameter(':share_type', 2)
1169
					->setParameter(':shareDeleted', 0);
1170
				$result = $qb->execute();
1171
1172
				$ids = [];
1173
				while ($item = $result->fetch()) {
1174
					$item = $sanitizeItem($item);
1175
					$items[] = $item;
1176
					$ids[] = $item['id'];
1177
				}
1178
				$result->closeCursor();
1179
1180
				// Add permssions for all USERGROUP shares of this item
1181
				if (!empty($ids)) {
1182
					$ids = $intArrayToLiteralArray($ids, $qb->expr());
1183
1184
					$qb = $connection->getQueryBuilder();
1185
					$qb->update('share')
1186
						->set('permissions', $qb->createParameter('permissions'))
1187
						->where($qb->expr()->in('id', $ids))
1188
						->setParameter(':permissions', $permissions);
1189
					$qb->execute();
1190
				}
1191
			}
1192
1193
			foreach ($items as $item) {
1194
				\OC_Hook::emit('OCP\Share', 'post_update_permissions', ['share' => $item]);
1195
			}
1196
1197
			return true;
1198
		}
1199
		$message = 'Setting permissions for %s failed, because the item was not found';
1200
		$message_t = $l->t('Setting permissions for %s failed, because the item was not found', [$itemSource]);
1201
1202
		\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSource), \OCP\Util::DEBUG);
1203
		throw new \Exception($message_t);
1204
	}
1205
1206
	/**
1207
	 * validate expiration date if it meets all constraints
1208
	 *
1209
	 * @param string $expireDate well formatted date string, e.g. "DD-MM-YYYY"
1210
	 * @param string $shareTime timestamp when the file was shared
1211
	 * @param string $itemType
1212
	 * @param string $itemSource
1213
	 * @return \DateTime validated date
1214
	 * @throws \Exception when the expire date is in the past or further in the future then the enforced date
1215
	 */
1216
	private static function validateExpireDate($expireDate, $shareTime, $itemType, $itemSource) {
1217
		$l = \OC::$server->getL10N('lib');
1218
		$date = new \DateTime($expireDate);
1219
		$today = new \DateTime('now');
1220
1221
		// if the user doesn't provide a share time we need to get it from the database
1222
		// fall-back mode to keep API stable, because the $shareTime parameter was added later
1223
		$defaultExpireDateEnforced = \OCP\Util::isDefaultExpireDateEnforced();
1224
		if ($defaultExpireDateEnforced && $shareTime === null) {
1225
			$items = self::getItemShared($itemType, $itemSource);
1226
			$firstItem = \reset($items);
1227
			$shareTime = (int)$firstItem['stime'];
1228
		}
1229
1230
		if ($defaultExpireDateEnforced) {
1231
			// initialize max date with share time
1232
			$maxDate = new \DateTime();
1233
			$maxDate->setTimestamp($shareTime);
1234
			$maxDays = \OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1235
			$maxDate->add(new \DateInterval('P' . $maxDays . 'D'));
1236
			if ($date > $maxDate) {
1237
				$warning = 'Cannot set expiration date. Shares cannot expire later than ' . $maxDays . ' after they have been shared';
1238
				$warning_t = $l->t('Cannot set expiration date. Shares cannot expire later than %s after they have been shared', [$maxDays]);
1239
				\OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN);
1240
				throw new \Exception($warning_t);
1241
			}
1242
		}
1243
1244 View Code Duplication
		if ($date < $today) {
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...
1245
			$message = 'Cannot set expiration date. Expiration date is in the past';
1246
			$message_t = $l->t('Cannot set expiration date. Expiration date is in the past');
1247
			\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::WARN);
1248
			throw new \Exception($message_t);
1249
		}
1250
1251
		return $date;
1252
	}
1253
1254
	/**
1255
	 * Set expiration date for a share
1256
	 * @param string $itemType
1257
	 * @param string $itemSource
1258
	 * @param string $date expiration date
1259
	 * @param int $shareTime timestamp from when the file was shared
1260
	 * @return boolean
1261
	 * @throws \Exception when the expire date is not set, in the past or further in the future then the enforced date
1262
	 */
1263
	public static function setExpirationDate($itemType, $itemSource, $date, $shareTime = null) {
1264
		$user = \OC_User::getUser();
1265
		$l = \OC::$server->getL10N('lib');
1266
1267
		if ($date == '') {
1268 View Code Duplication
			if (\OCP\Util::isDefaultExpireDateEnforced()) {
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...
1269
				$warning = 'Cannot clear expiration date. Shares are required to have an expiration date.';
1270
				$warning_t = $l->t('Cannot clear expiration date. Shares are required to have an expiration date.');
1271
				\OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN);
1272
				throw new \Exception($warning_t);
1273
			} else {
1274
				$date = null;
1275
			}
1276
		} else {
1277
			$date = self::validateExpireDate($date, $shareTime, $itemType, $itemSource);
1278
		}
1279
		$query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `expiration` = ? WHERE `item_type` = ? AND `item_source` = ?  AND `uid_owner` = ? AND `share_type` = ?');
1280
		$query->bindValue(1, $date, 'datetime');
1281
		$query->bindValue(2, $itemType);
1282
		$query->bindValue(3, $itemSource);
1283
		$query->bindValue(4, $user);
1284
		$query->bindValue(5, \OCP\Share::SHARE_TYPE_LINK);
1285
1286
		$query->execute();
1287
1288
		\OC_Hook::emit('OCP\Share', 'post_set_expiration_date', [
1289
			'itemType' => $itemType,
1290
			'itemSource' => $itemSource,
1291
			'date' => $date,
1292
			'uidOwner' => $user
1293
		]);
1294
1295
		return true;
1296
	}
1297
1298
	/**
1299
	 * Retrieve the owner of a connection
1300
	 *
1301
	 * @param IDBConnection $connection
1302
	 * @param int $shareId
1303
	 * @throws \Exception
1304
	 * @return string uid of share owner
1305
	 */
1306
	private static function getShareOwner(IDBConnection $connection, $shareId) {
1307
		$qb = $connection->getQueryBuilder();
1308
1309
		$qb->select('uid_owner')
1310
			->from('share')
1311
			->where($qb->expr()->eq('id', $qb->createParameter('shareId')))
1312
			->setParameter(':shareId', $shareId);
1313
		$result = $qb->execute();
1314
		$result = $result->fetch();
1315
1316
		if (empty($result)) {
1317
			throw new \Exception('Share not found');
1318
		}
1319
1320
		return $result['uid_owner'];
1321
	}
1322
1323
	/**
1324
	 * Set password for a public link share
1325
	 *
1326
	 * @param IUserSession $userSession
1327
	 * @param IDBConnection $connection
1328
	 * @param IConfig $config
1329
	 * @param int $shareId
1330
	 * @param string $password
1331
	 * @throws \Exception
1332
	 * @return boolean
1333
	 */
1334
	public static function setPassword(IUserSession $userSession,
1335
									   IDBConnection $connection,
1336
									   IConfig $config,
1337
									   $shareId, $password) {
1338
		$user = $userSession->getUser();
1339
		if ($user === null) {
1340
			throw new \Exception("User not logged in");
1341
		}
1342
1343
		$uid = self::getShareOwner($connection, $shareId);
1344
1345
		if ($uid !== $user->getUID()) {
1346
			throw new \Exception('Cannot update share of a different user');
1347
		}
1348
1349
		if ($password === '') {
1350
			$password = null;
1351
		}
1352
1353
		//If passwords are enforced the password can't be null
1354
		if (self::enforcePassword($config) && $password === null) {
1355
			throw new \Exception('Cannot remove password');
1356
		}
1357
1358
		self::verifyPassword($password);
1359
1360
		$qb = $connection->getQueryBuilder();
1361
		$qb->update('share')
1362
			->set('share_with', $qb->createParameter('pass'))
1363
			->where($qb->expr()->eq('id', $qb->createParameter('shareId')))
1364
			->setParameter(':pass', $password === null ? null : \OC::$server->getHasher()->hash($password))
1365
			->setParameter(':shareId', $shareId);
1366
1367
		$qb->execute();
1368
1369
		return true;
1370
	}
1371
1372
	/**
1373
	 * Checks whether a share has expired, calls unshareItem() if yes.
1374
	 * @param array $item Share data (usually database row)
1375
	 * @return boolean True if item was expired, false otherwise.
1376
	 */
1377
	protected static function expireItem(array $item) {
1378
		$result = false;
1379
1380
		// only use default expiration date for link shares
1381
		if ((int) $item['share_type'] === self::SHARE_TYPE_LINK) {
1382
1383
			// calculate expiration date
1384
			if (!empty($item['expiration'])) {
1385
				$userDefinedExpire = new \DateTime($item['expiration']);
1386
				$expires = $userDefinedExpire->getTimestamp();
1387
			} else {
1388
				$expires = null;
1389
			}
1390
1391
			// get default expiration settings
1392
			$defaultSettings = Helper::getDefaultExpireSetting();
1393
			$expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
1394
1395
			if (\is_int($expires)) {
1396
				$now = \time();
1397
				if ($now > $expires) {
1398
					self::unshareItem($item);
1399
					$result = true;
1400
				}
1401
			}
1402
		}
1403
		return $result;
1404
	}
1405
1406
	/**
1407
	 * Unshares a share given a share data array
1408
	 * @param array $item Share data (usually database row)
1409
	 * @param int $newParent parent ID
1410
	 * @return null
1411
	 */
1412
	protected static function unshareItem(array $item, $newParent = null) {
1413
		$shareType = (int)$item['share_type'];
1414
		$shareWith = null;
1415
		if ($shareType !== \OCP\Share::SHARE_TYPE_LINK) {
1416
			$shareWith = $item['share_with'];
1417
		}
1418
1419
		// Pass all the vars we have for now, they may be useful
1420
		$hookParams = [
1421
			'id'            => $item['id'],
1422
			'itemType'      => $item['item_type'],
1423
			'itemSource'    => $item['item_source'],
1424
			'shareType'     => $shareType,
1425
			'shareWith'     => $shareWith,
1426
			'itemParent'    => $item['parent'],
1427
			'uidOwner'      => $item['uid_owner'],
1428
		];
1429
1430
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
1431
		$deletedShares = Helper::delete($item['id'], false, null, $newParent);
1432
		$deletedShares[] = $hookParams;
1433
		$hookParams['deletedShares'] = $deletedShares;
1434
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
1435
		if ((int)$item['share_type'] === \OCP\Share::SHARE_TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
1436
			list(, $remote) = Helper::splitUserRemote($item['share_with']);
1437
			self::sendRemoteUnshare($remote, $item['id'], $item['token']);
1438
		}
1439
	}
1440
1441
	/**
1442
	 * Get the backend class for the specified item type
1443
	 * @param string $itemType
1444
	 * @throws \Exception
1445
	 * @return \OCP\Share_Backend
1446
	 */
1447
	public static function getBackend($itemType) {
1448
		$l = \OC::$server->getL10N('lib');
1449
		if (isset(self::$backends[$itemType])) {
1450
			return self::$backends[$itemType];
1451
		} elseif (isset(self::$backendTypes[$itemType]['class'])) {
1452
			$class = self::$backendTypes[$itemType]['class'];
1453
			if (\class_exists($class)) {
1454
				self::$backends[$itemType] = new $class;
1455 View Code Duplication
				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
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...
1456
					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
1457
					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]);
1458
					\OCP\Util::writeLog('OCP\Share', \sprintf($message, $class), \OCP\Util::ERROR);
1459
					throw new \Exception($message_t);
1460
				}
1461
				return self::$backends[$itemType];
1462 View Code Duplication
			} else {
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...
1463
				$message = 'Sharing backend %s not found';
1464
				$message_t = $l->t('Sharing backend %s not found', [$class]);
1465
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $class), \OCP\Util::ERROR);
1466
				throw new \Exception($message_t);
1467
			}
1468
		}
1469
		$message = 'Sharing backend for %s not found';
1470
		$message_t = $l->t('Sharing backend for %s not found', [$itemType]);
1471
		\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemType), \OCP\Util::ERROR);
1472
		throw new \Exception($message_t);
1473
	}
1474
1475
	/**
1476
	 * Check if resharing is allowed
1477
	 * @return boolean true if allowed or false
1478
	 *
1479
	 * Resharing is allowed by default if not configured
1480
	 */
1481
	public static function isResharingAllowed() {
1482
		if (!isset(self::$isResharingAllowed)) {
1483
			if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
1484
				self::$isResharingAllowed = true;
1485
			} else {
1486
				self::$isResharingAllowed = false;
1487
			}
1488
		}
1489
		return self::$isResharingAllowed;
1490
	}
1491
1492
	/**
1493
	 * Get a list of collection item types for the specified item type
1494
	 * @param string $itemType
1495
	 * @return array
1496
	 */
1497
	private static function getCollectionItemTypes($itemType) {
1498
		$collectionTypes = [$itemType];
1499
		foreach (self::$backendTypes as $type => $backend) {
1500
			if (\in_array($backend['collectionOf'], $collectionTypes)) {
1501
				$collectionTypes[] = $type;
1502
			}
1503
		}
1504
		// TODO Add option for collections to be collection of themselves...
1505
		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection)) {
1506
			unset($collectionTypes[0]);
1507
		}
1508
		// Return array if collections were found or the item type is a
1509
		// collection itself - collections can be inside collections
1510
		if (\count($collectionTypes) > 0) {
1511
			return $collectionTypes;
1512
		}
1513
		return false;
1514
	}
1515
1516
	/**
1517
	 * Get the owners of items shared with a user.
1518
	 *
1519
	 * @param string $user The user the items are shared with.
1520
	 * @param string $type The type of the items shared with the user.
1521
	 * @param boolean $includeCollections Include collection item types (optional)
1522
	 * @param boolean $includeOwner include owner in the list of users the item is shared with (optional)
1523
	 * @return array
1524
	 */
1525
	public static function getSharedItemsOwners($user, $type, $includeCollections = false, $includeOwner = false) {
1526
		// First, we find out if $type is part of a collection (and if that collection is part of
1527
		// another one and so on).
1528
		$collectionTypes = [];
1529
		if (!$includeCollections || !$collectionTypes = self::getCollectionItemTypes($type)) {
1530
			$collectionTypes[] = $type;
1531
		}
1532
1533
		// Of these collection types, along with our original $type, we make a
1534
		// list of the ones for which a sharing backend has been registered.
1535
		// FIXME: Ideally, we wouldn't need to nest getItemsSharedWith in this loop but just call it
1536
		// with its $includeCollections parameter set to true. Unfortunately, this fails currently.
1537
		$allMaybeSharedItems = [];
1538
		foreach ($collectionTypes as $collectionType) {
0 ignored issues
show
Bug introduced by
The expression $collectionTypes of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1539
			if (isset(self::$backends[$collectionType])) {
1540
				$allMaybeSharedItems[$collectionType] = self::getItemsSharedWithUser(
1541
					$collectionType,
1542
					$user,
1543
					self::FORMAT_NONE
1544
				);
1545
			}
1546
		}
1547
1548
		$owners = [];
1549
		if ($includeOwner) {
1550
			$owners[] = $user;
1551
		}
1552
1553
		// We take a look at all shared items of the given $type (or of the collections it is part of)
1554
		// and find out their owners. Then, we gather the tags for the original $type from all owners,
1555
		// and return them as elements of a list that look like "Tag (owner)".
1556
		foreach ($allMaybeSharedItems as $collectionType => $maybeSharedItems) {
1557
			foreach ($maybeSharedItems as $sharedItem) {
1558
				if (isset($sharedItem['id'])) { //workaround for https://github.com/owncloud/core/issues/2814
1559
					$owners[] = $sharedItem['uid_owner'];
1560
				}
1561
			}
1562
		}
1563
1564
		return $owners;
1565
	}
1566
1567
	/**
1568
	 * Get shared items from the database
1569
	 * @param string $itemType
1570
	 * @param string $item Item source or target (optional)
1571
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
1572
	 * @param string $shareWith User or group the item is being shared with
1573
	 * @param string $uidOwner User that is the owner of shared items (optional)
1574
	 * @param int $format Format to convert items to with formatItems() (optional)
1575
	 * @param mixed $parameters to pass to formatItems() (optional)
1576
	 * @param int $limit Number of items to return, -1 to return all matches (optional)
1577
	 * @param boolean $includeCollections Include collection item types (optional)
1578
	 * @param boolean $itemShareWithBySource (optional)
1579
	 * @param boolean $checkExpireDate
1580
	 * @return array
1581
	 *
1582
	 * See public functions getItem(s)... for parameter usage
1583
	 *
1584
	 */
1585
	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
1586
									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
1587
									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate  = true) {
1588
		if (!self::isEnabled()) {
1589
			return [];
1590
		}
1591 View Code Duplication
		if ($itemType === 'file' || $itemType === 'folder') {
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...
1592
			throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
1593
		}
1594
		$backend = self::getBackend($itemType);
1595
		$collectionTypes = false;
1596
		// Get filesystem root to add it to the file target and remove from the
1597
		// file source, match file_source with the file cache
1598
		$root = '';
1599
		$collectionTypes = self::getCollectionItemTypes($itemType);
1600
		if ($includeCollections && !isset($item) && $collectionTypes) {
1601
			// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
1602
			if (!in_array($itemType, $collectionTypes)) {
1603
				$itemTypes = array_merge([$itemType], $collectionTypes);
1604
			} else {
1605
				$itemTypes = $collectionTypes;
1606
			}
1607
			$placeholders = join(',', array_fill(0, count($itemTypes), '?'));
1608
			$where = ' WHERE `item_type` IN ('.$placeholders.'))';
1609
			$queryArgs = $itemTypes;
1610
		} else {
1611
			$where = ' WHERE `item_type` = ?';
1612
			$queryArgs = [$itemType];
1613
		}
1614
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1615
			$where .= ' AND `share_type` != ?';
1616
			$queryArgs[] = self::SHARE_TYPE_LINK;
1617
		}
1618
		if (isset($shareType)) {
1619
			// Include all user and group items
1620
			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
1621
				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
1622
				$queryArgs[] = self::SHARE_TYPE_USER;
1623
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1624
				$queryArgs[] = $shareWith;
1625
				$groups = self::getGroupsForUser($shareWith);
1626 View Code Duplication
				if (!empty($groups)) {
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...
1627
					$placeholders = \join(',', \array_fill(0, \count($groups), '?'));
1628
					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
1629
					$queryArgs[] = self::SHARE_TYPE_GROUP;
1630
					$queryArgs = \array_merge($queryArgs, $groups);
1631
				}
1632
				$where .= ')';
1633
				// Don't include own group shares
1634
				$where .= ' AND `uid_owner` != ?';
1635
				$queryArgs[] = $shareWith;
1636
			} else {
1637
				$where .= ' AND `share_type` = ?';
1638
				$queryArgs[] = $shareType;
1639
				if (isset($shareWith)) {
1640
					$where .= ' AND `share_with` = ?';
1641
					$queryArgs[] = $shareWith;
1642
				}
1643
			}
1644
		}
1645
		if (isset($uidOwner)) {
1646
			$where .= ' AND `uid_owner` = ?';
1647
			$queryArgs[] = $uidOwner;
1648
			if (!isset($shareType)) {
1649
				// Prevent unique user targets for group shares from being selected
1650
				$where .= ' AND `share_type` != ?';
1651
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1652
			}
1653
			$column = 'item_source';
1654
		} else {
1655
			$column = 'item_target';
1656
		}
1657
		if (isset($item)) {
1658
			$collectionTypes = self::getCollectionItemTypes($itemType);
1659
			if ($includeCollections && $collectionTypes) {
1660
				$where .= ' AND (';
1661
			} else {
1662
				$where .= ' AND';
1663
			}
1664
			// If looking for own shared items, check item_source else check item_target
1665
			if (isset($uidOwner) || $itemShareWithBySource) {
1666
				// If item type is a file, file source needs to be checked in case the item was converted
1667
				$where .= ' `item_source` = ?';
1668
				$column = 'item_source';
1669
			} else {
1670
				$where .= ' `item_target` = ?';
1671
			}
1672
			$queryArgs[] = $item;
1673 View Code Duplication
			if ($includeCollections && $collectionTypes) {
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...
1674
				$placeholders = join(',', array_fill(0, count($collectionTypes), '?'));
1675
				$where .= ' OR `item_type` IN ('.$placeholders.'))';
1676
				$queryArgs = \array_merge($queryArgs, $collectionTypes);
1677
			}
1678
		}
1679
1680
		if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
1681
			// Make sure the unique user target is returned if it exists,
1682
			// unique targets should follow the group share in the database
1683
			// If the limit is not 1, the filtering can be done later
1684
			$where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
1685
		} else {
1686
			$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
1687
		}
1688
1689
		if ($limit != -1 && !$includeCollections) {
1690
			// The limit must be at least 3, because filtering needs to be done
1691
			if ($limit < 3) {
1692
				$queryLimit = 3;
1693
			} else {
1694
				$queryLimit = $limit;
1695
			}
1696
		} else {
1697
			$queryLimit = null;
1698
		}
1699
		$select = self::createSelectStatement($format, $uidOwner);
1700
		$root = strlen($root);
1701
		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
1702
		$result = $query->execute($queryArgs);
1703 View Code Duplication
		if ($result === false) {
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...
1704
			\OCP\Util::writeLog('OCP\Share',
1705
				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
1706
				\OCP\Util::ERROR);
1707
		}
1708
		$items = [];
1709
		$targets = [];
1710
		$switchedItems = [];
1711
		$mounts = [];
1712
		while ($row = $result->fetchRow()) {
1713
			self::transformDBResults($row);
1714
			// Filter out duplicate group shares for users with unique targets
1715
			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
1716
				$row['share_type'] = self::SHARE_TYPE_GROUP;
1717
				$row['unique_name'] = true; // remember that we use a unique name for this user
1718
				$row['share_with'] = $items[$row['parent']]['share_with'];
1719
				// if the group share was unshared from the user we keep the permission, otherwise
1720
				// we take the permission from the parent because this is always the up-to-date
1721
				// permission for the group share
1722
				if ($row['permissions'] > 0) {
1723
					$row['permissions'] = $items[$row['parent']]['permissions'];
1724
				}
1725
				// Remove the parent group share
1726
				unset($items[$row['parent']]);
1727
				if ($row['permissions'] == 0) {
1728
					continue;
1729
				}
1730
			} elseif (!isset($uidOwner)) {
1731
				// Check if the same target already exists
1732
				if (isset($targets[$row['id']])) {
1733
					// Check if the same owner shared with the user twice
1734
					// through a group and user share - this is allowed
1735
					$id = $targets[$row['id']];
1736
					if (isset($items[$id]) && $row['uid_owner'] == $items[$id]['uid_owner']) {
1737
						// Switch to group share type to ensure resharing conditions aren't bypassed
1738
						if ($items[$id]['share_type'] != self::SHARE_TYPE_GROUP) {
1739
							$items[$id]['share_type'] = self::SHARE_TYPE_GROUP;
1740
							$items[$id]['share_with'] = $row['share_with'];
1741
						}
1742
						// Switch ids if sharing permission is granted on only
1743
						// one share to ensure correct parent is used if resharing
1744
						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
1745
							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
1746
							$items[$row['id']] = $items[$id];
1747
							$switchedItems[$id] = $row['id'];
1748
							unset($items[$id]);
1749
							$id = $row['id'];
1750
						}
1751
						$items[$id]['permissions'] |= (int)$row['permissions'];
1752
					}
1753
					continue;
1754
				} elseif (!empty($row['parent'])) {
1755
					$targets[$row['parent']] = $row['id'];
1756
				}
1757
			}
1758
			// Remove root from file source paths if retrieving own shared items
1759
			if (isset($uidOwner, $row['path'])) {
1760
				if (isset($row['parent'])) {
1761
					$query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?');
1762
					$parentResult = $query->execute([$row['parent']]);
1763
					if ($result === false) {
1764
						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
1765
							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
1766
							\OCP\Util::ERROR);
1767
					} else {
1768
						$parentRow = $parentResult->fetchRow();
1769
						$tmpPath = $parentRow['file_target'];
1770
						// find the right position where the row path continues from the target path
1771
						$pos = \strrpos($row['path'], $parentRow['file_target']);
1772
						$subPath = \substr($row['path'], $pos);
1773
						$splitPath = \explode('/', $subPath);
1774
						foreach (\array_slice($splitPath, 2) as $pathPart) {
1775
							$tmpPath = $tmpPath . '/' . $pathPart;
1776
						}
1777
						$row['path'] = $tmpPath;
1778
					}
1779
				} else {
1780
					if (!isset($mounts[$row['storage']])) {
1781
						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
1782
						if (\is_array($mountPoints) && !empty($mountPoints)) {
1783
							$mounts[$row['storage']] = \current($mountPoints);
1784
						}
1785
					}
1786
					if (!empty($mounts[$row['storage']])) {
1787
						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
1788
						$relPath = \substr($path, $root); // path relative to data/user
1789
						$row['path'] = \rtrim($relPath, '/');
1790
					}
1791
				}
1792
			}
1793
1794
			if ($checkExpireDate) {
1795
				if (self::expireItem($row)) {
1796
					continue;
1797
				}
1798
			}
1799
			// Check if resharing is allowed, if not remove share permission
1800
			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
1801
				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
1802
			}
1803
			// Add display names to result
1804
			$row['share_with_displayname'] = $row['share_with'];
1805
			if (isset($row['share_with']) && $row['share_with'] != '' &&
1806
				$row['share_type'] === self::SHARE_TYPE_USER) {
1807
				$row['share_with_displayname'] = \OCP\User::getDisplayName($row['share_with']);
1808
			} elseif (isset($row['share_with']) && $row['share_with'] != '' &&
1809
				$row['share_type'] === self::SHARE_TYPE_REMOTE) {
1810
				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
1811
				foreach ($addressBookEntries as $entry) {
1812
					foreach ($entry['CLOUD'] as $cloudID) {
1813
						if ($cloudID === $row['share_with']) {
1814
							$row['share_with_displayname'] = $entry['FN'];
1815
						}
1816
					}
1817
				}
1818
			}
1819
			if (isset($row['uid_owner']) && $row['uid_owner'] != '') {
1820
				$row['displayname_owner'] = \OCP\User::getDisplayName($row['uid_owner']);
1821
			}
1822
1823
			if ($row['permissions'] > 0) {
1824
				$items[$row['id']] = $row;
1825
			}
1826
		}
1827
1828
		// group items if we are looking for items shared with the current user
1829
		if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
1830
			$items = self::groupItems($items, $itemType);
1831
		}
1832
1833
		if (!empty($items)) {
1834
			$collectionItems = [];
1835
			foreach ($items as &$row) {
1836
				// Return only the item instead of a 2-dimensional array
1837
				if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType)) {
1838
					if ($format == self::FORMAT_NONE) {
1839
						return $row;
1840
					} else {
1841
						break;
1842
					}
1843
				}
1844
				// Check if this is a collection of the requested item type
1845
				if ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
1846
					if (($collectionBackend = self::getBackend($row['item_type']))
1847
						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
1848
						// Collections can be inside collections, check if the item is a collection
1849
						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
1850
							$collectionItems[] = $row;
1851
						} else {
1852
							$collection = [];
1853
							$collection['item_type'] = $row['item_type'];
1854
							$row['collection'] = $collection;
1855
							// Fetch all of the children sources
1856
							$children = $collectionBackend->getChildren($row[$column]);
0 ignored issues
show
Documentation introduced by
$row[$column] is of type array<string,?,{"item_type":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1857
							foreach ($children as $child) {
1858
								$childItem = $row;
1859
								$childItem['item_type'] = $itemType;
1860
								$childItem['item_source'] = $child['source'];
1861
								$childItem['item_target'] = $child['target'];
1862
								if (isset($item)) {
1863
									if ($childItem[$column] == $item) {
1864
										// Return only the item instead of a 2-dimensional array
1865
										if ($limit == 1) {
1866
											if ($format == self::FORMAT_NONE) {
1867
												return $childItem;
1868
											} else {
1869
												// Unset the items array and break out of both loops
1870
												$items = [];
1871
												$items[] = $childItem;
1872
												break 2;
1873
											}
1874
										} else {
1875
											$collectionItems[] = $childItem;
1876
										}
1877
									}
1878
								} else {
1879
									$collectionItems[] = $childItem;
1880
								}
1881
							}
1882
						}
1883
					}
1884
					// Remove collection item
1885
					$toRemove = $row['id'];
1886
					if (\array_key_exists($toRemove, $switchedItems)) {
1887
						$toRemove = $switchedItems[$toRemove];
1888
					}
1889
					unset($items[$toRemove]);
1890
				} elseif ($includeCollections && $collectionTypes && \in_array($row['item_type'], $collectionTypes)) {
1891
					// FIXME: Thats a dirty hack to improve file sharing performance,
1892
					// see github issue #10588 for more details
1893
					// Need to find a solution which works for all back-ends
1894
					$collectionBackend = self::getBackend($row['item_type']);
1895
					$sharedParents = $collectionBackend->getParents($row['item_source']);
1896
					foreach ($sharedParents as $parent) {
1897
						$collectionItems[] = $parent;
1898
					}
1899
				}
1900
			}
1901
			if (!empty($collectionItems)) {
1902
				$collectionItems = \array_unique($collectionItems, SORT_REGULAR);
1903
				$items = \array_merge($items, $collectionItems);
1904
			}
1905
1906
			// filter out invalid items, these can appear when subshare entries exist
1907
			// for a group in which the requested user isn't a member any more
1908
			$items = \array_filter($items, function ($item) {
1909
				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
1910
			});
1911
1912
			return self::formatResult($items, $column, $backend, $format, $parameters);
1913
		}
1914
1915
		return [];
1916
	}
1917
1918
	/**
1919
	 * group items with link to the same source
1920
	 *
1921
	 * @param array $items
1922
	 * @param string $itemType
1923
	 * @return array of grouped items
1924
	 */
1925
	protected static function groupItems($items, $itemType) {
1926 View Code Duplication
		if ($itemType === 'file' || $itemType === 'folder') {
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...
1927
			throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
1928
		}
1929
		$fileSharing = false;
1930
1931
		$result = [];
1932
1933
		foreach ($items as $item) {
1934
			$grouped = false;
1935
			foreach ($result as $key => $r) {
1936
				// for file/folder shares we need to compare file_source, otherwise we compare item_source
1937
				// only group shares if they already point to the same target, otherwise the file where shared
1938
				// before grouping of shares was added. In this case we don't group them toi avoid confusions
1939
				if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
1940
					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
1941
					// add the first item to the list of grouped shares
1942
					if (!isset($result[$key]['grouped'])) {
1943
						$result[$key]['grouped'][] = $result[$key];
1944
					}
1945
					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
1946
					$result[$key]['grouped'][] = $item;
1947
					$grouped = true;
1948
					break;
1949
				}
1950
			}
1951
1952
			if (!$grouped) {
1953
				$result[] = $item;
1954
			}
1955
		}
1956
1957
		return $result;
1958
	}
1959
1960
	/**
1961
	 * Put shared item into the database
1962
	 * @param string $itemType Item type
1963
	 * @param string $itemSource Item source
1964
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
1965
	 * @param string $shareWith User or group the item is being shared with
1966
	 * @param string $uidOwner User that is the owner of shared item
1967
	 * @param int $permissions CRUDS permissions
1968
	 * @param boolean|array $parentFolder Parent folder target (optional)
1969
	 * @param string $token (optional)
1970
	 * @param string $itemSourceName name of the source item (optional)
1971
	 * @param \DateTime $expirationDate (optional)
1972
	 * @throws \Exception
1973
	 * @return mixed id of the new share or false
1974
	 */
1975
	private static function put($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
1976
								$permissions, $parentFolder = null, $token = null, $itemSourceName = null, \DateTime $expirationDate = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $parentFolder is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1977 View Code Duplication
		if ($itemType === 'file' || $itemType === 'folder') {
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...
1978
			throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
1979
		}
1980
1981
		$queriesToExecute = [];
1982
		$suggestedItemTarget = null;
1983
		$groupFileTarget = $fileTarget = $suggestedFileTarget = $filePath = '';
1984
		$groupItemTarget = $itemTarget = $fileSource = $parent = 0;
1985
1986
		$result = self::checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate);
1987
		if (!empty($result)) {
1988
			$parent = $result['parent'];
1989
			$itemSource = $result['itemSource'];
1990
			$fileSource = $result['fileSource'];
1991
			$suggestedItemTarget = $result['suggestedItemTarget'];
1992
			$suggestedFileTarget = $result['suggestedFileTarget'];
1993
			$filePath = $result['filePath'];
1994
		}
1995
1996
		$isGroupShare = false;
1997
		if ($shareType == self::SHARE_TYPE_GROUP) {
1998
			$isGroupShare = true;
1999
			if (isset($shareWith['users'])) {
2000
				$users = $shareWith['users'];
2001
			} else {
2002
				$users = self::usersInGroup($shareWith['group']);
2003
			}
2004
			// remove current user from list
2005
			if (\in_array(\OCP\User::getUser(), $users)) {
2006
				unset($users[\array_search(\OCP\User::getUser(), $users)]);
2007
			}
2008
			$groupItemTarget = Helper::generateTarget($itemType, $itemSource,
2009
				$shareType, $shareWith['group'], $uidOwner, $suggestedItemTarget);
2010
			$groupFileTarget = Helper::generateTarget($itemType, $itemSource,
2011
				$shareType, $shareWith['group'], $uidOwner, $filePath);
2012
2013
			// add group share to table and remember the id as parent
2014
			$queriesToExecute['groupShare'] = [
2015
				'itemType'			=> $itemType,
2016
				'itemSource'		=> $itemSource,
2017
				'itemTarget'		=> $groupItemTarget,
2018
				'shareType'			=> $shareType,
2019
				'shareWith'			=> $shareWith['group'],
2020
				'uidOwner'			=> $uidOwner,
2021
				'permissions'		=> $permissions,
2022
				'shareTime'			=> \time(),
2023
				'fileSource'		=> $fileSource,
2024
				'fileTarget'		=> $groupFileTarget,
2025
				'token'				=> $token,
2026
				'parent'			=> $parent,
2027
				'expiration'		=> $expirationDate,
2028
			];
2029
		} else {
2030
			$users = [$shareWith];
2031
			$itemTarget = Helper::generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
2032
				$suggestedItemTarget);
2033
		}
2034
2035
		$run = true;
2036
		$error = '';
2037
		$preHookData = [
2038
			'itemType' => $itemType,
2039
			'itemSource' => $itemSource,
2040
			'shareType' => $shareType,
2041
			'uidOwner' => $uidOwner,
2042
			'permissions' => $permissions,
2043
			'fileSource' => $fileSource,
2044
			'expiration' => $expirationDate,
2045
			'token' => $token,
2046
			'run' => &$run,
2047
			'error' => &$error
2048
		];
2049
2050
		$preHookData['itemTarget'] = ($isGroupShare) ? $groupItemTarget : $itemTarget;
2051
		$preHookData['shareWith'] = ($isGroupShare) ? $shareWith['group'] : $shareWith;
2052
2053
		\OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
2054
2055
		if ($run === false) {
2056
			throw new \Exception($error);
2057
		}
2058
2059
		foreach ($users as $user) {
0 ignored issues
show
Bug introduced by
The expression $users of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2060
			$sourceId = $itemSource;
2061
			$sourceExists = self::getItemSharedWithBySource($itemType, $sourceId, self::FORMAT_NONE, null, true, $user);
2062
2063
			$userShareType = ($isGroupShare) ? self::$shareTypeGroupUserUnique : $shareType;
2064
2065
			if ($sourceExists && $sourceExists['item_source'] === $itemSource) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2066
				$fileTarget = $sourceExists['file_target'];
2067
				$itemTarget = $sourceExists['item_target'];
2068
2069
				// for group shares we don't need a additional entry if the target is the same
2070
				if ($isGroupShare && $groupItemTarget === $itemTarget) {
2071
					continue;
2072
				}
2073
			} elseif (!$sourceExists && !$isGroupShare) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2074
				$itemTarget = Helper::generateTarget($itemType, $itemSource, $userShareType, $user,
2075
					$uidOwner, $suggestedItemTarget, $parent);
2076
			} else {
2077
2078
				// group share which doesn't exists until now, check if we need a unique target for this user
2079
2080
				$itemTarget = Helper::generateTarget($itemType, $itemSource, self::SHARE_TYPE_USER, $user,
2081
					$uidOwner, $suggestedItemTarget, $parent);
2082
2083
				// do we also need a file target
2084
				if (($itemTarget === $groupItemTarget)) {
2085
					continue;
2086
				}
2087
			}
2088
2089
			$queriesToExecute[] = [
2090
				'itemType'			=> $itemType,
2091
				'itemSource'		=> $itemSource,
2092
				'itemTarget'		=> $itemTarget,
2093
				'shareType'			=> $userShareType,
2094
				'shareWith'			=> $user,
2095
				'uidOwner'			=> $uidOwner,
2096
				'permissions'		=> $permissions,
2097
				'shareTime'			=> \time(),
2098
				'fileSource'		=> $fileSource,
2099
				'fileTarget'		=> $fileTarget,
2100
				'token'				=> $token,
2101
				'parent'			=> $parent,
2102
				'expiration'		=> $expirationDate,
2103
			];
2104
		}
2105
2106
		$id = false;
2107
		if ($isGroupShare) {
2108
			$id = self::insertShare($queriesToExecute['groupShare']);
2109
			// Save this id, any extra rows for this group share will need to reference it
2110
			$parent = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share');
2111
			unset($queriesToExecute['groupShare']);
2112
		}
2113
2114
		foreach ($queriesToExecute as $shareQuery) {
2115
			$shareQuery['parent'] = $parent;
2116
			$id = self::insertShare($shareQuery);
2117
		}
2118
2119
		$postHookData = [
2120
			'itemType' => $itemType,
2121
			'itemSource' => $itemSource,
2122
			'parent' => $parent,
2123
			'shareType' => $shareType,
2124
			'uidOwner' => $uidOwner,
2125
			'permissions' => $permissions,
2126
			'fileSource' => $fileSource,
2127
			'id' => $parent,
2128
			'token' => $token,
2129
			'expirationDate' => $expirationDate,
2130
		];
2131
2132
		$postHookData['shareWith'] = ($isGroupShare) ? $shareWith['group'] : $shareWith;
2133
		$postHookData['itemTarget'] = ($isGroupShare) ? $groupItemTarget : $itemTarget;
2134
		$postHookData['fileTarget'] = ($isGroupShare) ? $groupFileTarget : $fileTarget;
2135
2136
		\OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
2137
2138
		return $id ? $id : false;
2139
	}
2140
2141
	/**
2142
	 * @param string $itemType
2143
	 * @param string $itemSource
2144
	 * @param int $shareType
2145
	 * @param string $shareWith
2146
	 * @param string $uidOwner
2147
	 * @param int $permissions
2148
	 * @param string|null $itemSourceName
2149
	 * @param null|\DateTime $expirationDate
2150
	 */
2151
	private static function checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate) {
2152 View Code Duplication
		if ($itemType === 'file' || $itemType === 'folder') {
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...
2153
			throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
2154
		}
2155
		$backend = self::getBackend($itemType);
2156
2157
		$l = \OC::$server->getL10N('lib');
2158
		$result = [];
2159
2160
		$column = 'item_source';
2161
2162
		$checkReshare = self::getItemSharedWithBySource($itemType, $itemSource, self::FORMAT_NONE, null, true);
2163
		if ($checkReshare) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $checkReshare of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2164
			// Check if attempting to share back to owner
2165
			if ($checkReshare['uid_owner'] == $shareWith && $shareType == self::SHARE_TYPE_USER) {
2166
				$message = 'Sharing %s failed, because the user %s is the original sharer';
2167
				$message_t = $l->t('Sharing failed, because the user %s is the original sharer', [$shareWith]);
2168
2169
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
2170
				throw new \Exception($message_t);
2171
			}
2172
		}
2173
2174
		if ($checkReshare && $checkReshare['uid_owner'] !== \OC_User::getUser()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $checkReshare of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2175
			// Check if share permissions is granted
2176
			if (self::isResharingAllowed() && (int)$checkReshare['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
2177
				if (~(int)$checkReshare['permissions'] & $permissions) {
2178
					$message = 'Sharing %s failed, because the permissions exceed permissions granted to %s';
2179
					$message_t = $l->t('Sharing %s failed, because the permissions exceed permissions granted to %s', [$itemSourceName, $uidOwner]);
2180
2181
					\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName, $uidOwner), \OCP\Util::DEBUG);
2182
					throw new \Exception($message_t);
2183
				} else {
2184
					// TODO Don't check if inside folder
2185
					$result['parent'] = $checkReshare['id'];
2186
2187
					$result['expirationDate'] = $expirationDate;
2188
					// $checkReshare['expiration'] could be null and then is always less than any value
2189
					if (isset($checkReshare['expiration']) && $checkReshare['expiration'] < $expirationDate) {
2190
						$result['expirationDate'] = $checkReshare['expiration'];
2191
					}
2192
2193
					// only suggest the same name as new target if it is a reshare of the
2194
					// same file/folder and not the reshare of a child
2195
					if ($checkReshare[$column] === $itemSource) {
2196
						$result['filePath'] = $checkReshare['file_target'];
2197
						$result['itemSource'] = $checkReshare['item_source'];
2198
						$result['fileSource'] = $checkReshare['file_source'];
2199
						$result['suggestedItemTarget'] = $checkReshare['item_target'];
2200
						$result['suggestedFileTarget'] = $checkReshare['file_target'];
2201
					} else {
2202
						$result['filePath'] = null;
2203
						$result['suggestedItemTarget'] = null;
2204
						$result['suggestedFileTarget'] = null;
2205
						$result['itemSource'] = $itemSource;
2206
						$result['fileSource'] = null;
2207
					}
2208
				}
2209 View Code Duplication
			} else {
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...
2210
				$message = 'Sharing %s failed, because resharing is not allowed';
2211
				$message_t = $l->t('Sharing %s failed, because resharing is not allowed', [$itemSourceName]);
2212
2213
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
2214
				throw new \Exception($message_t);
2215
			}
2216
		} else {
2217
			$result['parent'] = null;
2218
			$result['suggestedItemTarget'] = null;
2219
			$result['suggestedFileTarget'] = null;
2220
			$result['itemSource'] = $itemSource;
2221
			$result['expirationDate'] = $expirationDate;
2222 View Code Duplication
			if (!$backend->isValidSource($itemSource, $uidOwner)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $backend->isValidSource($itemSource, $uidOwner) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
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...
2223
				$message = 'Sharing %s failed, because the sharing backend for '
2224
					.'%s could not find its source';
2225
				$message_t = $l->t('Sharing %s failed, because the sharing backend for %s could not find its source', [$itemSource, $itemType]);
2226
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSource, $itemType), \OCP\Util::DEBUG);
2227
				throw new \Exception($message_t);
2228
			}
2229
			$result['filePath'] = null;
2230
			$result['fileSource'] = null;
2231
		}
2232
2233
		return $result;
2234
	}
2235
2236
	/**
2237
	 *
2238
	 * @param array $shareData
2239
	 * @return mixed false in case of a failure or the id of the new share
2240
	 */
2241
	private static function insertShare(array $shareData) {
2242
		$query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` ('
2243
			.' `item_type`, `item_source`, `item_target`, `share_type`,'
2244
			.' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
2245
			.' `file_target`, `token`, `parent`, `expiration`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)');
2246
		$query->bindValue(1, $shareData['itemType']);
2247
		$query->bindValue(2, $shareData['itemSource']);
2248
		$query->bindValue(3, $shareData['itemTarget']);
2249
		$query->bindValue(4, $shareData['shareType']);
2250
		$query->bindValue(5, $shareData['shareWith']);
2251
		$query->bindValue(6, $shareData['uidOwner']);
2252
		$query->bindValue(7, $shareData['permissions']);
2253
		$query->bindValue(8, $shareData['shareTime']);
2254
		$query->bindValue(9, $shareData['fileSource']);
2255
		$query->bindValue(10, $shareData['fileTarget']);
2256
		$query->bindValue(11, $shareData['token']);
2257
		$query->bindValue(12, $shareData['parent']);
2258
		$query->bindValue(13, $shareData['expiration'], 'datetime');
2259
		$result = $query->execute();
2260
2261
		$id = false;
2262
		if ($result) {
2263
			$id =  \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share');
2264
		}
2265
2266
		return $id;
2267
	}
2268
2269
	/**
2270
	 * Delete all shares with type SHARE_TYPE_LINK
2271
	 */
2272
	public static function removeAllLinkShares() {
2273
		// Delete any link shares
2274
		$query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*share` WHERE `share_type` = ?');
2275
		$result = $query->execute([self::SHARE_TYPE_LINK]);
2276
		while ($item = $result->fetchRow()) {
2277
			Helper::delete($item['id']);
2278
		}
2279
	}
2280
2281
	/**
2282
	 * In case a password protected link is not yet authenticated this function will return false
2283
	 *
2284
	 * @param array $linkItem
2285
	 * @return boolean
2286
	 */
2287
	public static function checkPasswordProtectedShare(array $linkItem) {
2288
		if (!isset($linkItem['share_with'])) {
2289
			return true;
2290
		}
2291
		if (!isset($linkItem['share_type'])) {
2292
			return true;
2293
		}
2294
		if (!isset($linkItem['id'])) {
2295
			return true;
2296
		}
2297
2298
		if ($linkItem['share_type'] != \OCP\Share::SHARE_TYPE_LINK) {
2299
			return true;
2300
		}
2301
2302 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...
2303
			&& \OC::$server->getSession()->get('public_link_authenticated') === (string)$linkItem['id']) {
2304
			return true;
2305
		}
2306
2307
		return false;
2308
	}
2309
2310
	/**
2311
	 * construct select statement
2312
	 * @param int $format
2313
	 * @param string $uidOwner
2314
	 * @return string select statement
2315
	 */
2316
	private static function createSelectStatement($format, $uidOwner = null) {
2317
		$select = '*';
2318
		if ($format == self::FORMAT_STATUSES) {
2319
			$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
2320
		} else {
2321
			if (isset($uidOwner)) {
2322
				$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
2323
					. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
2324
			}
2325
		}
2326
		return $select;
2327
	}
2328
2329
	/**
2330
	 * transform db results
2331
	 * @param array $row result
2332
	 */
2333
	private static function transformDBResults(&$row) {
2334
		if (isset($row['id'])) {
2335
			$row['id'] = (int) $row['id'];
2336
		}
2337
		if (isset($row['share_type'])) {
2338
			$row['share_type'] = (int) $row['share_type'];
2339
		}
2340
		if (isset($row['parent'])) {
2341
			$row['parent'] = (int) $row['parent'];
2342
		}
2343
		if (isset($row['file_parent'])) {
2344
			$row['file_parent'] = (int) $row['file_parent'];
2345
		}
2346
		if (isset($row['file_source'])) {
2347
			$row['file_source'] = (int) $row['file_source'];
2348
		}
2349
		if (isset($row['permissions'])) {
2350
			$row['permissions'] = (int) $row['permissions'];
2351
		}
2352
		if (isset($row['storage'])) {
2353
			$row['storage'] = (int) $row['storage'];
2354
		}
2355
		if (isset($row['stime'])) {
2356
			$row['stime'] = (int) $row['stime'];
2357
		}
2358
		if (isset($row['expiration']) && $row['share_type'] !== self::SHARE_TYPE_LINK) {
2359
			// discard expiration date for non-link shares, which might have been
2360
			// set by ancient bugs
2361
			$row['expiration'] = null;
2362
		}
2363
	}
2364
2365
	/**
2366
	 * format result
2367
	 * @param array $items result
2368
	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
2369
	 * @param \OCP\Share_Backend $backend sharing backend
2370
	 * @param int $format
2371
	 * @param array $parameters additional format parameters
2372
	 * @return array format result
2373
	 */
2374
	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE, $parameters = null) {
2375
		if ($format === self::FORMAT_NONE) {
2376
			return $items;
2377
		} elseif ($format === self::FORMAT_STATUSES) {
2378
			$statuses = [];
2379
			foreach ($items as $item) {
2380
				if ($item['share_type'] === self::SHARE_TYPE_LINK) {
2381
					if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
2382
						continue;
2383
					}
2384
					$statuses[$item[$column]]['link'] = true;
2385
				} elseif (!isset($statuses[$item[$column]])) {
2386
					$statuses[$item[$column]]['link'] = false;
2387
				}
2388
				if (!empty($item['file_target'])) {
2389
					$statuses[$item[$column]]['path'] = $item['path'];
2390
				}
2391
			}
2392
			return $statuses;
2393
		} else {
2394
			return $backend->formatItems($items, $format, $parameters);
2395
		}
2396
	}
2397
2398
	/**
2399
	 * remove protocol from URL
2400
	 *
2401
	 * @param string $url
2402
	 * @return string
2403
	 */
2404 View Code Duplication
	public static function removeProtocolFromUrl($url) {
2405
		if (\strpos($url, 'https://') === 0) {
2406
			return \substr($url, \strlen('https://'));
2407
		} elseif (\strpos($url, 'http://') === 0) {
2408
			return \substr($url, \strlen('http://'));
2409
		}
2410
2411
		return $url;
2412
	}
2413
2414
	/**
2415
	 * try http post first with https and then with http as a fallback
2416
	 *
2417
	 * @param string $remoteDomain
2418
	 * @param string $urlSuffix
2419
	 * @param array $fields post parameters
2420
	 * @return array
2421
	 */
2422
	private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) {
2423
		$allowHttpFallback = \OC::$server->getConfig()->getSystemValue('sharing.federation.allowHttpFallback', false) === true;
2424
		// Always try https first
2425
		$protocol = 'https://';
2426
		$discoveryManager = new DiscoveryManager(
2427
			\OC::$server->getMemCacheFactory(),
2428
			\OC::$server->getHTTPClientService()
2429
		);
2430
2431
		$endpoint = $discoveryManager->getShareEndpoint($protocol . $remoteDomain);
2432
		// Try HTTPS
2433
		$result = \OC::$server->getHTTPHelper()->post(
2434
			$protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT,
2435
			$fields);
2436
2437
		if ($result['success'] === true) {
2438
			// Return if https worked
2439
			return $result;
2440
		} elseif ($result['success'] === false && $allowHttpFallback) {
2441
			// If https failed and we can try http - try that
2442
			$protocol = 'http://';
2443
			$result = \OC::$server->getHTTPHelper()->post(
2444
			$protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT,
2445
			$fields);
2446
			return $result;
2447
		} else {
2448
			// Else we just return the failure
2449
			return $result;
2450
		}
2451
	}
2452
2453
	/**
2454
	 * send server-to-server share to remote server
2455
	 *
2456
	 * @param string $token
2457
	 * @param string $shareWith
2458
	 * @param string $name
2459
	 * @param int $remote_id
2460
	 * @param string $owner
2461
	 * @return bool
2462
	 */
2463
	private static function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner) {
2464
		list($user, $remote) = Helper::splitUserRemote($shareWith);
2465
2466
		if ($user && $remote) {
2467
			$url = $remote;
2468
2469
			$local = \OC::$server->getURLGenerator()->getAbsoluteURL('/');
2470
2471
			$fields = [
2472
				'shareWith' => $user,
2473
				'token' => $token,
2474
				'name' => $name,
2475
				'remoteId' => $remote_id,
2476
				'owner' => $owner,
2477
				'remote' => $local,
2478
			];
2479
2480
			$url = self::removeProtocolFromUrl($url);
2481
			$result = self::tryHttpPostToShareEndpoint($url, '', $fields);
2482
			$status = \json_decode($result['result'], true);
2483
2484
			if ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200)) {
2485
				\OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $remote]);
2486
				return true;
2487
			}
2488
		}
2489
2490
		return false;
2491
	}
2492
2493
	/**
2494
	 * send server-to-server unshare to remote server
2495
	 *
2496
	 * @param string $remote url
2497
	 * @param int $id share id
2498
	 * @param string $token
2499
	 * @return bool
2500
	 */
2501
	private static function sendRemoteUnshare($remote, $id, $token) {
2502
		$url = \rtrim($remote, '/');
2503
		$fields = ['token' => $token, 'format' => 'json'];
2504
		$url = self::removeProtocolFromUrl($url);
2505
		$result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields);
2506
		$status = \json_decode($result['result'], true);
2507
2508
		return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
2509
	}
2510
2511
	/**
2512
	 * check if user can only share with group members
2513
	 * @return bool
2514
	 */
2515
	public static function shareWithGroupMembersOnly() {
2516
		$value = \OC::$server->getAppConfig()->getValue('core', 'shareapi_only_share_with_group_members', 'no');
2517
		return ($value === 'yes') ? true : false;
2518
	}
2519
2520
	/**
2521
	 * check if user can only share with groups he's member of
2522
	 * @return bool
2523
	 */
2524
	public static function shareWithMembershipGroupOnly() {
2525
		$value = \OC::$server->getAppConfig()->getValue('core', 'shareapi_only_share_with_membership_groups', 'no');
2526
		return ($value === 'yes') ? true : false;
2527
	}
2528
2529
	/**
2530
	 * @return bool
2531
	 */
2532
	public static function isDefaultExpireDateEnabled() {
2533
		$defaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no');
2534
		return ($defaultExpireDateEnabled === "yes") ? true : false;
2535
	}
2536
2537
	/**
2538
	 * @return bool
2539
	 */
2540
	public static function enforceDefaultExpireDate() {
2541
		$enforceDefaultExpireDate = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_expire_date', 'no');
2542
		return ($enforceDefaultExpireDate === "yes") ? true : false;
2543
	}
2544
2545
	/**
2546
	 * @return int
2547
	 */
2548
	public static function getExpireInterval() {
2549
		return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
2550
	}
2551
2552
	/**
2553
	 * Checks whether the given path is reachable for the given owner
2554
	 *
2555
	 * @param string $path path relative to files
2556
	 * @param string $ownerStorageId storage id of the owner
2557
	 *
2558
	 * @return boolean true if file is reachable, false otherwise
2559
	 */
2560
	private static function isFileReachable($path, $ownerStorageId) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2561
		// if outside the home storage, file is always considered reachable
2562
		if (!(\substr($ownerStorageId, 0, 6) === 'home::' ||
2563
			\substr($ownerStorageId, 0, 13) === 'object::user:'
2564
		)) {
2565
			return true;
2566
		}
2567
2568
		// if inside the home storage, the file has to be under "/files/"
2569
		$path = \ltrim($path, '/');
2570
		if (\substr($path, 0, 6) === 'files/') {
2571
			return true;
2572
		}
2573
2574
		return false;
2575
	}
2576
2577
	/**
2578
	 * @param IConfig $config
2579
	 * @return bool
2580
	 */
2581
	public static function enforcePassword(IConfig $config) {
2582
		$enforcePassword = $config->getAppValue('core', 'shareapi_enforce_links_password', 'no');
2583
		return ($enforcePassword === "yes") ? true : false;
2584
	}
2585
2586
	/**
2587
	 * Get all share entries, including non-unique group items
2588
	 *
2589
	 * @param string $owner
2590
	 * @return array
2591
	 */
2592
	public static function getAllSharesForOwner($owner) {
2593
		$query = 'SELECT * FROM `*PREFIX*share` WHERE `uid_owner` = ?';
2594
		$result = \OC::$server->getDatabaseConnection()->executeQuery($query, [$owner]);
2595
		return $result->fetchAll();
2596
	}
2597
2598
	/**
2599
	 * Get all share entries, including non-unique group items for a file
2600
	 *
2601
	 * @param int $id
2602
	 * @return array
2603
	 */
2604
	public static function getAllSharesForFileId($id) {
2605
		$query = 'SELECT * FROM `*PREFIX*share` WHERE `file_source` = ?';
2606
		$result = \OC::$server->getDatabaseConnection()->executeQuery($query, [$id]);
2607
		return $result->fetchAll();
2608
	}
2609
2610
	/**
2611
	 * @param string $password
2612
	 * @throws \Exception
2613
	 */
2614
	private static function verifyPassword($password) {
2615
		$accepted = true;
2616
		$message = '';
2617
		\OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
2618
			'password' => $password,
2619
			'accepted' => &$accepted,
2620
			'message' => &$message
2621
		]);
2622
2623
		if (!$accepted) {
2624
			throw new \Exception($message);
2625
		}
2626
2627
		\OC::$server->getEventDispatcher()->dispatch(
2628
			'OCP\Share::validatePassword',
2629
			new GenericEvent(null, ['password' => $password])
2630
		);
2631
	}
2632
2633
	/**
2634
	 * @param $user
2635
	 * @return Group[]
2636
	 */
2637
	private static function getGroupsForUser($user) {
2638
		$groups = \OC::$server->getGroupManager()->getUserIdGroups($user, 'sharing');
2639
		return \array_values(\array_map(function (Group $g) {
2640
			return $g->getGID();
2641
		}, $groups));
2642
	}
2643
2644
	/**
2645
	 * @param $group
2646
	 * @return mixed
2647
	 */
2648
	private static function usersInGroup($group) {
2649
		$g = \OC::$server->getGroupManager()->get($group);
2650
		if ($g === null) {
2651
			return [];
2652
		}
2653
		return \array_values(\array_map(function (IUser $u) {
2654
			return $u->getUID();
2655
		}, $g->getUsers()));
2656
	}
2657
}
2658