Passed
Push — master ( 80e12c...127af0 )
by Julius
15:15 queued 16s
created

ShareAPIController::getSciencemeshShareHelper()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
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
631
			//Expire date
632
			if ($expireDate !== '') {
633
				try {
634
					$expireDate = $this->parseDate($expireDate);
635
					$share->setExpirationDate($expireDate);
636
				} catch (\Exception $e) {
637
					throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
638
				}
639
			}
640
		} elseif ($shareType === IShare::TYPE_REMOTE) {
641
			if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
642
				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]));
643
			}
644
645
			if ($shareWith === null) {
646
				throw new OCSNotFoundException($this->l->t('Please specify a valid federated user ID'));
647
			}
648
649
			$share->setSharedWith($shareWith);
650
			$share->setPermissions($permissions);
651
			if ($expireDate !== '') {
652
				try {
653
					$expireDate = $this->parseDate($expireDate);
654
					$share->setExpirationDate($expireDate);
655
				} catch (\Exception $e) {
656
					throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
657
				}
658
			}
659
		} elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
660
			if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
661
				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]));
662
			}
663
664
			if ($shareWith === null) {
665
				throw new OCSNotFoundException($this->l->t('Please specify a valid federated group ID'));
666
			}
667
668
			$share->setSharedWith($shareWith);
669
			$share->setPermissions($permissions);
670
			if ($expireDate !== '') {
671
				try {
672
					$expireDate = $this->parseDate($expireDate);
673
					$share->setExpirationDate($expireDate);
674
				} catch (\Exception $e) {
675
					throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
676
				}
677
			}
678
		} elseif ($shareType === IShare::TYPE_CIRCLE) {
679
			if (!\OC::$server->getAppManager()->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
680
				throw new OCSNotFoundException($this->l->t('You cannot share to a Circle if the app is not enabled'));
681
			}
682
683
			$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...
684
685
			// Valid circle is required to share
686
			if ($circle === null) {
687
				throw new OCSNotFoundException($this->l->t('Please specify a valid circle'));
688
			}
689
			$share->setSharedWith($shareWith);
690
			$share->setPermissions($permissions);
691
		} elseif ($shareType === IShare::TYPE_ROOM) {
692
			try {
693
				$this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
694
			} catch (QueryException $e) {
695
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
696
			}
697
		} elseif ($shareType === IShare::TYPE_DECK) {
698
			try {
699
				$this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
700
			} catch (QueryException $e) {
701
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
702
			}
703
		} elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
704
			try {
705
				$this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
706
			} catch (QueryException $e) {
707
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support sciencemesh shares', [$node->getPath()]));
708
			}
709
		} else {
710
			throw new OCSBadRequestException($this->l->t('Unknown share type'));
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 ($expireDate === '') {
1220
				$share->setExpirationDate(null);
1221
			} elseif ($expireDate !== null) {
1222
				try {
1223
					$expireDate = $this->parseDate($expireDate);
1224
				} catch (\Exception $e) {
1225
					throw new OCSBadRequestException($e->getMessage(), $e);
1226
				}
1227
				$share->setExpirationDate($expireDate);
1228
			}
1229
1230
			if ($password === '') {
1231
				$share->setPassword(null);
1232
			} elseif ($password !== null) {
1233
				$share->setPassword($password);
1234
			}
1235
1236
			if ($label !== null) {
1237
				if (strlen($label) > 255) {
1238
					throw new OCSBadRequestException("Maximum label length is 255");
1239
				}
1240
				$share->setLabel($label);
1241
			}
1242
1243
			if ($sendPasswordByTalk === 'true') {
1244
				if (!$this->appManager->isEnabledForUser('spreed')) {
1245
					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.'));
1246
				}
1247
1248
				$share->setSendPasswordByTalk(true);
1249
			} elseif ($sendPasswordByTalk !== null) {
1250
				$share->setSendPasswordByTalk(false);
1251
			}
1252
		}
1253
1254
		// NOT A LINK SHARE
1255
		else {
1256
			if ($permissions !== null) {
1257
				$share->setPermissions($permissions);
1258
			}
1259
1260
			if ($expireDate === '') {
1261
				$share->setExpirationDate(null);
1262
			} elseif ($expireDate !== null) {
1263
				try {
1264
					$expireDate = $this->parseDate($expireDate);
1265
				} catch (\Exception $e) {
1266
					throw new OCSBadRequestException($e->getMessage(), $e);
1267
				}
1268
				$share->setExpirationDate($expireDate);
1269
			}
1270
		}
1271
1272
		try {
1273
			$share = $this->shareManager->updateShare($share);
1274
		} catch (GenericShareException $e) {
1275
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1276
			throw new OCSException($e->getHint(), (int)$code);
1277
		} catch (\Exception $e) {
1278
			throw new OCSBadRequestException($e->getMessage(), $e);
1279
		}
1280
1281
		return new DataResponse($this->formatShare($share));
1282
	}
1283
1284
	/**
1285
	 * @NoAdminRequired
1286
	 */
1287
	public function pendingShares(): DataResponse {
1288
		$pendingShares = [];
1289
1290
		$shareTypes = [
1291
			IShare::TYPE_USER,
1292
			IShare::TYPE_GROUP
1293
		];
1294
1295
		foreach ($shareTypes as $shareType) {
1296
			$shares = $this->shareManager->getSharedWith($this->currentUser, $shareType, null, -1, 0);
1297
1298
			foreach ($shares as $share) {
1299
				if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
1300
					$pendingShares[] = $share;
1301
				}
1302
			}
1303
		}
1304
1305
		$result = array_filter(array_map(function (IShare $share) {
1306
			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1307
			$nodes = $userFolder->getById($share->getNodeId());
1308
			if (empty($nodes)) {
1309
				// fallback to guessing the path
1310
				$node = $userFolder->get($share->getTarget());
1311
				if ($node === null || $share->getTarget() === '') {
1312
					return null;
1313
				}
1314
			} else {
1315
				$node = $nodes[0];
1316
			}
1317
1318
			try {
1319
				$formattedShare = $this->formatShare($share, $node);
1320
				$formattedShare['status'] = $share->getStatus();
1321
				$formattedShare['path'] = $share->getNode()->getName();
1322
				$formattedShare['permissions'] = 0;
1323
				return $formattedShare;
1324
			} catch (NotFoundException $e) {
1325
				return null;
1326
			}
1327
		}, $pendingShares), function ($entry) {
1328
			return $entry !== null;
1329
		});
1330
1331
		return new DataResponse($result);
1332
	}
1333
1334
	/**
1335
	 * @NoAdminRequired
1336
	 *
1337
	 * @param string $id
1338
	 * @return DataResponse
1339
	 * @throws OCSNotFoundException
1340
	 * @throws OCSException
1341
	 * @throws OCSBadRequestException
1342
	 */
1343
	public function acceptShare(string $id): DataResponse {
1344
		try {
1345
			$share = $this->getShareById($id);
1346
		} catch (ShareNotFound $e) {
1347
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1348
		}
1349
1350
		if (!$this->canAccessShare($share)) {
1351
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1352
		}
1353
1354
		try {
1355
			$this->shareManager->acceptShare($share, $this->currentUser);
1356
		} catch (GenericShareException $e) {
1357
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1358
			throw new OCSException($e->getHint(), (int)$code);
1359
		} catch (\Exception $e) {
1360
			throw new OCSBadRequestException($e->getMessage(), $e);
1361
		}
1362
1363
		return new DataResponse();
1364
	}
1365
1366
	/**
1367
	 * Does the user have read permission on the share
1368
	 *
1369
	 * @param \OCP\Share\IShare $share the share to check
1370
	 * @param boolean $checkGroups check groups as well?
1371
	 * @return boolean
1372
	 * @throws NotFoundException
1373
	 *
1374
	 * @suppress PhanUndeclaredClassMethod
1375
	 */
1376
	protected function canAccessShare(\OCP\Share\IShare $share, bool $checkGroups = true): bool {
1377
		// A file with permissions 0 can't be accessed by us. So Don't show it
1378
		if ($share->getPermissions() === 0) {
1379
			return false;
1380
		}
1381
1382
		// Owner of the file and the sharer of the file can always get share
1383
		if ($share->getShareOwner() === $this->currentUser
1384
			|| $share->getSharedBy() === $this->currentUser) {
1385
			return true;
1386
		}
1387
1388
		// If the share is shared with you, you can access it!
1389
		if ($share->getShareType() === IShare::TYPE_USER
1390
			&& $share->getSharedWith() === $this->currentUser) {
1391
			return true;
1392
		}
1393
1394
		// Have reshare rights on the shared file/folder ?
1395
		// Does the currentUser have access to the shared file?
1396
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
1397
		$files = $userFolder->getById($share->getNodeId());
1398
		if (!empty($files) && $this->shareProviderResharingRights($this->currentUser, $share, $files[0])) {
1399
			return true;
1400
		}
1401
1402
		// If in the recipient group, you can see the share
1403
		if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
1404
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1405
			$user = $this->userManager->get($this->currentUser);
1406
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1407
				return true;
1408
			}
1409
		}
1410
1411
		if ($share->getShareType() === IShare::TYPE_CIRCLE) {
1412
			// TODO: have a sanity check like above?
1413
			return true;
1414
		}
1415
1416
		if ($share->getShareType() === IShare::TYPE_ROOM) {
1417
			try {
1418
				return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
1419
			} catch (QueryException $e) {
1420
				return false;
1421
			}
1422
		}
1423
1424
		if ($share->getShareType() === IShare::TYPE_DECK) {
1425
			try {
1426
				return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
1427
			} catch (QueryException $e) {
1428
				return false;
1429
			}
1430
		}
1431
1432
		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1433
			try {
1434
				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->currentUser);
1435
			} catch (QueryException $e) {
1436
				return false;
1437
			}
1438
		}
1439
1440
		return false;
1441
	}
1442
1443
	/**
1444
	 * Does the user have edit permission on the share
1445
	 *
1446
	 * @param \OCP\Share\IShare $share the share to check
1447
	 * @return boolean
1448
	 */
1449
	protected function canEditShare(\OCP\Share\IShare $share): bool {
1450
		// A file with permissions 0 can't be accessed by us. So Don't show it
1451
		if ($share->getPermissions() === 0) {
1452
			return false;
1453
		}
1454
1455
		// The owner of the file and the creator of the share
1456
		// can always edit the share
1457
		if ($share->getShareOwner() === $this->currentUser ||
1458
			$share->getSharedBy() === $this->currentUser
1459
		) {
1460
			return true;
1461
		}
1462
1463
		//! we do NOT support some kind of `admin` in groups.
1464
		//! You cannot edit shares shared to a group you're
1465
		//! a member of if you're not the share owner or the file owner!
1466
1467
		return false;
1468
	}
1469
1470
	/**
1471
	 * Does the user have delete permission on the share
1472
	 *
1473
	 * @param \OCP\Share\IShare $share the share to check
1474
	 * @return boolean
1475
	 */
1476
	protected function canDeleteShare(\OCP\Share\IShare $share): bool {
1477
		// A file with permissions 0 can't be accessed by us. So Don't show it
1478
		if ($share->getPermissions() === 0) {
1479
			return false;
1480
		}
1481
1482
		// if the user is the recipient, i can unshare
1483
		// the share with self
1484
		if ($share->getShareType() === IShare::TYPE_USER &&
1485
			$share->getSharedWith() === $this->currentUser
1486
		) {
1487
			return true;
1488
		}
1489
1490
		// The owner of the file and the creator of the share
1491
		// can always delete the share
1492
		if ($share->getShareOwner() === $this->currentUser ||
1493
			$share->getSharedBy() === $this->currentUser
1494
		) {
1495
			return true;
1496
		}
1497
1498
		return false;
1499
	}
1500
1501
	/**
1502
	 * Does the user have delete permission on the share
1503
	 * This differs from the canDeleteShare function as it only
1504
	 * remove the share for the current user. It does NOT
1505
	 * completely delete the share but only the mount point.
1506
	 * It can then be restored from the deleted shares section.
1507
	 *
1508
	 * @param \OCP\Share\IShare $share the share to check
1509
	 * @return boolean
1510
	 *
1511
	 * @suppress PhanUndeclaredClassMethod
1512
	 */
1513
	protected function canDeleteShareFromSelf(\OCP\Share\IShare $share): bool {
1514
		if ($share->getShareType() !== IShare::TYPE_GROUP &&
1515
			$share->getShareType() !== IShare::TYPE_ROOM &&
1516
			$share->getShareType() !== IShare::TYPE_DECK &&
1517
			$share->getShareType() !== IShare::TYPE_SCIENCEMESH
1518
		) {
1519
			return false;
1520
		}
1521
1522
		if ($share->getShareOwner() === $this->currentUser ||
1523
			$share->getSharedBy() === $this->currentUser
1524
		) {
1525
			// Delete the whole share, not just for self
1526
			return false;
1527
		}
1528
1529
		// If in the recipient group, you can delete the share from self
1530
		if ($share->getShareType() === IShare::TYPE_GROUP) {
1531
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1532
			$user = $this->userManager->get($this->currentUser);
1533
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1534
				return true;
1535
			}
1536
		}
1537
1538
		if ($share->getShareType() === IShare::TYPE_ROOM) {
1539
			try {
1540
				return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
1541
			} catch (QueryException $e) {
1542
				return false;
1543
			}
1544
		}
1545
1546
		if ($share->getShareType() === IShare::TYPE_DECK) {
1547
			try {
1548
				return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
1549
			} catch (QueryException $e) {
1550
				return false;
1551
			}
1552
		}
1553
1554
		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1555
			try {
1556
				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->currentUser);
1557
			} catch (QueryException $e) {
1558
				return false;
1559
			}
1560
		}
1561
1562
		return false;
1563
	}
1564
1565
	/**
1566
	 * Make sure that the passed date is valid ISO 8601
1567
	 * So YYYY-MM-DD
1568
	 * If not throw an exception
1569
	 *
1570
	 * @param string $expireDate
1571
	 *
1572
	 * @throws \Exception
1573
	 * @return \DateTime
1574
	 */
1575
	private function parseDate(string $expireDate): \DateTime {
1576
		try {
1577
			$date = new \DateTime(trim($expireDate, "\""));
1578
		} catch (\Exception $e) {
1579
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
1580
		}
1581
1582
		$date->setTime(0, 0, 0);
1583
1584
		return $date;
1585
	}
1586
1587
	/**
1588
	 * Since we have multiple providers but the OCS Share API v1 does
1589
	 * not support this we need to check all backends.
1590
	 *
1591
	 * @param string $id
1592
	 * @return \OCP\Share\IShare
1593
	 * @throws ShareNotFound
1594
	 */
1595
	private function getShareById(string $id): IShare {
1596
		$share = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $share is dead and can be removed.
Loading history...
1597
1598
		// First check if it is an internal share.
1599
		try {
1600
			$share = $this->shareManager->getShareById('ocinternal:' . $id, $this->currentUser);
1601
			return $share;
1602
		} catch (ShareNotFound $e) {
1603
			// Do nothing, just try the other share type
1604
		}
1605
1606
1607
		try {
1608
			if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1609
				$share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->currentUser);
1610
				return $share;
1611
			}
1612
		} catch (ShareNotFound $e) {
1613
			// Do nothing, just try the other share type
1614
		}
1615
1616
		try {
1617
			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1618
				$share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->currentUser);
1619
				return $share;
1620
			}
1621
		} catch (ShareNotFound $e) {
1622
			// Do nothing, just try the other share type
1623
		}
1624
1625
		try {
1626
			$share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->currentUser);
1627
			return $share;
1628
		} catch (ShareNotFound $e) {
1629
			// Do nothing, just try the other share type
1630
		}
1631
1632
		try {
1633
			if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1634
				$share = $this->shareManager->getShareById('deck:' . $id, $this->currentUser);
1635
				return $share;
1636
			}
1637
		} catch (ShareNotFound $e) {
1638
			// Do nothing, just try the other share type
1639
		}
1640
1641
		try {
1642
			if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
1643
				$share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->currentUser);
1644
				return $share;
1645
			}
1646
		} catch (ShareNotFound $e) {
1647
			// Do nothing, just try the other share type
1648
		}
1649
1650
		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1651
			throw new ShareNotFound();
1652
		}
1653
		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->currentUser);
1654
1655
		return $share;
1656
	}
1657
1658
	/**
1659
	 * Lock a Node
1660
	 *
1661
	 * @param \OCP\Files\Node $node
1662
	 * @throws LockedException
1663
	 */
1664
	private function lock(\OCP\Files\Node $node) {
1665
		$node->lock(ILockingProvider::LOCK_SHARED);
1666
		$this->lockedNode = $node;
1667
	}
1668
1669
	/**
1670
	 * Cleanup the remaining locks
1671
	 * @throws LockedException
1672
	 */
1673
	public function cleanup() {
1674
		if ($this->lockedNode !== null) {
1675
			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1676
		}
1677
	}
1678
1679
	/**
1680
	 * Returns the helper of ShareAPIController for room shares.
1681
	 *
1682
	 * If the Talk application is not enabled or the helper is not available
1683
	 * a QueryException is thrown instead.
1684
	 *
1685
	 * @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...
1686
	 * @throws QueryException
1687
	 */
1688
	private function getRoomShareHelper() {
1689
		if (!$this->appManager->isEnabledForUser('spreed')) {
1690
			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

1690
			throw /** @scrutinizer ignore-deprecated */ new QueryException();
Loading history...
1691
		}
1692
1693
		return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
1694
	}
1695
1696
	/**
1697
	 * Returns the helper of ShareAPIHelper for deck shares.
1698
	 *
1699
	 * If the Deck application is not enabled or the helper is not available
1700
	 * a QueryException is thrown instead.
1701
	 *
1702
	 * @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...
1703
	 * @throws QueryException
1704
	 */
1705
	private function getDeckShareHelper() {
1706
		if (!$this->appManager->isEnabledForUser('deck')) {
1707
			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

1707
			throw /** @scrutinizer ignore-deprecated */ new QueryException();
Loading history...
1708
		}
1709
1710
		return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
1711
	}
1712
1713
	/**
1714
	 * Returns the helper of ShareAPIHelper for sciencemesh shares.
1715
	 *
1716
	 * If the sciencemesh application is not enabled or the helper is not available
1717
	 * a QueryException is thrown instead.
1718
	 *
1719
	 * @return \OCA\Deck\Sharing\ShareAPIHelper
1720
	 * @throws QueryException
1721
	 */
1722
	private function getSciencemeshShareHelper() {
1723
		if (!$this->appManager->isEnabledForUser('sciencemesh')) {
1724
			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

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