ShareAPIController   F
last analyzed

Complexity

Total Complexity 344

Size/Duplication

Total Lines 1892
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 854
dl 0
loc 1892
rs 1.746
c 0
b 0
f 0
wmc 344

34 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 31 1
A getShare() 0 24 5
F formatShare() 0 165 37
B getInheritedShares() 0 69 11
B getSharesInDir() 0 46 10
A getSharedWithMe() 0 30 5
A hasPermission() 0 2 1
A getShares() 0 32 4
A getDisplayNameFromAddressBook() 0 16 5
A deleteShare() 0 32 6
C getFormattedShares() 0 68 14
A cleanup() 0 3 2
A getDeckShareHelper() 0 6 2
A parseDate() 0 10 2
A setShareAttributes() 0 20 5
A getSharesFromNode() 0 39 5
B pendingShares() 0 45 9
C shareProviderResharingRights() 0 44 17
A hasResharingRights() 0 18 6
A getSciencemeshShareHelper() 0 6 2
C getShareById() 0 61 12
A mergeFormattedShares() 0 4 3
A confirmSharingRights() 0 3 2
A canEditShare() 0 19 4
A lock() 0 3 1
B checkInheritedAttributes() 0 28 9
A getAllShares() 0 34 3
C canDeleteShareFromSelf() 0 50 17
A getRoomShareHelper() 0 6 2
A canDeleteShare() 0 23 6
D canAccessShare() 0 65 20
A acceptShare() 0 21 6
F updateShare() 0 182 51
F createShare() 0 260 59

How to fix   Complexity   

Complex Class

Complex classes like ShareAPIController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ShareAPIController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Bjoern Schiessle <[email protected]>
9
 * @author castillo92 <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Daniel Calviño Sánchez <[email protected]>
12
 * @author Daniel Kesselberg <[email protected]>
13
 * @author Gary Kim <[email protected]>
14
 * @author Georg Ehrke <[email protected]>
15
 * @author Joas Schilling <[email protected]>
16
 * @author John Molakvoæ <[email protected]>
17
 * @author Julius Härtl <[email protected]>
18
 * @author Lukas Reschke <[email protected]>
19
 * @author Maxence Lange <[email protected]>
20
 * @author Maxence Lange <[email protected]>
21
 * @author Michael Jobst <[email protected]>
22
 * @author Morris Jobke <[email protected]>
23
 * @author Richard Steinmetz <[email protected]>
24
 * @author Robin Appelman <[email protected]>
25
 * @author Roeland Jago Douma <[email protected]>
26
 * @author Valdnet <[email protected]>
27
 * @author Vincent Petry <[email protected]>
28
 * @author waleczny <[email protected]>
29
 *
30
 * @license AGPL-3.0
31
 *
32
 * This code is free software: you can redistribute it and/or modify
33
 * it under the terms of the GNU Affero General Public License, version 3,
34
 * as published by the Free Software Foundation.
35
 *
36
 * This program is distributed in the hope that it will be useful,
37
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
38
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39
 * GNU Affero General Public License for more details.
40
 *
41
 * You should have received a copy of the GNU Affero General Public License, version 3,
42
 * along with this program. If not, see <http://www.gnu.org/licenses/>
43
 *
44
 */
45
namespace OCA\Files_Sharing\Controller;
46
47
use OC\Files\FileInfo;
48
use OC\Files\Storage\Wrapper\Wrapper;
49
use OCA\Files_Sharing\Exceptions\SharingRightsException;
50
use OCA\Files_Sharing\External\Storage;
51
use OCA\Files_Sharing\SharedStorage;
52
use OCA\Files\Helper;
53
use OCP\App\IAppManager;
54
use OCP\AppFramework\Http\DataResponse;
55
use OCP\AppFramework\OCS\OCSBadRequestException;
56
use OCP\AppFramework\OCS\OCSException;
57
use OCP\AppFramework\OCS\OCSForbiddenException;
58
use OCP\AppFramework\OCS\OCSNotFoundException;
59
use OCP\AppFramework\OCSController;
60
use OCP\AppFramework\QueryException;
61
use OCP\Constants;
62
use OCP\Files\InvalidPathException;
63
use OCP\Files\IRootFolder;
64
use OCP\Files\Folder;
65
use OCP\Files\Node;
66
use OCP\Files\NotFoundException;
67
use OCP\IConfig;
68
use OCP\IGroupManager;
69
use OCP\IL10N;
70
use OCP\IPreview;
71
use OCP\IRequest;
72
use OCP\IServerContainer;
73
use OCP\IURLGenerator;
74
use OCP\IUserManager;
75
use OCP\Lock\ILockingProvider;
76
use OCP\Lock\LockedException;
77
use OCP\Share;
78
use OCP\Share\Exceptions\GenericShareException;
79
use OCP\Share\Exceptions\ShareNotFound;
80
use OCP\Share\IManager;
81
use OCP\Share\IShare;
82
use OCP\UserStatus\IManager as IUserStatusManager;
83
84
/**
85
 * Class Share20OCS
86
 *
87
 * @package OCA\Files_Sharing\API
88
 */
89
class ShareAPIController extends OCSController {
90
91
	/** @var IManager */
92
	private $shareManager;
93
	/** @var IGroupManager */
94
	private $groupManager;
95
	/** @var IUserManager */
96
	private $userManager;
97
	/** @var IRootFolder */
98
	private $rootFolder;
99
	/** @var IURLGenerator */
100
	private $urlGenerator;
101
	/** @var string */
102
	private $currentUser;
103
	/** @var IL10N */
104
	private $l;
105
	/** @var \OCP\Files\Node */
106
	private $lockedNode;
107
	/** @var IConfig */
108
	private $config;
109
	/** @var IAppManager */
110
	private $appManager;
111
	/** @var IServerContainer */
112
	private $serverContainer;
113
	/** @var IUserStatusManager */
114
	private $userStatusManager;
115
	/** @var IPreview */
116
	private $previewManager;
117
118
	/**
119
	 * Share20OCS constructor.
120
	 *
121
	 * @param string $appName
122
	 * @param IRequest $request
123
	 * @param IManager $shareManager
124
	 * @param IGroupManager $groupManager
125
	 * @param IUserManager $userManager
126
	 * @param IRootFolder $rootFolder
127
	 * @param IURLGenerator $urlGenerator
128
	 * @param string $userId
129
	 * @param IL10N $l10n
130
	 * @param IConfig $config
131
	 * @param IAppManager $appManager
132
	 * @param IServerContainer $serverContainer
133
	 * @param IUserStatusManager $userStatusManager
134
	 */
135
	public function __construct(
136
		string $appName,
137
		IRequest $request,
138
		IManager $shareManager,
139
		IGroupManager $groupManager,
140
		IUserManager $userManager,
141
		IRootFolder $rootFolder,
142
		IURLGenerator $urlGenerator,
143
		string $userId = null,
144
		IL10N $l10n,
145
		IConfig $config,
146
		IAppManager $appManager,
147
		IServerContainer $serverContainer,
148
		IUserStatusManager $userStatusManager,
149
		IPreview $previewManager
150
	) {
151
		parent::__construct($appName, $request);
152
153
		$this->shareManager = $shareManager;
154
		$this->userManager = $userManager;
155
		$this->groupManager = $groupManager;
156
		$this->request = $request;
157
		$this->rootFolder = $rootFolder;
158
		$this->urlGenerator = $urlGenerator;
159
		$this->currentUser = $userId;
160
		$this->l = $l10n;
161
		$this->config = $config;
162
		$this->appManager = $appManager;
163
		$this->serverContainer = $serverContainer;
164
		$this->userStatusManager = $userStatusManager;
165
		$this->previewManager = $previewManager;
166
	}
167
168
	/**
169
	 * Convert an IShare to an array for OCS output
170
	 *
171
	 * @param \OCP\Share\IShare $share
172
	 * @param Node|null $recipientNode
173
	 * @return array
174
	 * @throws NotFoundException In case the node can't be resolved.
175
	 *
176
	 * @suppress PhanUndeclaredClassMethod
177
	 */
178
	protected function formatShare(IShare $share, Node $recipientNode = null): array {
179
		$sharedBy = $this->userManager->get($share->getSharedBy());
180
		$shareOwner = $this->userManager->get($share->getShareOwner());
181
182
		$result = [
183
			'id' => $share->getId(),
184
			'share_type' => $share->getShareType(),
185
			'uid_owner' => $share->getSharedBy(),
186
			'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
187
			// recipient permissions
188
			'permissions' => $share->getPermissions(),
189
			// current user permissions on this share
190
			'can_edit' => $this->canEditShare($share),
191
			'can_delete' => $this->canDeleteShare($share),
192
			'stime' => $share->getShareTime()->getTimestamp(),
193
			'parent' => null,
194
			'expiration' => null,
195
			'token' => null,
196
			'uid_file_owner' => $share->getShareOwner(),
197
			'note' => $share->getNote(),
198
			'label' => $share->getLabel(),
199
			'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
200
		];
201
202
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
203
		if ($recipientNode) {
204
			$node = $recipientNode;
205
		} else {
206
			$nodes = $userFolder->getById($share->getNodeId());
207
			if (empty($nodes)) {
208
				// fallback to guessing the path
209
				$node = $userFolder->get($share->getTarget());
210
				if ($node === null || $share->getTarget() === '') {
211
					throw new NotFoundException();
212
				}
213
			} else {
214
				$node = reset($nodes);
215
			}
216
		}
217
218
		$result['path'] = $userFolder->getRelativePath($node->getPath());
219
		if ($node instanceof Folder) {
220
			$result['item_type'] = 'folder';
221
		} else {
222
			$result['item_type'] = 'file';
223
		}
224
225
		$result['mimetype'] = $node->getMimetype();
226
		$result['has_preview'] = $this->previewManager->isAvailable($node);
227
		$result['storage_id'] = $node->getStorage()->getId();
228
		$result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
229
		$result['item_source'] = $node->getId();
230
		$result['file_source'] = $node->getId();
231
		$result['file_parent'] = $node->getParent()->getId();
232
		$result['file_target'] = $share->getTarget();
233
234
		$expiration = $share->getExpirationDate();
235
		if ($expiration !== null) {
236
			$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
237
		}
238
239
		if ($share->getShareType() === IShare::TYPE_USER) {
240
			$sharedWith = $this->userManager->get($share->getSharedWith());
241
			$result['share_with'] = $share->getSharedWith();
242
			$result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
243
			$result['share_with_displayname_unique'] = $sharedWith !== null ? (
244
				 !empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
245
			) : $share->getSharedWith();
246
			$result['status'] = [];
247
248
			$userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
249
			$userStatus = array_shift($userStatuses);
250
			if ($userStatus) {
251
				$result['status'] = [
252
					'status' => $userStatus->getStatus(),
253
					'message' => $userStatus->getMessage(),
254
					'icon' => $userStatus->getIcon(),
255
					'clearAt' => $userStatus->getClearAt()
256
						? (int)$userStatus->getClearAt()->format('U')
257
						: null,
258
				];
259
			}
260
		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
261
			$group = $this->groupManager->get($share->getSharedWith());
262
			$result['share_with'] = $share->getSharedWith();
263
			$result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
264
		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
265
266
			// "share_with" and "share_with_displayname" for passwords of link
267
			// shares was deprecated in Nextcloud 15, use "password" instead.
268
			$result['share_with'] = $share->getPassword();
269
			$result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
270
271
			$result['password'] = $share->getPassword();
272
273
			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
274
275
			$result['token'] = $share->getToken();
276
			$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
277
		} elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
278
			$result['share_with'] = $share->getSharedWith();
279
			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
280
			$result['token'] = $share->getToken();
281
		} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
282
			$result['share_with'] = $share->getSharedWith();
283
			$result['password'] = $share->getPassword();
284
			$result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
285
			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
286
			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
287
			$result['token'] = $share->getToken();
288
		} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
289
			// getSharedWith() returns either "name (type, owner)" or
290
			// "name (type, owner) [id]", depending on the Circles app version.
291
			$hasCircleId = (substr($share->getSharedWith(), -1) === ']');
292
293
			$result['share_with_displayname'] = $share->getSharedWithDisplayName();
294
			if (empty($result['share_with_displayname'])) {
295
				$displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
296
				$result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
297
			}
298
299
			$result['share_with_avatar'] = $share->getSharedWithAvatar();
300
301
			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
302
			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
303
			if (is_bool($shareWithLength)) {
0 ignored issues
show
introduced by
The condition is_bool($shareWithLength) is always false.
Loading history...
304
				$shareWithLength = -1;
305
			}
306
			$result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
307
		} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
308
			$result['share_with'] = $share->getSharedWith();
309
			$result['share_with_displayname'] = '';
310
311
			try {
312
				$result = array_merge($result, $this->getRoomShareHelper()->formatShare($share));
313
			} catch (QueryException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
314
			}
315
		} elseif ($share->getShareType() === IShare::TYPE_DECK) {
316
			$result['share_with'] = $share->getSharedWith();
317
			$result['share_with_displayname'] = '';
318
319
			try {
320
				$result = array_merge($result, $this->getDeckShareHelper()->formatShare($share));
321
			} catch (QueryException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
322
			}
323
		} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
324
			$result['share_with'] = $share->getSharedWith();
325
			$result['share_with_displayname'] = '';
326
327
			try {
328
				$result = array_merge($result, $this->getSciencemeshShareHelper()->formatShare($share));
329
			} catch (QueryException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
330
			}
331
		}
332
333
334
		$result['mail_send'] = $share->getMailSend() ? 1 : 0;
335
		$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
336
337
		$result['attributes'] = null;
338
		if ($attributes = $share->getAttributes()) {
339
			$result['attributes'] =  \json_encode($attributes->toArray());
340
		}
341
342
		return $result;
343
	}
344
345
	/**
346
	 * Check if one of the users address books knows the exact property, if
347
	 * yes we return the full name.
348
	 *
349
	 * @param string $query
350
	 * @param string $property
351
	 * @return string
352
	 */
353
	private function getDisplayNameFromAddressBook(string $query, string $property): string {
354
		// FIXME: If we inject the contacts manager it gets initialized before any address books are registered
355
		$result = \OC::$server->getContactsManager()->search($query, [$property], [
356
			'limit' => 1,
357
			'enumeration' => false,
358
			'strict_search' => true,
359
		]);
360
		foreach ($result as $r) {
361
			foreach ($r[$property] as $value) {
362
				if ($value === $query && $r['FN']) {
363
					return $r['FN'];
364
				}
365
			}
366
		}
367
368
		return $query;
369
	}
370
371
	/**
372
	 * Get a specific share by id
373
	 *
374
	 * @NoAdminRequired
375
	 *
376
	 * @param string $id
377
	 * @param bool $includeTags
378
	 * @return DataResponse
379
	 * @throws OCSNotFoundException
380
	 */
381
	public function getShare(string $id, bool $include_tags = false): DataResponse {
382
		try {
383
			$share = $this->getShareById($id);
384
		} catch (ShareNotFound $e) {
385
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
386
		}
387
388
		try {
389
			if ($this->canAccessShare($share)) {
390
				$share = $this->formatShare($share);
391
392
				if ($include_tags) {
393
					$share = Helper::populateTags([$share], 'file_source', \OC::$server->getTagManager());
394
				} else {
395
					$share = [$share];
396
				}
397
398
				return new DataResponse($share);
399
			}
400
		} catch (NotFoundException $e) {
401
			// Fall through
402
		}
403
404
		throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
405
	}
406
407
	/**
408
	 * Delete a share
409
	 *
410
	 * @NoAdminRequired
411
	 *
412
	 * @param string $id
413
	 * @return DataResponse
414
	 * @throws OCSNotFoundException
415
	 */
416
	public function deleteShare(string $id): DataResponse {
417
		try {
418
			$share = $this->getShareById($id);
419
		} catch (ShareNotFound $e) {
420
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
421
		}
422
423
		try {
424
			$this->lock($share->getNode());
425
		} catch (LockedException $e) {
426
			throw new OCSNotFoundException($this->l->t('Could not delete share'));
427
		}
428
429
		if (!$this->canAccessShare($share)) {
430
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
431
		}
432
433
		// if it's a group share or a room share
434
		// we don't delete the share, but only the
435
		// mount point. Allowing it to be restored
436
		// from the deleted shares
437
		if ($this->canDeleteShareFromSelf($share)) {
438
			$this->shareManager->deleteFromSelf($share, $this->currentUser);
439
		} else {
440
			if (!$this->canDeleteShare($share)) {
441
				throw new OCSForbiddenException($this->l->t('Could not delete share'));
442
			}
443
444
			$this->shareManager->deleteShare($share);
445
		}
446
447
		return new DataResponse();
448
	}
449
450
	/**
451
	 * @NoAdminRequired
452
	 *
453
	 * @param string $path
454
	 * @param int $permissions
455
	 * @param int $shareType
456
	 * @param string $shareWith
457
	 * @param string $publicUpload
458
	 * @param string $password
459
	 * @param string $sendPasswordByTalk
460
	 * @param string $expireDate
461
	 * @param string $label
462
	 * @param string $attributes
463
	 *
464
	 * @return DataResponse
465
	 * @throws NotFoundException
466
	 * @throws OCSBadRequestException
467
	 * @throws OCSException
468
	 * @throws OCSForbiddenException
469
	 * @throws OCSNotFoundException
470
	 * @throws InvalidPathException
471
	 * @suppress PhanUndeclaredClassMethod
472
	 */
473
	public function createShare(
474
		string $path = null,
475
		int $permissions = null,
476
		int $shareType = -1,
477
		string $shareWith = null,
478
		string $publicUpload = 'false',
479
		string $password = '',
480
		string $sendPasswordByTalk = null,
481
		string $expireDate = '',
482
		string $note = '',
483
		string $label = '',
484
		string $attributes = null
485
	): DataResponse {
486
		$share = $this->shareManager->newShare();
487
488
		if ($permissions === null) {
489
			if ($shareType === IShare::TYPE_LINK
490
				|| $shareType === IShare::TYPE_EMAIL) {
491
492
				// to keep legacy default behaviour, we ignore the setting below for link shares
493
				$permissions = Constants::PERMISSION_READ;
494
			} else {
495
				$permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
496
			}
497
		}
498
499
		// Verify path
500
		if ($path === null) {
501
			throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
502
		}
503
504
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
505
		try {
506
			/** @var \OC\Files\Node\Node $node */
507
			$node = $userFolder->get($path);
508
		} catch (NotFoundException $e) {
509
			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
510
		}
511
512
		// a user can have access to a file through different paths, with differing permissions
513
		// combine all permissions to determine if the user can share this file
514
		$nodes = $userFolder->getById($node->getId());
515
		foreach ($nodes as $nodeById) {
516
			/** @var FileInfo $fileInfo */
517
			$fileInfo = $node->getFileInfo();
518
			$fileInfo['permissions'] |= $nodeById->getPermissions();
519
		}
520
521
		$share->setNode($node);
522
523
		try {
524
			$this->lock($share->getNode());
525
		} catch (LockedException $e) {
526
			throw new OCSNotFoundException($this->l->t('Could not create share'));
527
		}
528
529
		if ($permissions < 0 || $permissions > Constants::PERMISSION_ALL) {
530
			throw new OCSNotFoundException($this->l->t('Invalid permissions'));
531
		}
532
533
		// Shares always require read permissions
534
		$permissions |= Constants::PERMISSION_READ;
535
536
		if ($node instanceof \OCP\Files\File) {
537
			// Single file shares should never have delete or create permissions
538
			$permissions &= ~Constants::PERMISSION_DELETE;
539
			$permissions &= ~Constants::PERMISSION_CREATE;
540
		}
541
542
		/**
543
		 * Hack for https://github.com/owncloud/core/issues/22587
544
		 * We check the permissions via webdav. But the permissions of the mount point
545
		 * do not equal the share permissions. Here we fix that for federated mounts.
546
		 */
547
		if ($node->getStorage()->instanceOfStorage(Storage::class)) {
548
			$permissions &= ~($permissions & ~$node->getPermissions());
549
		}
550
551
		if ($attributes !== null) {
552
			$share = $this->setShareAttributes($share, $attributes);
553
		}
554
555
		$share->setSharedBy($this->currentUser);
556
		$this->checkInheritedAttributes($share);
557
558
		if ($shareType === IShare::TYPE_USER) {
559
			// Valid user is required to share
560
			if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
561
				throw new OCSNotFoundException($this->l->t('Please specify a valid user'));
562
			}
563
			$share->setSharedWith($shareWith);
564
			$share->setPermissions($permissions);
565
		} elseif ($shareType === IShare::TYPE_GROUP) {
566
			if (!$this->shareManager->allowGroupSharing()) {
567
				throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
568
			}
569
570
			// Valid group is required to share
571
			if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
572
				throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
573
			}
574
			$share->setSharedWith($shareWith);
575
			$share->setPermissions($permissions);
576
		} elseif ($shareType === IShare::TYPE_LINK
577
			|| $shareType === IShare::TYPE_EMAIL) {
578
579
			// Can we even share links?
580
			if (!$this->shareManager->shareApiAllowLinks()) {
581
				throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
582
			}
583
584
			if ($publicUpload === 'true') {
585
				// Check if public upload is allowed
586
				if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
587
					throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
588
				}
589
590
				// Public upload can only be set for folders
591
				if ($node instanceof \OCP\Files\File) {
592
					throw new OCSNotFoundException($this->l->t('Public upload is only possible for publicly shared folders'));
593
				}
594
595
				$permissions = Constants::PERMISSION_READ |
596
					Constants::PERMISSION_CREATE |
597
					Constants::PERMISSION_UPDATE |
598
					Constants::PERMISSION_DELETE;
599
			}
600
601
			// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
602
			if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
603
				$permissions |= Constants::PERMISSION_SHARE;
604
			}
605
606
			$share->setPermissions($permissions);
607
608
			// Set password
609
			if ($password !== '') {
610
				$share->setPassword($password);
611
			}
612
613
			// Only share by mail have a recipient
614
			if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
615
				$share->setSharedWith($shareWith);
616
			}
617
618
			// If we have a label, use it
619
			if (!empty($label)) {
620
				$share->setLabel($label);
621
			}
622
623
			if ($sendPasswordByTalk === 'true') {
624
				if (!$this->appManager->isEnabledForUser('spreed')) {
625
					throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$node->getPath()]));
626
				}
627
628
				$share->setSendPasswordByTalk(true);
629
			}
630
		} elseif ($shareType === IShare::TYPE_REMOTE) {
631
			if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
632
				throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
633
			}
634
635
			if ($shareWith === null) {
636
				throw new OCSNotFoundException($this->l->t('Please specify a valid federated user ID'));
637
			}
638
639
			$share->setSharedWith($shareWith);
640
			$share->setPermissions($permissions);
641
			if ($expireDate !== '') {
642
				try {
643
					$expireDate = $this->parseDate($expireDate);
644
					$share->setExpirationDate($expireDate);
645
				} catch (\Exception $e) {
646
					throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
647
				}
648
			}
649
		} elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
650
			if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
651
				throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
652
			}
653
654
			if ($shareWith === null) {
655
				throw new OCSNotFoundException($this->l->t('Please specify a valid federated group ID'));
656
			}
657
658
			$share->setSharedWith($shareWith);
659
			$share->setPermissions($permissions);
660
			if ($expireDate !== '') {
661
				try {
662
					$expireDate = $this->parseDate($expireDate);
663
					$share->setExpirationDate($expireDate);
664
				} catch (\Exception $e) {
665
					throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
666
				}
667
			}
668
		} elseif ($shareType === IShare::TYPE_CIRCLE) {
669
			if (!\OC::$server->getAppManager()->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
670
				throw new OCSNotFoundException($this->l->t('You cannot share to a Circle if the app is not enabled'));
671
			}
672
673
			$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($shareWith);
0 ignored issues
show
Bug introduced by
The type OCA\Circles\Api\v1\Circles was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
674
675
			// Valid circle is required to share
676
			if ($circle === null) {
677
				throw new OCSNotFoundException($this->l->t('Please specify a valid circle'));
678
			}
679
			$share->setSharedWith($shareWith);
680
			$share->setPermissions($permissions);
681
		} elseif ($shareType === IShare::TYPE_ROOM) {
682
			try {
683
				$this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
684
			} catch (QueryException $e) {
685
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
686
			}
687
		} elseif ($shareType === IShare::TYPE_DECK) {
688
			try {
689
				$this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
690
			} catch (QueryException $e) {
691
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
692
			}
693
		} elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
694
			try {
695
				$this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
696
			} catch (QueryException $e) {
697
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support ScienceMesh shares', [$node->getPath()]));
698
			}
699
		} else {
700
			throw new OCSBadRequestException($this->l->t('Unknown share type'));
701
		}
702
703
		//Expire date
704
		if ($expireDate !== '') {
705
			try {
706
				$expireDate = $this->parseDate($expireDate);
707
				$share->setExpirationDate($expireDate);
708
			} catch (\Exception $e) {
709
				throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
710
			}
711
		}
712
713
		$share->setShareType($shareType);
714
715
		if ($note !== '') {
716
			$share->setNote($note);
717
		}
718
719
		try {
720
			$share = $this->shareManager->createShare($share);
721
		} catch (GenericShareException $e) {
722
			\OC::$server->getLogger()->logException($e);
723
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
724
			throw new OCSException($e->getHint(), $code);
725
		} catch (\Exception $e) {
726
			\OC::$server->getLogger()->logException($e);
727
			throw new OCSForbiddenException($e->getMessage(), $e);
728
		}
729
730
		$output = $this->formatShare($share);
731
732
		return new DataResponse($output);
733
	}
734
735
	/**
736
	 * @param null|Node $node
737
	 * @param boolean $includeTags
738
	 *
739
	 * @return array
740
	 */
741
	private function getSharedWithMe($node, bool $includeTags): array {
742
		$userShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_USER, $node, -1, 0);
743
		$groupShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_GROUP, $node, -1, 0);
744
		$circleShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_CIRCLE, $node, -1, 0);
745
		$roomShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_ROOM, $node, -1, 0);
746
		$deckShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_DECK, $node, -1, 0);
747
		$sciencemeshShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_SCIENCEMESH, $node, -1, 0);
748
749
		$shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares);
750
751
		$filteredShares = array_filter($shares, function (IShare $share) {
752
			return $share->getShareOwner() !== $this->currentUser;
753
		});
754
755
		$formatted = [];
756
		foreach ($filteredShares as $share) {
757
			if ($this->canAccessShare($share)) {
758
				try {
759
					$formatted[] = $this->formatShare($share);
760
				} catch (NotFoundException $e) {
761
					// Ignore this share
762
				}
763
			}
764
		}
765
766
		if ($includeTags) {
767
			$formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
768
		}
769
770
		return $formatted;
771
	}
772
773
	/**
774
	 * @param \OCP\Files\Node $folder
775
	 *
776
	 * @return array
777
	 * @throws OCSBadRequestException
778
	 * @throws NotFoundException
779
	 */
780
	private function getSharesInDir(Node $folder): array {
781
		if (!($folder instanceof \OCP\Files\Folder)) {
782
			throw new OCSBadRequestException($this->l->t('Not a directory'));
783
		}
784
785
		$nodes = $folder->getDirectoryListing();
786
787
		/** @var \OCP\Share\IShare[] $shares */
788
		$shares = array_reduce($nodes, function ($carry, $node) {
789
			$carry = array_merge($carry, $this->getAllShares($node, true));
790
			return $carry;
791
		}, []);
792
793
		// filter out duplicate shares
794
		$known = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $known is dead and can be removed.
Loading history...
795
796
797
		$formatted = $miniFormatted = [];
798
		$resharingRight = false;
799
		$known = [];
800
		foreach ($shares as $share) {
801
			if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->currentUser) {
802
				continue;
803
			}
804
805
			try {
806
				$format = $this->formatShare($share);
807
808
				$known[] = $share->getId();
809
				$formatted[] = $format;
810
				if ($share->getSharedBy() === $this->currentUser) {
811
					$miniFormatted[] = $format;
812
				}
813
				if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $folder)) {
814
					$resharingRight = true;
815
				}
816
			} catch (\Exception $e) {
817
				//Ignore this share
818
			}
819
		}
820
821
		if (!$resharingRight) {
822
			$formatted = $miniFormatted;
823
		}
824
825
		return $formatted;
826
	}
827
828
	/**
829
	 * The getShares function.
830
	 *
831
	 * @NoAdminRequired
832
	 *
833
	 * @param string $shared_with_me
834
	 * @param string $reshares
835
	 * @param string $subfiles
836
	 * @param string $path
837
	 *
838
	 * - Get shares by the current user
839
	 * - Get shares by the current user and reshares (?reshares=true)
840
	 * - Get shares with the current user (?shared_with_me=true)
841
	 * - Get shares for a specific path (?path=...)
842
	 * - Get all shares in a folder (?subfiles=true&path=..)
843
	 *
844
	 * @param string $include_tags
845
	 *
846
	 * @return DataResponse
847
	 * @throws NotFoundException
848
	 * @throws OCSBadRequestException
849
	 * @throws OCSNotFoundException
850
	 */
851
	public function getShares(
852
		string $shared_with_me = 'false',
853
		string $reshares = 'false',
854
		string $subfiles = 'false',
855
		string $path = '',
856
		string $include_tags = 'false'
857
	): DataResponse {
858
		$node = null;
859
		if ($path !== '') {
860
			$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
861
			try {
862
				$node = $userFolder->get($path);
863
				$this->lock($node);
864
			} catch (NotFoundException $e) {
865
				throw new OCSNotFoundException(
866
					$this->l->t('Wrong path, file/folder does not exist')
867
				);
868
			} catch (LockedException $e) {
869
				throw new OCSNotFoundException($this->l->t('Could not lock node'));
870
			}
871
		}
872
873
		$shares = $this->getFormattedShares(
874
			$this->currentUser,
875
			$node,
876
			($shared_with_me === 'true'),
877
			($reshares === 'true'),
878
			($subfiles === 'true'),
879
			($include_tags === 'true')
880
		);
881
882
		return new DataResponse($shares);
883
	}
884
885
886
	/**
887
	 * @param string $viewer
888
	 * @param Node $node
889
	 * @param bool $sharedWithMe
890
	 * @param bool $reShares
891
	 * @param bool $subFiles
892
	 * @param bool $includeTags
893
	 *
894
	 * @return array
895
	 * @throws NotFoundException
896
	 * @throws OCSBadRequestException
897
	 */
898
	private function getFormattedShares(
899
		string $viewer,
900
		$node = null,
901
		bool $sharedWithMe = false,
902
		bool $reShares = false,
903
		bool $subFiles = false,
904
		bool $includeTags = false
905
	): array {
906
		if ($sharedWithMe) {
907
			return $this->getSharedWithMe($node, $includeTags);
908
		}
909
910
		if ($subFiles) {
911
			return $this->getSharesInDir($node);
912
		}
913
914
		$shares = $this->getSharesFromNode($viewer, $node, $reShares);
915
916
		$known = $formatted = $miniFormatted = [];
917
		$resharingRight = false;
918
		foreach ($shares as $share) {
919
			try {
920
				$share->getNode();
921
			} catch (NotFoundException $e) {
922
				/*
923
				 * Ignore shares where we can't get the node
924
				 * For example deleted shares
925
				 */
926
				continue;
927
			}
928
929
			if (in_array($share->getId(), $known)
930
				|| ($share->getSharedWith() === $this->currentUser && $share->getShareType() === IShare::TYPE_USER)) {
931
				continue;
932
			}
933
934
			$known[] = $share->getId();
935
			try {
936
				/** @var IShare $share */
937
				$format = $this->formatShare($share, $node);
938
				$formatted[] = $format;
939
940
				// let's also build a list of shares created
941
				// by the current user only, in case
942
				// there is no resharing rights
943
				if ($share->getSharedBy() === $this->currentUser) {
944
					$miniFormatted[] = $format;
945
				}
946
947
				// check if one of those share is shared with me
948
				// and if I have resharing rights on it
949
				if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $node)) {
950
					$resharingRight = true;
951
				}
952
			} catch (InvalidPathException | NotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
953
			}
954
		}
955
956
		if (!$resharingRight) {
957
			$formatted = $miniFormatted;
958
		}
959
960
		if ($includeTags) {
961
			$formatted =
962
				Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
963
		}
964
965
		return $formatted;
966
	}
967
968
969
	/**
970
	 * The getInheritedShares function.
971
	 * returns all shares relative to a file, including parent folders shares rights.
972
	 *
973
	 * @NoAdminRequired
974
	 *
975
	 * @param string $path
976
	 *
977
	 * - Get shares by the current user
978
	 * - Get shares by the current user and reshares (?reshares=true)
979
	 * - Get shares with the current user (?shared_with_me=true)
980
	 * - Get shares for a specific path (?path=...)
981
	 * - Get all shares in a folder (?subfiles=true&path=..)
982
	 *
983
	 * @return DataResponse
984
	 * @throws InvalidPathException
985
	 * @throws NotFoundException
986
	 * @throws OCSNotFoundException
987
	 * @throws OCSBadRequestException
988
	 * @throws SharingRightsException
989
	 */
990
	public function getInheritedShares(string $path): DataResponse {
991
992
		// get Node from (string) path.
993
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
994
		try {
995
			$node = $userFolder->get($path);
996
			$this->lock($node);
997
		} catch (\OCP\Files\NotFoundException $e) {
998
			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
999
		} catch (LockedException $e) {
1000
			throw new OCSNotFoundException($this->l->t('Could not lock path'));
1001
		}
1002
1003
		if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
1004
			throw new SharingRightsException('no sharing rights on this item');
1005
		}
1006
1007
		// The current top parent we have access to
1008
		$parent = $node;
1009
1010
		// initiate real owner.
1011
		$owner = $node->getOwner()
1012
					  ->getUID();
1013
		if (!$this->userManager->userExists($owner)) {
1014
			return new DataResponse([]);
1015
		}
1016
1017
		// get node based on the owner, fix owner in case of external storage
1018
		$userFolder = $this->rootFolder->getUserFolder($owner);
1019
		if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) {
1020
			$owner = $node->getOwner()
1021
						  ->getUID();
1022
			$userFolder = $this->rootFolder->getUserFolder($owner);
1023
			$nodes = $userFolder->getById($node->getId());
1024
			$node = array_shift($nodes);
1025
		}
1026
		$basePath = $userFolder->getPath();
1027
1028
		// generate node list for each parent folders
1029
		/** @var Node[] $nodes */
1030
		$nodes = [];
1031
		while ($node->getPath() !== $basePath) {
1032
			$node = $node->getParent();
1033
			$nodes[] = $node;
1034
		}
1035
1036
		// The user that is requesting this list
1037
		$currentUserFolder = $this->rootFolder->getUserFolder($this->currentUser);
1038
1039
		// for each nodes, retrieve shares.
1040
		$shares = [];
1041
1042
		foreach ($nodes as $node) {
1043
			$getShares = $this->getFormattedShares($owner, $node, false, true);
1044
1045
			$currentUserNodes = $currentUserFolder->getById($node->getId());
1046
			if (!empty($currentUserNodes)) {
1047
				$parent = array_pop($currentUserNodes);
1048
			}
1049
1050
			$subPath = $currentUserFolder->getRelativePath($parent->getPath());
1051
			foreach ($getShares as &$share) {
1052
				$share['via_fileid'] = $parent->getId();
1053
				$share['via_path'] = $subPath;
1054
			}
1055
			$this->mergeFormattedShares($shares, $getShares);
1056
		}
1057
1058
		return new DataResponse(array_values($shares));
1059
	}
1060
1061
	/**
1062
	 * Check whether a set of permissions contains the permissions to check.
1063
	 */
1064
	private function hasPermission(int $permissionsSet, int $permissionsToCheck): bool {
1065
		return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
1066
	}
1067
1068
1069
	/**
1070
	 * @NoAdminRequired
1071
	 *
1072
	 * @param string $id
1073
	 * @param int $permissions
1074
	 * @param string $password
1075
	 * @param string $sendPasswordByTalk
1076
	 * @param string $publicUpload
1077
	 * @param string $expireDate
1078
	 * @param string $note
1079
	 * @param string $label
1080
	 * @param string $hideDownload
1081
	 * @param string $attributes
1082
	 * @return DataResponse
1083
	 * @throws LockedException
1084
	 * @throws NotFoundException
1085
	 * @throws OCSBadRequestException
1086
	 * @throws OCSForbiddenException
1087
	 * @throws OCSNotFoundException
1088
	 */
1089
	public function updateShare(
1090
		string $id,
1091
		int $permissions = null,
1092
		string $password = null,
1093
		string $sendPasswordByTalk = null,
1094
		string $publicUpload = null,
1095
		string $expireDate = null,
1096
		string $note = null,
1097
		string $label = null,
1098
		string $hideDownload = null,
1099
		string $attributes = null
1100
	): DataResponse {
1101
		try {
1102
			$share = $this->getShareById($id);
1103
		} catch (ShareNotFound $e) {
1104
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1105
		}
1106
1107
		$this->lock($share->getNode());
1108
1109
		if (!$this->canAccessShare($share, false)) {
1110
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1111
		}
1112
1113
		if (!$this->canEditShare($share)) {
1114
			throw new OCSForbiddenException('You are not allowed to edit incoming shares');
1115
		}
1116
1117
		if (
1118
			$permissions === null &&
1119
			$password === null &&
1120
			$sendPasswordByTalk === null &&
1121
			$publicUpload === null &&
1122
			$expireDate === null &&
1123
			$note === null &&
1124
			$label === null &&
1125
			$hideDownload === null &&
1126
			$attributes === null
1127
		) {
1128
			throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
1129
		}
1130
1131
		if ($note !== null) {
1132
			$share->setNote($note);
1133
		}
1134
1135
		if ($attributes !== null) {
1136
			$share = $this->setShareAttributes($share, $attributes);
1137
		}
1138
		$this->checkInheritedAttributes($share);
1139
1140
		/**
1141
		 * expirationdate, password and publicUpload only make sense for link shares
1142
		 */
1143
		if ($share->getShareType() === IShare::TYPE_LINK
1144
			|| $share->getShareType() === IShare::TYPE_EMAIL) {
1145
1146
			/**
1147
			 * We do not allow editing link shares that the current user
1148
			 * doesn't own. This is confusing and lead to errors when
1149
			 * someone else edit a password or expiration date without
1150
			 * the share owner knowing about it.
1151
			 * We only allow deletion
1152
			 */
1153
1154
			if ($share->getSharedBy() !== $this->currentUser) {
1155
				throw new OCSForbiddenException('You are not allowed to edit link shares that you don\'t own');
1156
			}
1157
1158
			// Update hide download state
1159
			if ($hideDownload === 'true') {
1160
				$share->setHideDownload(true);
1161
			} elseif ($hideDownload === 'false') {
1162
				$share->setHideDownload(false);
1163
			}
1164
1165
			$newPermissions = null;
1166
			if ($publicUpload === 'true') {
1167
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
1168
			} elseif ($publicUpload === 'false') {
1169
				$newPermissions = Constants::PERMISSION_READ;
1170
			}
1171
1172
			if ($permissions !== null) {
1173
				$newPermissions = $permissions;
1174
				$newPermissions = $newPermissions & ~Constants::PERMISSION_SHARE;
1175
			}
1176
1177
			if ($newPermissions !== null) {
1178
				if (!$this->hasPermission($newPermissions, Constants::PERMISSION_READ) && !$this->hasPermission($newPermissions, Constants::PERMISSION_CREATE)) {
1179
					throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
1180
				}
1181
1182
				if (!$this->hasPermission($newPermissions, Constants::PERMISSION_READ) && (
1183
						$this->hasPermission($newPermissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($newPermissions, Constants::PERMISSION_DELETE)
1184
					)) {
1185
					throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
1186
				}
1187
			}
1188
1189
			if (
1190
				// legacy
1191
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
1192
				// correct
1193
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
1194
			) {
1195
				if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
1196
					throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1197
				}
1198
1199
				if (!($share->getNode() instanceof \OCP\Files\Folder)) {
1200
					throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1201
				}
1202
1203
				// normalize to correct public upload permissions
1204
				if ($publicUpload === 'true') {
1205
					$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
1206
				}
1207
			}
1208
1209
			if ($newPermissions !== null) {
1210
				// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
1211
				if (($newPermissions & Constants::PERMISSION_READ) && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
1212
					$newPermissions |= Constants::PERMISSION_SHARE;
1213
				}
1214
1215
				$share->setPermissions($newPermissions);
1216
				$permissions = $newPermissions;
0 ignored issues
show
Unused Code introduced by
The assignment to $permissions is dead and can be removed.
Loading history...
1217
			}
1218
1219
			if ($password === '') {
1220
				$share->setPassword(null);
1221
			} elseif ($password !== null) {
1222
				$share->setPassword($password);
1223
			}
1224
1225
			if ($label !== null) {
1226
				if (strlen($label) > 255) {
1227
					throw new OCSBadRequestException("Maximum label length is 255");
1228
				}
1229
				$share->setLabel($label);
1230
			}
1231
1232
			if ($sendPasswordByTalk === 'true') {
1233
				if (!$this->appManager->isEnabledForUser('spreed')) {
1234
					throw new OCSForbiddenException($this->l->t('"Sending the password by Nextcloud Talk" for sharing a file or folder failed because Nextcloud Talk is not enabled.'));
1235
				}
1236
1237
				$share->setSendPasswordByTalk(true);
1238
			} elseif ($sendPasswordByTalk !== null) {
1239
				$share->setSendPasswordByTalk(false);
1240
			}
1241
		}
1242
1243
		// NOT A LINK SHARE
1244
		else {
1245
			if ($permissions !== null) {
1246
				$share->setPermissions($permissions);
1247
			}
1248
		}
1249
1250
		if ($expireDate === '') {
1251
			$share->setExpirationDate(null);
1252
		} elseif ($expireDate !== null) {
1253
			try {
1254
				$expireDate = $this->parseDate($expireDate);
1255
			} catch (\Exception $e) {
1256
				throw new OCSBadRequestException($e->getMessage(), $e);
1257
			}
1258
			$share->setExpirationDate($expireDate);
1259
		}
1260
1261
		try {
1262
			$share = $this->shareManager->updateShare($share);
1263
		} catch (GenericShareException $e) {
1264
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1265
			throw new OCSException($e->getHint(), (int)$code);
1266
		} catch (\Exception $e) {
1267
			throw new OCSBadRequestException($e->getMessage(), $e);
1268
		}
1269
1270
		return new DataResponse($this->formatShare($share));
1271
	}
1272
1273
	/**
1274
	 * @NoAdminRequired
1275
	 */
1276
	public function pendingShares(): DataResponse {
1277
		$pendingShares = [];
1278
1279
		$shareTypes = [
1280
			IShare::TYPE_USER,
1281
			IShare::TYPE_GROUP
1282
		];
1283
1284
		foreach ($shareTypes as $shareType) {
1285
			$shares = $this->shareManager->getSharedWith($this->currentUser, $shareType, null, -1, 0);
1286
1287
			foreach ($shares as $share) {
1288
				if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
1289
					$pendingShares[] = $share;
1290
				}
1291
			}
1292
		}
1293
1294
		$result = array_filter(array_map(function (IShare $share) {
1295
			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1296
			$nodes = $userFolder->getById($share->getNodeId());
1297
			if (empty($nodes)) {
1298
				// fallback to guessing the path
1299
				$node = $userFolder->get($share->getTarget());
1300
				if ($node === null || $share->getTarget() === '') {
1301
					return null;
1302
				}
1303
			} else {
1304
				$node = $nodes[0];
1305
			}
1306
1307
			try {
1308
				$formattedShare = $this->formatShare($share, $node);
1309
				$formattedShare['status'] = $share->getStatus();
1310
				$formattedShare['path'] = $share->getNode()->getName();
1311
				$formattedShare['permissions'] = 0;
1312
				return $formattedShare;
1313
			} catch (NotFoundException $e) {
1314
				return null;
1315
			}
1316
		}, $pendingShares), function ($entry) {
1317
			return $entry !== null;
1318
		});
1319
1320
		return new DataResponse($result);
1321
	}
1322
1323
	/**
1324
	 * @NoAdminRequired
1325
	 *
1326
	 * @param string $id
1327
	 * @return DataResponse
1328
	 * @throws OCSNotFoundException
1329
	 * @throws OCSException
1330
	 * @throws OCSBadRequestException
1331
	 */
1332
	public function acceptShare(string $id): DataResponse {
1333
		try {
1334
			$share = $this->getShareById($id);
1335
		} catch (ShareNotFound $e) {
1336
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1337
		}
1338
1339
		if (!$this->canAccessShare($share)) {
1340
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1341
		}
1342
1343
		try {
1344
			$this->shareManager->acceptShare($share, $this->currentUser);
1345
		} catch (GenericShareException $e) {
1346
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1347
			throw new OCSException($e->getHint(), (int)$code);
1348
		} catch (\Exception $e) {
1349
			throw new OCSBadRequestException($e->getMessage(), $e);
1350
		}
1351
1352
		return new DataResponse();
1353
	}
1354
1355
	/**
1356
	 * Does the user have read permission on the share
1357
	 *
1358
	 * @param \OCP\Share\IShare $share the share to check
1359
	 * @param boolean $checkGroups check groups as well?
1360
	 * @return boolean
1361
	 * @throws NotFoundException
1362
	 *
1363
	 * @suppress PhanUndeclaredClassMethod
1364
	 */
1365
	protected function canAccessShare(\OCP\Share\IShare $share, bool $checkGroups = true): bool {
1366
		// A file with permissions 0 can't be accessed by us. So Don't show it
1367
		if ($share->getPermissions() === 0) {
1368
			return false;
1369
		}
1370
1371
		// Owner of the file and the sharer of the file can always get share
1372
		if ($share->getShareOwner() === $this->currentUser
1373
			|| $share->getSharedBy() === $this->currentUser) {
1374
			return true;
1375
		}
1376
1377
		// If the share is shared with you, you can access it!
1378
		if ($share->getShareType() === IShare::TYPE_USER
1379
			&& $share->getSharedWith() === $this->currentUser) {
1380
			return true;
1381
		}
1382
1383
		// Have reshare rights on the shared file/folder ?
1384
		// Does the currentUser have access to the shared file?
1385
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
1386
		$files = $userFolder->getById($share->getNodeId());
1387
		if (!empty($files) && $this->shareProviderResharingRights($this->currentUser, $share, $files[0])) {
1388
			return true;
1389
		}
1390
1391
		// If in the recipient group, you can see the share
1392
		if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
1393
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1394
			$user = $this->userManager->get($this->currentUser);
1395
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1396
				return true;
1397
			}
1398
		}
1399
1400
		if ($share->getShareType() === IShare::TYPE_CIRCLE) {
1401
			// TODO: have a sanity check like above?
1402
			return true;
1403
		}
1404
1405
		if ($share->getShareType() === IShare::TYPE_ROOM) {
1406
			try {
1407
				return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
1408
			} catch (QueryException $e) {
1409
				return false;
1410
			}
1411
		}
1412
1413
		if ($share->getShareType() === IShare::TYPE_DECK) {
1414
			try {
1415
				return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
1416
			} catch (QueryException $e) {
1417
				return false;
1418
			}
1419
		}
1420
1421
		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1422
			try {
1423
				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->currentUser);
1424
			} catch (QueryException $e) {
1425
				return false;
1426
			}
1427
		}
1428
1429
		return false;
1430
	}
1431
1432
	/**
1433
	 * Does the user have edit permission on the share
1434
	 *
1435
	 * @param \OCP\Share\IShare $share the share to check
1436
	 * @return boolean
1437
	 */
1438
	protected function canEditShare(\OCP\Share\IShare $share): bool {
1439
		// A file with permissions 0 can't be accessed by us. So Don't show it
1440
		if ($share->getPermissions() === 0) {
1441
			return false;
1442
		}
1443
1444
		// The owner of the file and the creator of the share
1445
		// can always edit the share
1446
		if ($share->getShareOwner() === $this->currentUser ||
1447
			$share->getSharedBy() === $this->currentUser
1448
		) {
1449
			return true;
1450
		}
1451
1452
		//! we do NOT support some kind of `admin` in groups.
1453
		//! You cannot edit shares shared to a group you're
1454
		//! a member of if you're not the share owner or the file owner!
1455
1456
		return false;
1457
	}
1458
1459
	/**
1460
	 * Does the user have delete permission on the share
1461
	 *
1462
	 * @param \OCP\Share\IShare $share the share to check
1463
	 * @return boolean
1464
	 */
1465
	protected function canDeleteShare(\OCP\Share\IShare $share): bool {
1466
		// A file with permissions 0 can't be accessed by us. So Don't show it
1467
		if ($share->getPermissions() === 0) {
1468
			return false;
1469
		}
1470
1471
		// if the user is the recipient, i can unshare
1472
		// the share with self
1473
		if ($share->getShareType() === IShare::TYPE_USER &&
1474
			$share->getSharedWith() === $this->currentUser
1475
		) {
1476
			return true;
1477
		}
1478
1479
		// The owner of the file and the creator of the share
1480
		// can always delete the share
1481
		if ($share->getShareOwner() === $this->currentUser ||
1482
			$share->getSharedBy() === $this->currentUser
1483
		) {
1484
			return true;
1485
		}
1486
1487
		return false;
1488
	}
1489
1490
	/**
1491
	 * Does the user have delete permission on the share
1492
	 * This differs from the canDeleteShare function as it only
1493
	 * remove the share for the current user. It does NOT
1494
	 * completely delete the share but only the mount point.
1495
	 * It can then be restored from the deleted shares section.
1496
	 *
1497
	 * @param \OCP\Share\IShare $share the share to check
1498
	 * @return boolean
1499
	 *
1500
	 * @suppress PhanUndeclaredClassMethod
1501
	 */
1502
	protected function canDeleteShareFromSelf(\OCP\Share\IShare $share): bool {
1503
		if ($share->getShareType() !== IShare::TYPE_GROUP &&
1504
			$share->getShareType() !== IShare::TYPE_ROOM &&
1505
			$share->getShareType() !== IShare::TYPE_DECK &&
1506
			$share->getShareType() !== IShare::TYPE_SCIENCEMESH
1507
		) {
1508
			return false;
1509
		}
1510
1511
		if ($share->getShareOwner() === $this->currentUser ||
1512
			$share->getSharedBy() === $this->currentUser
1513
		) {
1514
			// Delete the whole share, not just for self
1515
			return false;
1516
		}
1517
1518
		// If in the recipient group, you can delete the share from self
1519
		if ($share->getShareType() === IShare::TYPE_GROUP) {
1520
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1521
			$user = $this->userManager->get($this->currentUser);
1522
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1523
				return true;
1524
			}
1525
		}
1526
1527
		if ($share->getShareType() === IShare::TYPE_ROOM) {
1528
			try {
1529
				return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
1530
			} catch (QueryException $e) {
1531
				return false;
1532
			}
1533
		}
1534
1535
		if ($share->getShareType() === IShare::TYPE_DECK) {
1536
			try {
1537
				return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
1538
			} catch (QueryException $e) {
1539
				return false;
1540
			}
1541
		}
1542
1543
		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1544
			try {
1545
				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->currentUser);
1546
			} catch (QueryException $e) {
1547
				return false;
1548
			}
1549
		}
1550
1551
		return false;
1552
	}
1553
1554
	/**
1555
	 * Make sure that the passed date is valid ISO 8601
1556
	 * So YYYY-MM-DD
1557
	 * If not throw an exception
1558
	 *
1559
	 * @param string $expireDate
1560
	 *
1561
	 * @throws \Exception
1562
	 * @return \DateTime
1563
	 */
1564
	private function parseDate(string $expireDate): \DateTime {
1565
		try {
1566
			$date = new \DateTime(trim($expireDate, "\""));
1567
		} catch (\Exception $e) {
1568
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
1569
		}
1570
1571
		$date->setTime(0, 0, 0);
1572
1573
		return $date;
1574
	}
1575
1576
	/**
1577
	 * Since we have multiple providers but the OCS Share API v1 does
1578
	 * not support this we need to check all backends.
1579
	 *
1580
	 * @param string $id
1581
	 * @return \OCP\Share\IShare
1582
	 * @throws ShareNotFound
1583
	 */
1584
	private function getShareById(string $id): IShare {
1585
		$share = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $share is dead and can be removed.
Loading history...
1586
1587
		// First check if it is an internal share.
1588
		try {
1589
			$share = $this->shareManager->getShareById('ocinternal:' . $id, $this->currentUser);
1590
			return $share;
1591
		} catch (ShareNotFound $e) {
1592
			// Do nothing, just try the other share type
1593
		}
1594
1595
1596
		try {
1597
			if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1598
				$share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->currentUser);
1599
				return $share;
1600
			}
1601
		} catch (ShareNotFound $e) {
1602
			// Do nothing, just try the other share type
1603
		}
1604
1605
		try {
1606
			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1607
				$share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->currentUser);
1608
				return $share;
1609
			}
1610
		} catch (ShareNotFound $e) {
1611
			// Do nothing, just try the other share type
1612
		}
1613
1614
		try {
1615
			$share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->currentUser);
1616
			return $share;
1617
		} catch (ShareNotFound $e) {
1618
			// Do nothing, just try the other share type
1619
		}
1620
1621
		try {
1622
			if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1623
				$share = $this->shareManager->getShareById('deck:' . $id, $this->currentUser);
1624
				return $share;
1625
			}
1626
		} catch (ShareNotFound $e) {
1627
			// Do nothing, just try the other share type
1628
		}
1629
1630
		try {
1631
			if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
1632
				$share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->currentUser);
1633
				return $share;
1634
			}
1635
		} catch (ShareNotFound $e) {
1636
			// Do nothing, just try the other share type
1637
		}
1638
1639
		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1640
			throw new ShareNotFound();
1641
		}
1642
		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->currentUser);
1643
1644
		return $share;
1645
	}
1646
1647
	/**
1648
	 * Lock a Node
1649
	 *
1650
	 * @param \OCP\Files\Node $node
1651
	 * @throws LockedException
1652
	 */
1653
	private function lock(\OCP\Files\Node $node) {
1654
		$node->lock(ILockingProvider::LOCK_SHARED);
1655
		$this->lockedNode = $node;
1656
	}
1657
1658
	/**
1659
	 * Cleanup the remaining locks
1660
	 * @throws LockedException
1661
	 */
1662
	public function cleanup() {
1663
		if ($this->lockedNode !== null) {
1664
			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1665
		}
1666
	}
1667
1668
	/**
1669
	 * Returns the helper of ShareAPIController for room shares.
1670
	 *
1671
	 * If the Talk application is not enabled or the helper is not available
1672
	 * a QueryException is thrown instead.
1673
	 *
1674
	 * @return \OCA\Talk\Share\Helper\ShareAPIController
0 ignored issues
show
Bug introduced by
The type OCA\Talk\Share\Helper\ShareAPIController was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1675
	 * @throws QueryException
1676
	 */
1677
	private function getRoomShareHelper() {
1678
		if (!$this->appManager->isEnabledForUser('spreed')) {
1679
			throw new QueryException();
0 ignored issues
show
Deprecated Code introduced by
The class OCP\AppFramework\QueryException has been deprecated: 20.0.0 catch \Psr\Container\ContainerExceptionInterface ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1679
			throw /** @scrutinizer ignore-deprecated */ new QueryException();
Loading history...
1680
		}
1681
1682
		return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
1683
	}
1684
1685
	/**
1686
	 * Returns the helper of ShareAPIHelper for deck shares.
1687
	 *
1688
	 * If the Deck application is not enabled or the helper is not available
1689
	 * a QueryException is thrown instead.
1690
	 *
1691
	 * @return \OCA\Deck\Sharing\ShareAPIHelper
0 ignored issues
show
Bug introduced by
The type OCA\Deck\Sharing\ShareAPIHelper was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1692
	 * @throws QueryException
1693
	 */
1694
	private function getDeckShareHelper() {
1695
		if (!$this->appManager->isEnabledForUser('deck')) {
1696
			throw new QueryException();
0 ignored issues
show
Deprecated Code introduced by
The class OCP\AppFramework\QueryException has been deprecated: 20.0.0 catch \Psr\Container\ContainerExceptionInterface ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1696
			throw /** @scrutinizer ignore-deprecated */ new QueryException();
Loading history...
1697
		}
1698
1699
		return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
1700
	}
1701
1702
	/**
1703
	 * Returns the helper of ShareAPIHelper for sciencemesh shares.
1704
	 *
1705
	 * If the sciencemesh application is not enabled or the helper is not available
1706
	 * a QueryException is thrown instead.
1707
	 *
1708
	 * @return \OCA\Deck\Sharing\ShareAPIHelper
1709
	 * @throws QueryException
1710
	 */
1711
	private function getSciencemeshShareHelper() {
1712
		if (!$this->appManager->isEnabledForUser('sciencemesh')) {
1713
			throw new QueryException();
0 ignored issues
show
Deprecated Code introduced by
The class OCP\AppFramework\QueryException has been deprecated: 20.0.0 catch \Psr\Container\ContainerExceptionInterface ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1713
			throw /** @scrutinizer ignore-deprecated */ new QueryException();
Loading history...
1714
		}
1715
1716
		return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
1717
	}
1718
1719
	/**
1720
	 * @param string $viewer
1721
	 * @param Node $node
1722
	 * @param bool $reShares
1723
	 *
1724
	 * @return IShare[]
1725
	 */
1726
	private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
1727
		$providers = [
1728
			IShare::TYPE_USER,
1729
			IShare::TYPE_GROUP,
1730
			IShare::TYPE_LINK,
1731
			IShare::TYPE_EMAIL,
1732
			IShare::TYPE_CIRCLE,
1733
			IShare::TYPE_ROOM,
1734
			IShare::TYPE_DECK,
1735
			IShare::TYPE_SCIENCEMESH
1736
		];
1737
1738
		// Should we assume that the (currentUser) viewer is the owner of the node !?
1739
		$shares = [];
1740
		foreach ($providers as $provider) {
1741
			if (!$this->shareManager->shareProviderExists($provider)) {
1742
				continue;
1743
			}
1744
1745
			$providerShares =
1746
				$this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
1747
			$shares = array_merge($shares, $providerShares);
1748
		}
1749
1750
		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1751
			$federatedShares = $this->shareManager->getSharesBy(
1752
				$this->currentUser, IShare::TYPE_REMOTE, $node, $reShares, -1, 0
1753
			);
1754
			$shares = array_merge($shares, $federatedShares);
1755
		}
1756
1757
		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1758
			$federatedShares = $this->shareManager->getSharesBy(
1759
				$this->currentUser, IShare::TYPE_REMOTE_GROUP, $node, $reShares, -1, 0
1760
			);
1761
			$shares = array_merge($shares, $federatedShares);
1762
		}
1763
1764
		return $shares;
1765
	}
1766
1767
1768
	/**
1769
	 * @param Node $node
1770
	 *
1771
	 * @throws SharingRightsException
1772
	 */
1773
	private function confirmSharingRights(Node $node): void {
0 ignored issues
show
Unused Code introduced by
The method confirmSharingRights() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
1774
		if (!$this->hasResharingRights($this->currentUser, $node)) {
1775
			throw new SharingRightsException('no sharing rights on this item');
1776
		}
1777
	}
1778
1779
1780
	/**
1781
	 * @param string $viewer
1782
	 * @param Node $node
1783
	 *
1784
	 * @return bool
1785
	 */
1786
	private function hasResharingRights($viewer, $node): bool {
1787
		if ($viewer === $node->getOwner()->getUID()) {
1788
			return true;
1789
		}
1790
1791
		foreach ([$node, $node->getParent()] as $node) {
0 ignored issues
show
introduced by
$node is overwriting one of the parameters of this function.
Loading history...
1792
			$shares = $this->getSharesFromNode($viewer, $node, true);
1793
			foreach ($shares as $share) {
1794
				try {
1795
					if ($this->shareProviderResharingRights($viewer, $share, $node)) {
1796
						return true;
1797
					}
1798
				} catch (InvalidPathException | NotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1799
				}
1800
			}
1801
		}
1802
1803
		return false;
1804
	}
1805
1806
1807
	/**
1808
	 * Returns if we can find resharing rights in an IShare object for a specific user.
1809
	 *
1810
	 * @suppress PhanUndeclaredClassMethod
1811
	 *
1812
	 * @param string $userId
1813
	 * @param IShare $share
1814
	 * @param Node $node
1815
	 *
1816
	 * @return bool
1817
	 * @throws NotFoundException
1818
	 * @throws InvalidPathException
1819
	 */
1820
	private function shareProviderResharingRights(string $userId, IShare $share, $node): bool {
1821
		if ($share->getShareOwner() === $userId) {
1822
			return true;
1823
		}
1824
1825
		// we check that current user have parent resharing rights on the current file
1826
		if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) {
1827
			return true;
1828
		}
1829
1830
		if ((\OCP\Constants::PERMISSION_SHARE & $share->getPermissions()) === 0) {
1831
			return false;
1832
		}
1833
1834
		if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $userId) {
1835
			return true;
1836
		}
1837
1838
		if ($share->getShareType() === IShare::TYPE_GROUP && $this->groupManager->isInGroup($userId, $share->getSharedWith())) {
1839
			return true;
1840
		}
1841
1842
		if ($share->getShareType() === IShare::TYPE_CIRCLE && \OC::$server->getAppManager()->isEnabledForUser('circles')
1843
			&& class_exists('\OCA\Circles\Api\v1\Circles')) {
1844
			$hasCircleId = (str_ends_with($share->getSharedWith(), ']'));
1845
			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
1846
			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
1847
			if ($shareWithLength === false) {
0 ignored issues
show
introduced by
The condition $shareWithLength === false is always false.
Loading history...
1848
				$sharedWith = substr($share->getSharedWith(), $shareWithStart);
1849
			} else {
1850
				$sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
1851
			}
1852
			try {
1853
				$member = \OCA\Circles\Api\v1\Circles::getMember($sharedWith, $userId, 1);
1854
				if ($member->getLevel() >= 4) {
1855
					return true;
1856
				}
1857
				return false;
1858
			} catch (QueryException $e) {
1859
				return false;
1860
			}
1861
		}
1862
1863
		return false;
1864
	}
1865
1866
	/**
1867
	 * Get all the shares for the current user
1868
	 *
1869
	 * @param Node|null $path
1870
	 * @param boolean $reshares
1871
	 * @return IShare[]
1872
	 */
1873
	private function getAllShares(?Node $path = null, bool $reshares = false) {
1874
		// Get all shares
1875
		$userShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_USER, $path, $reshares, -1, 0);
1876
		$groupShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
1877
		$linkShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_LINK, $path, $reshares, -1, 0);
1878
1879
		// EMAIL SHARES
1880
		$mailShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
1881
1882
		// CIRCLE SHARES
1883
		$circleShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
1884
1885
		// TALK SHARES
1886
		$roomShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
1887
1888
		// DECK SHARES
1889
		$deckShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_DECK, $path, $reshares, -1, 0);
1890
1891
		// SCIENCEMESH SHARES
1892
		$sciencemeshShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_SCIENCEMESH, $path, $reshares, -1, 0);
1893
1894
		// FEDERATION
1895
		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1896
			$federatedShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
1897
		} else {
1898
			$federatedShares = [];
1899
		}
1900
		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1901
			$federatedGroupShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
1902
		} else {
1903
			$federatedGroupShares = [];
1904
		}
1905
1906
		return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares, $federatedShares, $federatedGroupShares);
1907
	}
1908
1909
1910
	/**
1911
	 * merging already formatted shares.
1912
	 * We'll make an associative array to easily detect duplicate Ids.
1913
	 * Keys _needs_ to be removed after all shares are retrieved and merged.
1914
	 *
1915
	 * @param array $shares
1916
	 * @param array $newShares
1917
	 */
1918
	private function mergeFormattedShares(array &$shares, array $newShares) {
1919
		foreach ($newShares as $newShare) {
1920
			if (!array_key_exists($newShare['id'], $shares)) {
1921
				$shares[$newShare['id']] = $newShare;
1922
			}
1923
		}
1924
	}
1925
1926
	/**
1927
	 * @param IShare $share
1928
	 * @param string|null $attributesString
1929
	 * @return IShare modified share
1930
	 */
1931
	private function setShareAttributes(IShare $share, ?string $attributesString) {
1932
		$newShareAttributes = null;
1933
		if ($attributesString !== null) {
1934
			$newShareAttributes = $this->shareManager->newShare()->newAttributes();
1935
			$formattedShareAttributes = \json_decode($attributesString, true);
1936
			if (is_array($formattedShareAttributes)) {
1937
				foreach ($formattedShareAttributes as $formattedAttr) {
1938
					$newShareAttributes->setAttribute(
1939
						$formattedAttr['scope'],
1940
						$formattedAttr['key'],
1941
						is_string($formattedAttr['enabled']) ? (bool) \json_decode($formattedAttr['enabled']) : $formattedAttr['enabled']
1942
					);
1943
				}
1944
			} else {
1945
				throw new OCSBadRequestException('Invalid share attributes provided: \"' . $attributesString . '\"');
1946
			}
1947
		}
1948
		$share->setAttributes($newShareAttributes);
1949
1950
		return $share;
1951
	}
1952
1953
	private function checkInheritedAttributes(IShare $share): void {
1954
		if (!$share->getSharedBy()) {
1955
			return; // Probably in a test
1956
		}
1957
		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1958
		$nodes = $userFolder->getById($share->getNodeId());
1959
		if (empty($nodes)) {
1960
			return;
1961
		}
1962
		$node = $nodes[0];
1963
		if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
1964
			$storage = $node->getStorage();
1965
			if ($storage instanceof Wrapper) {
1966
				$storage = $storage->getInstanceOfStorage(SharedStorage::class);
1967
				if ($storage === null) {
1968
					throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
1969
				}
1970
			} else {
1971
				throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
1972
			}
1973
			/** @var \OCA\Files_Sharing\SharedStorage $storage */
1974
			$inheritedAttributes = $storage->getShare()->getAttributes();
1975
			if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) {
1976
				$share->setHideDownload(true);
1977
				$attributes = $share->getAttributes();
1978
				if ($attributes) {
1979
					$attributes->setAttribute('permissions', 'download', false);
1980
					$share->setAttributes($attributes);
1981
				}
1982
			}
1983
		}
1984
1985
	}
1986
}
1987