Passed
Push — master ( 16be14...3c693d )
by Roeland
16:02 queued 12s
created

ShareAPIController::getDeckShareHelper()   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 Christoph Wurst <[email protected]>
10
 * @author Daniel Calviño Sánchez <[email protected]>
11
 * @author Daniel Kesselberg <[email protected]>
12
 * @author Gary Kim <[email protected]>
13
 * @author Georg Ehrke <[email protected]>
14
 * @author Joas Schilling <[email protected]>
15
 * @author John Molakvoæ (skjnldsv) <[email protected]>
16
 * @author Julius Härtl <[email protected]>
17
 * @author Lukas Reschke <[email protected]>
18
 * @author Maxence Lange <[email protected]>
19
 * @author Maxence Lange <[email protected]>
20
 * @author Michael Jobst <[email protected]>
21
 * @author Morris Jobke <[email protected]>
22
 * @author Robin Appelman <[email protected]>
23
 * @author Roeland Jago Douma <[email protected]>
24
 * @author Vincent Petry <[email protected]>
25
 * @author waleczny <[email protected]>
26
 *
27
 * @license AGPL-3.0
28
 *
29
 * This code is free software: you can redistribute it and/or modify
30
 * it under the terms of the GNU Affero General Public License, version 3,
31
 * as published by the Free Software Foundation.
32
 *
33
 * This program is distributed in the hope that it will be useful,
34
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
 * GNU Affero General Public License for more details.
37
 *
38
 * You should have received a copy of the GNU Affero General Public License, version 3,
39
 * along with this program. If not, see <http://www.gnu.org/licenses/>
40
 *
41
 */
42
43
namespace OCA\Files_Sharing\Controller;
44
45
use OCA\Files_Sharing\Exceptions\SharingRightsException;
46
use OCA\Files_Sharing\External\Storage;
47
use OCA\Files\Helper;
48
use OCP\App\IAppManager;
49
use OCP\AppFramework\Http\DataResponse;
50
use OCP\AppFramework\OCS\OCSBadRequestException;
51
use OCP\AppFramework\OCS\OCSException;
52
use OCP\AppFramework\OCS\OCSForbiddenException;
53
use OCP\AppFramework\OCS\OCSNotFoundException;
54
use OCP\AppFramework\OCSController;
55
use OCP\AppFramework\QueryException;
56
use OCP\Constants;
57
use OCP\Files\InvalidPathException;
58
use OCP\Files\IRootFolder;
59
use OCP\Files\Folder;
60
use OCP\Files\Node;
61
use OCP\Files\NotFoundException;
62
use OCP\IConfig;
63
use OCP\IGroupManager;
64
use OCP\IL10N;
65
use OCP\IPreview;
66
use OCP\IRequest;
67
use OCP\IServerContainer;
68
use OCP\IURLGenerator;
69
use OCP\IUserManager;
70
use OCP\Lock\ILockingProvider;
71
use OCP\Lock\LockedException;
72
use OCP\Share;
73
use OCP\Share\Exceptions\GenericShareException;
74
use OCP\Share\Exceptions\ShareNotFound;
75
use OCP\Share\IManager;
76
use OCP\Share\IShare;
77
use OCP\UserStatus\IManager as IUserStatusManager;
78
79
/**
80
 * Class Share20OCS
81
 *
82
 * @package OCA\Files_Sharing\API
83
 */
84
class ShareAPIController extends OCSController {
85
86
	/** @var IManager */
87
	private $shareManager;
88
	/** @var IGroupManager */
89
	private $groupManager;
90
	/** @var IUserManager */
91
	private $userManager;
92
	/** @var IRootFolder */
93
	private $rootFolder;
94
	/** @var IURLGenerator */
95
	private $urlGenerator;
96
	/** @var string */
97
	private $currentUser;
98
	/** @var IL10N */
99
	private $l;
100
	/** @var \OCP\Files\Node */
101
	private $lockedNode;
102
	/** @var IConfig */
103
	private $config;
104
	/** @var IAppManager */
105
	private $appManager;
106
	/** @var IServerContainer */
107
	private $serverContainer;
108
	/** @var IUserStatusManager */
109
	private $userStatusManager;
110
	/** @var IPreview */
111
	private $previewManager;
112
113
	/**
114
	 * Share20OCS constructor.
115
	 *
116
	 * @param string $appName
117
	 * @param IRequest $request
118
	 * @param IManager $shareManager
119
	 * @param IGroupManager $groupManager
120
	 * @param IUserManager $userManager
121
	 * @param IRootFolder $rootFolder
122
	 * @param IURLGenerator $urlGenerator
123
	 * @param string $userId
124
	 * @param IL10N $l10n
125
	 * @param IConfig $config
126
	 * @param IAppManager $appManager
127
	 * @param IServerContainer $serverContainer
128
	 * @param IUserStatusManager $userStatusManager
129
	 */
130
	public function __construct(
131
		string $appName,
132
		IRequest $request,
133
		IManager $shareManager,
134
		IGroupManager $groupManager,
135
		IUserManager $userManager,
136
		IRootFolder $rootFolder,
137
		IURLGenerator $urlGenerator,
138
		string $userId = null,
139
		IL10N $l10n,
140
		IConfig $config,
141
		IAppManager $appManager,
142
		IServerContainer $serverContainer,
143
		IUserStatusManager $userStatusManager,
144
		IPreview $previewManager
145
	) {
146
		parent::__construct($appName, $request);
147
148
		$this->shareManager = $shareManager;
149
		$this->userManager = $userManager;
150
		$this->groupManager = $groupManager;
151
		$this->request = $request;
152
		$this->rootFolder = $rootFolder;
153
		$this->urlGenerator = $urlGenerator;
154
		$this->currentUser = $userId;
155
		$this->l = $l10n;
156
		$this->config = $config;
157
		$this->appManager = $appManager;
158
		$this->serverContainer = $serverContainer;
159
		$this->userStatusManager = $userStatusManager;
160
		$this->previewManager = $previewManager;
161
	}
162
163
	/**
164
	 * Convert an IShare to an array for OCS output
165
	 *
166
	 * @param \OCP\Share\IShare $share
167
	 * @param Node|null $recipientNode
168
	 * @return array
169
	 * @throws NotFoundException In case the node can't be resolved.
170
	 *
171
	 * @suppress PhanUndeclaredClassMethod
172
	 */
173
	protected function formatShare(IShare $share, Node $recipientNode = null): array {
174
		$sharedBy = $this->userManager->get($share->getSharedBy());
175
		$shareOwner = $this->userManager->get($share->getShareOwner());
176
177
		$result = [
178
			'id' => $share->getId(),
179
			'share_type' => $share->getShareType(),
180
			'uid_owner' => $share->getSharedBy(),
181
			'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
182
			// recipient permissions
183
			'permissions' => $share->getPermissions(),
184
			// current user permissions on this share
185
			'can_edit' => $this->canEditShare($share),
186
			'can_delete' => $this->canDeleteShare($share),
187
			'stime' => $share->getShareTime()->getTimestamp(),
188
			'parent' => null,
189
			'expiration' => null,
190
			'token' => null,
191
			'uid_file_owner' => $share->getShareOwner(),
192
			'note' => $share->getNote(),
193
			'label' => $share->getLabel(),
194
			'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
195
		];
196
197
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
198
		if ($recipientNode) {
199
			$node = $recipientNode;
200
		} else {
201
			$nodes = $userFolder->getById($share->getNodeId());
202
			if (empty($nodes)) {
203
				// fallback to guessing the path
204
				$node = $userFolder->get($share->getTarget());
205
				if ($node === null || $share->getTarget() === '') {
206
					throw new NotFoundException();
207
				}
208
			} else {
209
				$node = reset($nodes);
210
			}
211
		}
212
213
		$result['path'] = $userFolder->getRelativePath($node->getPath());
214
		if ($node instanceof Folder) {
215
			$result['item_type'] = 'folder';
216
		} else {
217
			$result['item_type'] = 'file';
218
		}
219
220
		$result['mimetype'] = $node->getMimetype();
221
		$result['has_preview'] = $this->previewManager->isAvailable($node);
222
		$result['storage_id'] = $node->getStorage()->getId();
223
		$result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
224
		$result['item_source'] = $node->getId();
225
		$result['file_source'] = $node->getId();
226
		$result['file_parent'] = $node->getParent()->getId();
227
		$result['file_target'] = $share->getTarget();
228
229
		$expiration = $share->getExpirationDate();
230
		if ($expiration !== null) {
231
			$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
232
		}
233
234
		if ($share->getShareType() === IShare::TYPE_USER) {
235
			$sharedWith = $this->userManager->get($share->getSharedWith());
236
			$result['share_with'] = $share->getSharedWith();
237
			$result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
238
			$result['status'] = [];
239
240
			$userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
241
			$userStatus = array_shift($userStatuses);
242
			if ($userStatus) {
243
				$result['status'] = [
244
					'status' => $userStatus->getStatus(),
245
					'message' => $userStatus->getMessage(),
246
					'icon' => $userStatus->getIcon(),
247
					'clearAt' => $userStatus->getClearAt()
248
						? (int)$userStatus->getClearAt()->format('U')
249
						: null,
250
				];
251
			}
252
		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
253
			$group = $this->groupManager->get($share->getSharedWith());
254
			$result['share_with'] = $share->getSharedWith();
255
			$result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
256
		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
257
258
			// "share_with" and "share_with_displayname" for passwords of link
259
			// shares was deprecated in Nextcloud 15, use "password" instead.
260
			$result['share_with'] = $share->getPassword();
261
			$result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
262
263
			$result['password'] = $share->getPassword();
264
265
			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
266
267
			$result['token'] = $share->getToken();
268
			$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
269
		} elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
270
			$result['share_with'] = $share->getSharedWith();
271
			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
272
			$result['token'] = $share->getToken();
273
		} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
274
			$result['share_with'] = $share->getSharedWith();
275
			$result['password'] = $share->getPassword();
276
			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
277
			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
278
			$result['token'] = $share->getToken();
279
		} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
280
			// getSharedWith() returns either "name (type, owner)" or
281
			// "name (type, owner) [id]", depending on the Circles app version.
282
			$hasCircleId = (substr($share->getSharedWith(), -1) === ']');
283
284
			$result['share_with_displayname'] = $share->getSharedWithDisplayName();
285
			if (empty($result['share_with_displayname'])) {
286
				$displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
287
				$result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
288
			}
289
290
			$result['share_with_avatar'] = $share->getSharedWithAvatar();
291
292
			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
293
			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
294
			if (is_bool($shareWithLength)) {
0 ignored issues
show
introduced by
The condition is_bool($shareWithLength) is always false.
Loading history...
295
				$shareWithLength = -1;
296
			}
297
			$result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
298
		} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
299
			$result['share_with'] = $share->getSharedWith();
300
			$result['share_with_displayname'] = '';
301
302
			try {
303
				$result = array_merge($result, $this->getRoomShareHelper()->formatShare($share));
304
			} catch (QueryException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
305
			}
306
		} elseif ($share->getShareType() === IShare::TYPE_DECK) {
307
			$result['share_with'] = $share->getSharedWith();
308
			$result['share_with_displayname'] = '';
309
310
			try {
311
				$result = array_merge($result, $this->getDeckShareHelper()->formatShare($share));
312
			} catch (QueryException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
313
			}
314
		}
315
316
317
		$result['mail_send'] = $share->getMailSend() ? 1 : 0;
318
		$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
319
320
		return $result;
321
	}
322
323
	/**
324
	 * Check if one of the users address books knows the exact property, if
325
	 * yes we return the full name.
326
	 *
327
	 * @param string $query
328
	 * @param string $property
329
	 * @return string
330
	 */
331
	private function getDisplayNameFromAddressBook(string $query, string $property): string {
332
		// FIXME: If we inject the contacts manager it gets initialized bofore any address books are registered
333
		$result = \OC::$server->getContactsManager()->search($query, [$property]);
334
		foreach ($result as $r) {
335
			foreach ($r[$property] as $value) {
336
				if ($value === $query && $r['FN']) {
337
					return $r['FN'];
338
				}
339
			}
340
		}
341
342
		return $query;
343
	}
344
345
	/**
346
	 * Get a specific share by id
347
	 *
348
	 * @NoAdminRequired
349
	 *
350
	 * @param string $id
351
	 * @return DataResponse
352
	 * @throws OCSNotFoundException
353
	 */
354
	public function getShare(string $id): DataResponse {
355
		try {
356
			$share = $this->getShareById($id);
357
		} catch (ShareNotFound $e) {
358
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
359
		}
360
361
		try {
362
			if ($this->canAccessShare($share)) {
363
				$share = $this->formatShare($share);
364
				return new DataResponse([$share]);
365
			}
366
		} catch (NotFoundException $e) {
367
			// Fall trough
368
		}
369
370
		throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
371
	}
372
373
	/**
374
	 * Delete a share
375
	 *
376
	 * @NoAdminRequired
377
	 *
378
	 * @param string $id
379
	 * @return DataResponse
380
	 * @throws OCSNotFoundException
381
	 */
382
	public function deleteShare(string $id): DataResponse {
383
		try {
384
			$share = $this->getShareById($id);
385
		} catch (ShareNotFound $e) {
386
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
387
		}
388
389
		try {
390
			$this->lock($share->getNode());
391
		} catch (LockedException $e) {
392
			throw new OCSNotFoundException($this->l->t('Could not delete share'));
393
		}
394
395
		if (!$this->canAccessShare($share)) {
396
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
397
		}
398
399
		// if it's a group share or a room share
400
		// we don't delete the share, but only the
401
		// mount point. Allowing it to be restored
402
		// from the deleted shares
403
		if ($this->canDeleteShareFromSelf($share)) {
404
			$this->shareManager->deleteFromSelf($share, $this->currentUser);
405
		} else {
406
			if (!$this->canDeleteShare($share)) {
407
				throw new OCSForbiddenException($this->l->t('Could not delete share'));
408
			}
409
410
			$this->shareManager->deleteShare($share);
411
		}
412
413
		return new DataResponse();
414
	}
415
416
	/**
417
	 * @NoAdminRequired
418
	 *
419
	 * @param string $path
420
	 * @param int $permissions
421
	 * @param int $shareType
422
	 * @param string $shareWith
423
	 * @param string $publicUpload
424
	 * @param string $password
425
	 * @param string $sendPasswordByTalk
426
	 * @param string $expireDate
427
	 * @param string $label
428
	 *
429
	 * @return DataResponse
430
	 * @throws NotFoundException
431
	 * @throws OCSBadRequestException
432
	 * @throws OCSException
433
	 * @throws OCSForbiddenException
434
	 * @throws OCSNotFoundException
435
	 * @throws InvalidPathException
436
	 * @suppress PhanUndeclaredClassMethod
437
	 */
438
	public function createShare(
439
		string $path = null,
440
		int $permissions = null,
441
		int $shareType = -1,
442
		string $shareWith = null,
443
		string $publicUpload = 'false',
444
		string $password = '',
445
		string $sendPasswordByTalk = null,
446
		string $expireDate = '',
447
		string $label = ''
448
	): DataResponse {
449
		$share = $this->shareManager->newShare();
450
451
		if ($permissions === null) {
452
			$permissions = $this->config->getAppValue('core', 'shareapi_default_permissions', Constants::PERMISSION_ALL);
453
		}
454
455
		// Verify path
456
		if ($path === null) {
457
			throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
458
		}
459
460
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
461
		try {
462
			$path = $userFolder->get($path);
463
		} catch (NotFoundException $e) {
464
			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder doesn\'t exist'));
465
		}
466
467
		$share->setNode($path);
468
469
		try {
470
			$this->lock($share->getNode());
471
		} catch (LockedException $e) {
472
			throw new OCSNotFoundException($this->l->t('Could not create share'));
473
		}
474
475
		if ($permissions < 0 || $permissions > Constants::PERMISSION_ALL) {
476
			throw new OCSNotFoundException($this->l->t('invalid permissions'));
477
		}
478
479
		// Shares always require read permissions
480
		$permissions |= Constants::PERMISSION_READ;
481
482
		if ($path instanceof \OCP\Files\File) {
483
			// Single file shares should never have delete or create permissions
484
			$permissions &= ~Constants::PERMISSION_DELETE;
485
			$permissions &= ~Constants::PERMISSION_CREATE;
486
		}
487
488
		/**
489
		 * Hack for https://github.com/owncloud/core/issues/22587
490
		 * We check the permissions via webdav. But the permissions of the mount point
491
		 * do not equal the share permissions. Here we fix that for federated mounts.
492
		 */
493
		if ($path->getStorage()->instanceOfStorage(Storage::class)) {
494
			$permissions &= ~($permissions & ~$path->getPermissions());
495
		}
496
497
		if ($shareType === IShare::TYPE_USER) {
498
			// Valid user is required to share
499
			if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
500
				throw new OCSNotFoundException($this->l->t('Please specify a valid user'));
501
			}
502
			$share->setSharedWith($shareWith);
503
			$share->setPermissions($permissions);
504
		} elseif ($shareType === IShare::TYPE_GROUP) {
505
			if (!$this->shareManager->allowGroupSharing()) {
506
				throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
507
			}
508
509
			// Valid group is required to share
510
			if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
511
				throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
512
			}
513
			$share->setSharedWith($shareWith);
514
			$share->setPermissions($permissions);
515
		} elseif ($shareType === IShare::TYPE_LINK
516
			|| $shareType === IShare::TYPE_EMAIL) {
517
518
			// Can we even share links?
519
			if (!$this->shareManager->shareApiAllowLinks()) {
520
				throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
521
			}
522
523
			if ($publicUpload === 'true') {
524
				// Check if public upload is allowed
525
				if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
526
					throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
527
				}
528
529
				// Public upload can only be set for folders
530
				if ($path instanceof \OCP\Files\File) {
531
					throw new OCSNotFoundException($this->l->t('Public upload is only possible for publicly shared folders'));
532
				}
533
534
				$permissions = Constants::PERMISSION_READ |
535
					Constants::PERMISSION_CREATE |
536
					Constants::PERMISSION_UPDATE |
537
					Constants::PERMISSION_DELETE;
538
			} else {
539
				$permissions = Constants::PERMISSION_READ;
540
			}
541
542
			// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
543
			if (($permissions & Constants::PERMISSION_READ) && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
544
				$permissions |= Constants::PERMISSION_SHARE;
545
			}
546
547
			$share->setPermissions($permissions);
548
549
			// Set password
550
			if ($password !== '') {
551
				$share->setPassword($password);
552
			}
553
554
			// Only share by mail have a recipient
555
			if ($shareType === IShare::TYPE_EMAIL) {
556
				$share->setSharedWith($shareWith);
557
			} else {
558
				// Only link share have a label
559
				if (!empty($label)) {
560
					$share->setLabel($label);
561
				}
562
			}
563
564
			if ($sendPasswordByTalk === 'true') {
565
				if (!$this->appManager->isEnabledForUser('spreed')) {
566
					throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$path->getPath()]));
567
				}
568
569
				$share->setSendPasswordByTalk(true);
570
			}
571
572
			//Expire date
573
			if ($expireDate !== '') {
574
				try {
575
					$expireDate = $this->parseDate($expireDate);
576
					$share->setExpirationDate($expireDate);
577
				} catch (\Exception $e) {
578
					throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
579
				}
580
			}
581
		} elseif ($shareType === IShare::TYPE_REMOTE) {
582
			if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
583
				throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$path->getPath(), $shareType]));
584
			}
585
586
			$share->setSharedWith($shareWith);
587
			$share->setPermissions($permissions);
588
		} elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
589
			if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
590
				throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$path->getPath(), $shareType]));
591
			}
592
593
			$share->setSharedWith($shareWith);
594
			$share->setPermissions($permissions);
595
		} elseif ($shareType === IShare::TYPE_CIRCLE) {
596
			if (!\OC::$server->getAppManager()->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
597
				throw new OCSNotFoundException($this->l->t('You cannot share to a Circle if the app is not enabled'));
598
			}
599
600
			$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...
601
602
			// Valid circle is required to share
603
			if ($circle === null) {
604
				throw new OCSNotFoundException($this->l->t('Please specify a valid circle'));
605
			}
606
			$share->setSharedWith($shareWith);
607
			$share->setPermissions($permissions);
608
		} elseif ($shareType === IShare::TYPE_ROOM) {
609
			try {
610
				$this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
611
			} catch (QueryException $e) {
612
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$path->getPath()]));
613
			}
614
		} elseif ($shareType === IShare::TYPE_DECK) {
615
			try {
616
				$this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
617
			} catch (QueryException $e) {
618
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$path->getPath()]));
619
			}
620
		} else {
621
			throw new OCSBadRequestException($this->l->t('Unknown share type'));
622
		}
623
624
		$share->setShareType($shareType);
625
		$share->setSharedBy($this->currentUser);
626
627
		try {
628
			$share = $this->shareManager->createShare($share);
629
		} catch (GenericShareException $e) {
630
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
631
			throw new OCSException($e->getHint(), $code);
632
		} catch (\Exception $e) {
633
			throw new OCSForbiddenException($e->getMessage(), $e);
634
		}
635
636
		$output = $this->formatShare($share);
637
638
		return new DataResponse($output);
639
	}
640
641
	/**
642
	 * @param null|Node $node
643
	 * @param boolean $includeTags
644
	 *
645
	 * @return array
646
	 */
647
	private function getSharedWithMe($node, bool $includeTags): array {
648
		$userShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_USER, $node, -1, 0);
649
		$groupShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_GROUP, $node, -1, 0);
650
		$circleShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_CIRCLE, $node, -1, 0);
651
		$roomShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_ROOM, $node, -1, 0);
652
		$deckShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_DECK, $node, -1, 0);
653
654
		$shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares);
655
656
		$filteredShares = array_filter($shares, function (IShare $share) {
657
			return $share->getShareOwner() !== $this->currentUser;
658
		});
659
660
		$formatted = [];
661
		foreach ($filteredShares as $share) {
662
			if ($this->canAccessShare($share)) {
663
				try {
664
					$formatted[] = $this->formatShare($share);
665
				} catch (NotFoundException $e) {
666
					// Ignore this share
667
				}
668
			}
669
		}
670
671
		if ($includeTags) {
672
			$formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
673
		}
674
675
		return $formatted;
676
	}
677
678
	/**
679
	 * @param \OCP\Files\Node $folder
680
	 *
681
	 * @return array
682
	 * @throws OCSBadRequestException
683
	 * @throws NotFoundException
684
	 */
685
	private function getSharesInDir(Node $folder): array {
686
		if (!($folder instanceof \OCP\Files\Folder)) {
687
			throw new OCSBadRequestException($this->l->t('Not a directory'));
688
		}
689
690
		$nodes = $folder->getDirectoryListing();
691
692
		/** @var \OCP\Share\IShare[] $shares */
693
		$shares = array_reduce($nodes, function ($carry, $node) {
694
			$carry = array_merge($carry, $this->getAllShares($node, true));
695
			return $carry;
696
		}, []);
697
698
		// filter out duplicate shares
699
		$known = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $known is dead and can be removed.
Loading history...
700
701
702
		$formatted = $miniFormatted = [];
703
		$resharingRight = false;
704
		$known = [];
705
		foreach ($shares as $share) {
706
			if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->currentUser) {
707
				continue;
708
			}
709
710
			try {
711
				$format = $this->formatShare($share);
712
713
				$known[] = $share->getId();
714
				$formatted[] = $format;
715
				if ($share->getSharedBy() === $this->currentUser) {
716
					$miniFormatted[] = $format;
717
				}
718
				if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $folder)) {
719
					$resharingRight = true;
720
				}
721
			} catch (\Exception $e) {
722
				//Ignore this share
723
			}
724
		}
725
726
		if (!$resharingRight) {
727
			$formatted = $miniFormatted;
728
		}
729
730
		return $formatted;
731
	}
732
733
	/**
734
	 * The getShares function.
735
	 *
736
	 * @NoAdminRequired
737
	 *
738
	 * @param string $shared_with_me
739
	 * @param string $reshares
740
	 * @param string $subfiles
741
	 * @param string $path
742
	 *
743
	 * - Get shares by the current user
744
	 * - Get shares by the current user and reshares (?reshares=true)
745
	 * - Get shares with the current user (?shared_with_me=true)
746
	 * - Get shares for a specific path (?path=...)
747
	 * - Get all shares in a folder (?subfiles=true&path=..)
748
	 *
749
	 * @param string $include_tags
750
	 *
751
	 * @return DataResponse
752
	 * @throws NotFoundException
753
	 * @throws OCSBadRequestException
754
	 * @throws OCSNotFoundException
755
	 */
756
	public function getShares(
757
		string $shared_with_me = 'false',
758
		string $reshares = 'false',
759
		string $subfiles = 'false',
760
		string $path = '',
761
		string $include_tags = 'false'
762
	): DataResponse {
763
		$node = null;
764
		if ($path !== '') {
765
			$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
766
			try {
767
				$node = $userFolder->get($path);
768
				$this->lock($node);
769
			} catch (NotFoundException $e) {
770
				throw new OCSNotFoundException(
771
					$this->l->t('Wrong path, file/folder doesn\'t exist')
772
				);
773
			} catch (LockedException $e) {
774
				throw new OCSNotFoundException($this->l->t('Could not lock node'));
775
			}
776
		}
777
778
		$shares = $this->getFormattedShares(
779
			$this->currentUser,
780
			$node,
781
			($shared_with_me === 'true'),
782
			($reshares === 'true'),
783
			($subfiles === 'true'),
784
			($include_tags === 'true')
785
		);
786
787
		return new DataResponse($shares);
788
	}
789
790
791
	/**
792
	 * @param string $viewer
793
	 * @param Node $node
794
	 * @param bool $sharedWithMe
795
	 * @param bool $reShares
796
	 * @param bool $subFiles
797
	 * @param bool $includeTags
798
	 *
799
	 * @return array
800
	 * @throws NotFoundException
801
	 * @throws OCSBadRequestException
802
	 */
803
	private function getFormattedShares(
804
		string $viewer,
805
		$node = null,
806
		bool $sharedWithMe = false,
807
		bool $reShares = false,
808
		bool $subFiles = false,
809
		bool $includeTags = false
810
	): array {
811
		if ($sharedWithMe) {
812
			return $this->getSharedWithMe($node, $includeTags);
813
		}
814
815
		if ($subFiles) {
816
			return $this->getSharesInDir($node);
817
		}
818
819
		$shares = $this->getSharesFromNode($viewer, $node, $reShares);
820
821
		$known = $formatted = $miniFormatted = [];
822
		$resharingRight = false;
823
		foreach ($shares as $share) {
824
			try {
825
				$share->getNode();
826
			} catch (NotFoundException $e) {
827
				/*
828
				 * Ignore shares where we can't get the node
829
				 * For example deleted shares
830
				 */
831
				continue;
832
			}
833
834
			if (in_array($share->getId(), $known)
835
				|| ($share->getSharedWith() === $this->currentUser && $share->getShareType() === IShare::TYPE_USER)) {
836
				continue;
837
			}
838
839
			$known[] = $share->getId();
840
			try {
841
				/** @var IShare $share */
842
				$format = $this->formatShare($share, $node);
843
				$formatted[] = $format;
844
845
				// let's also build a list of shares created
846
				// by the current user only, in case
847
				// there is no resharing rights
848
				if ($share->getSharedBy() === $this->currentUser) {
849
					$miniFormatted[] = $format;
850
				}
851
852
				// check if one of those share is shared with me
853
				// and if I have resharing rights on it
854
				if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $node)) {
855
					$resharingRight = true;
856
				}
857
			} 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...
858
			}
859
		}
860
861
		if (!$resharingRight) {
862
			$formatted = $miniFormatted;
863
		}
864
865
		if ($includeTags) {
866
			$formatted =
867
				Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
868
		}
869
870
		return $formatted;
871
	}
872
873
874
	/**
875
	 * The getInheritedShares function.
876
	 * returns all shares relative to a file, including parent folders shares rights.
877
	 *
878
	 * @NoAdminRequired
879
	 *
880
	 * @param string $path
881
	 *
882
	 * - Get shares by the current user
883
	 * - Get shares by the current user and reshares (?reshares=true)
884
	 * - Get shares with the current user (?shared_with_me=true)
885
	 * - Get shares for a specific path (?path=...)
886
	 * - Get all shares in a folder (?subfiles=true&path=..)
887
	 *
888
	 * @return DataResponse
889
	 * @throws InvalidPathException
890
	 * @throws NotFoundException
891
	 * @throws OCSNotFoundException
892
	 * @throws OCSBadRequestException
893
	 * @throws SharingRightsException
894
	 */
895
	public function getInheritedShares(string $path): DataResponse {
896
897
		// get Node from (string) path.
898
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
899
		try {
900
			$node = $userFolder->get($path);
901
			$this->lock($node);
902
		} catch (\OCP\Files\NotFoundException $e) {
903
			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder doesn\'t exist'));
904
		} catch (LockedException $e) {
905
			throw new OCSNotFoundException($this->l->t('Could not lock path'));
906
		}
907
908
		if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
909
			throw new SharingRightsException('no sharing rights on this item');
910
		}
911
912
		// The current top parent we have access to
913
		$parent = $node;
914
915
		// initiate real owner.
916
		$owner = $node->getOwner()
917
					  ->getUID();
918
		if (!$this->userManager->userExists($owner)) {
919
			return new DataResponse([]);
920
		}
921
922
		// get node based on the owner, fix owner in case of external storage
923
		$userFolder = $this->rootFolder->getUserFolder($owner);
924
		if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) {
925
			$owner = $node->getOwner()
926
						  ->getUID();
927
			$userFolder = $this->rootFolder->getUserFolder($owner);
928
			$nodes = $userFolder->getById($node->getId());
929
			$node = array_shift($nodes);
930
		}
931
		$basePath = $userFolder->getPath();
932
933
		// generate node list for each parent folders
934
		/** @var Node[] $nodes */
935
		$nodes = [];
936
		while ($node->getPath() !== $basePath) {
937
			$node = $node->getParent();
938
			$nodes[] = $node;
939
		}
940
941
		// The user that is requesting this list
942
		$currentUserFolder = $this->rootFolder->getUserFolder($this->currentUser);
943
944
		// for each nodes, retrieve shares.
945
		$shares = [];
946
947
		foreach ($nodes as $node) {
948
			$getShares = $this->getFormattedShares($owner, $node, false, true);
949
950
			$currentUserNodes = $currentUserFolder->getById($node->getId());
951
			if (!empty($currentUserNodes)) {
952
				$parent = array_pop($currentUserNodes);
953
			}
954
955
			$subPath = $currentUserFolder->getRelativePath($parent->getPath());
956
			foreach ($getShares as &$share) {
957
				$share['via_fileid'] = $parent->getId();
958
				$share['via_path'] = $subPath;
959
			}
960
			$this->mergeFormattedShares($shares, $getShares);
961
		}
962
963
		return new DataResponse(array_values($shares));
964
	}
965
966
967
	/**
968
	 * @NoAdminRequired
969
	 *
970
	 * @param string $id
971
	 * @param int $permissions
972
	 * @param string $password
973
	 * @param string $sendPasswordByTalk
974
	 * @param string $publicUpload
975
	 * @param string $expireDate
976
	 * @param string $note
977
	 * @param string $label
978
	 * @param string $hideDownload
979
	 * @return DataResponse
980
	 * @throws LockedException
981
	 * @throws NotFoundException
982
	 * @throws OCSBadRequestException
983
	 * @throws OCSForbiddenException
984
	 * @throws OCSNotFoundException
985
	 */
986
	public function updateShare(
987
		string $id,
988
		int $permissions = null,
989
		string $password = null,
990
		string $sendPasswordByTalk = null,
991
		string $publicUpload = null,
992
		string $expireDate = null,
993
		string $note = null,
994
		string $label = null,
995
		string $hideDownload = null
996
	): DataResponse {
997
		try {
998
			$share = $this->getShareById($id);
999
		} catch (ShareNotFound $e) {
1000
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
1001
		}
1002
1003
		$this->lock($share->getNode());
1004
1005
		if (!$this->canAccessShare($share, false)) {
1006
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
1007
		}
1008
1009
		if (!$this->canEditShare($share)) {
1010
			throw new OCSForbiddenException('You are not allowed to edit incoming shares');
1011
		}
1012
1013
		if (
1014
			$permissions === null &&
1015
			$password === null &&
1016
			$sendPasswordByTalk === null &&
1017
			$publicUpload === null &&
1018
			$expireDate === null &&
1019
			$note === null &&
1020
			$label === null &&
1021
			$hideDownload === null
1022
		) {
1023
			throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
1024
		}
1025
1026
		if ($note !== null) {
1027
			$share->setNote($note);
1028
		}
1029
1030
		/**
1031
		 * expirationdate, password and publicUpload only make sense for link shares
1032
		 */
1033
		if ($share->getShareType() === IShare::TYPE_LINK
1034
			|| $share->getShareType() === IShare::TYPE_EMAIL) {
1035
1036
			/**
1037
			 * We do not allow editing link shares that the current user
1038
			 * doesn't own. This is confusing and lead to errors when
1039
			 * someone else edit a password or expiration date without
1040
			 * the share owner knowing about it.
1041
			 * We only allow deletion
1042
			 */
1043
1044
			if ($share->getSharedBy() !== $this->currentUser) {
1045
				throw new OCSForbiddenException('You are not allowed to edit link shares that you don\'t own');
1046
			}
1047
1048
			// Update hide download state
1049
			if ($hideDownload === 'true') {
1050
				$share->setHideDownload(true);
1051
			} elseif ($hideDownload === 'false') {
1052
				$share->setHideDownload(false);
1053
			}
1054
1055
			$newPermissions = null;
1056
			if ($publicUpload === 'true') {
1057
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
1058
			} elseif ($publicUpload === 'false') {
1059
				$newPermissions = Constants::PERMISSION_READ;
1060
			}
1061
1062
			if ($permissions !== null) {
1063
				$newPermissions = (int) $permissions;
1064
				$newPermissions = $newPermissions & ~Constants::PERMISSION_SHARE;
1065
			}
1066
1067
			if ($newPermissions !== null &&
1068
				!in_array($newPermissions, [
1069
					Constants::PERMISSION_READ,
1070
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE, // legacy
1071
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE, // correct
1072
					Constants::PERMISSION_CREATE, // hidden file list
1073
					Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE, // allow to edit single files
1074
				], true)
1075
			) {
1076
				throw new OCSBadRequestException($this->l->t('Can\'t change permissions for public share links'));
1077
			}
1078
1079
			if (
1080
				// legacy
1081
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
1082
				// correct
1083
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
1084
			) {
1085
				if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
1086
					throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1087
				}
1088
1089
				if (!($share->getNode() instanceof \OCP\Files\Folder)) {
1090
					throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1091
				}
1092
1093
				// normalize to correct public upload permissions
1094
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
1095
			}
1096
1097
			if ($newPermissions !== null) {
1098
				// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
1099
				if (($newPermissions & Constants::PERMISSION_READ) && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
1100
					$newPermissions |= Constants::PERMISSION_SHARE;
1101
				}
1102
1103
				$share->setPermissions($newPermissions);
1104
				$permissions = $newPermissions;
0 ignored issues
show
Unused Code introduced by
The assignment to $permissions is dead and can be removed.
Loading history...
1105
			}
1106
1107
			if ($expireDate === '') {
1108
				$share->setExpirationDate(null);
1109
			} elseif ($expireDate !== null) {
1110
				try {
1111
					$expireDate = $this->parseDate($expireDate);
1112
				} catch (\Exception $e) {
1113
					throw new OCSBadRequestException($e->getMessage(), $e);
1114
				}
1115
				$share->setExpirationDate($expireDate);
1116
			}
1117
1118
			if ($password === '') {
1119
				$share->setPassword(null);
1120
			} elseif ($password !== null) {
1121
				$share->setPassword($password);
1122
			}
1123
1124
			// only link shares have labels
1125
			if ($share->getShareType() === IShare::TYPE_LINK && $label !== null) {
1126
				if (strlen($label) > 255) {
1127
					throw new OCSBadRequestException("Maxmimum label length is 255");
1128
				}
1129
				$share->setLabel($label);
1130
			}
1131
1132
			if ($sendPasswordByTalk === 'true') {
1133
				if (!$this->appManager->isEnabledForUser('spreed')) {
1134
					throw new OCSForbiddenException($this->l->t('Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled'));
1135
				}
1136
1137
				$share->setSendPasswordByTalk(true);
1138
			} elseif ($sendPasswordByTalk !== null) {
1139
				$share->setSendPasswordByTalk(false);
1140
			}
1141
		}
1142
1143
		// NOT A LINK SHARE
1144
		else {
1145
			if ($permissions !== null) {
1146
				$permissions = (int) $permissions;
1147
				$share->setPermissions($permissions);
1148
			}
1149
1150
			if ($expireDate === '') {
1151
				$share->setExpirationDate(null);
1152
			} elseif ($expireDate !== null) {
1153
				try {
1154
					$expireDate = $this->parseDate($expireDate);
1155
				} catch (\Exception $e) {
1156
					throw new OCSBadRequestException($e->getMessage(), $e);
1157
				}
1158
				$share->setExpirationDate($expireDate);
1159
			}
1160
		}
1161
1162
		try {
1163
			$share = $this->shareManager->updateShare($share);
1164
		} catch (GenericShareException $e) {
1165
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1166
			throw new OCSException($e->getHint(), $code);
1167
		} catch (\Exception $e) {
1168
			throw new OCSBadRequestException($e->getMessage(), $e);
1169
		}
1170
1171
		return new DataResponse($this->formatShare($share));
1172
	}
1173
1174
	/**
1175
	 * @NoAdminRequired
1176
	 */
1177
	public function pendingShares(): DataResponse {
1178
		$pendingShares = [];
1179
1180
		$shareTypes = [
1181
			IShare::TYPE_USER,
1182
			IShare::TYPE_GROUP
1183
		];
1184
1185
		foreach ($shareTypes as $shareType) {
1186
			$shares = $this->shareManager->getSharedWith($this->currentUser, $shareType, null, -1, 0);
1187
1188
			foreach ($shares as $share) {
1189
				if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
1190
					$pendingShares[] = $share;
1191
				}
1192
			}
1193
		}
1194
1195
		$result = array_filter(array_map(function (IShare $share) {
1196
			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1197
			$nodes = $userFolder->getById($share->getNodeId());
1198
			if (empty($nodes)) {
1199
				// fallback to guessing the path
1200
				$node = $userFolder->get($share->getTarget());
1201
				if ($node === null || $share->getTarget() === '') {
1202
					return null;
1203
				}
1204
			} else {
1205
				$node = $nodes[0];
1206
			}
1207
1208
			try {
1209
				$formattedShare = $this->formatShare($share, $node);
1210
				$formattedShare['status'] = $share->getStatus();
1211
				$formattedShare['path'] = $share->getNode()->getName();
1212
				$formattedShare['permissions'] = 0;
1213
				return $formattedShare;
1214
			} catch (NotFoundException $e) {
1215
				return null;
1216
			}
1217
		}, $pendingShares), function ($entry) {
1218
			return $entry !== null;
1219
		});
1220
1221
		return new DataResponse($result);
1222
	}
1223
1224
	/**
1225
	 * @NoAdminRequired
1226
	 *
1227
	 * @param string $id
1228
	 * @return DataResponse
1229
	 * @throws OCSNotFoundException
1230
	 * @throws OCSException
1231
	 * @throws OCSBadRequestException
1232
	 */
1233
	public function acceptShare(string $id): DataResponse {
1234
		try {
1235
			$share = $this->getShareById($id);
1236
		} catch (ShareNotFound $e) {
1237
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
1238
		}
1239
1240
		if (!$this->canAccessShare($share)) {
1241
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
1242
		}
1243
1244
		try {
1245
			$this->shareManager->acceptShare($share, $this->currentUser);
1246
		} catch (GenericShareException $e) {
1247
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1248
			throw new OCSException($e->getHint(), $code);
1249
		} catch (\Exception $e) {
1250
			throw new OCSBadRequestException($e->getMessage(), $e);
1251
		}
1252
1253
		return new DataResponse();
1254
	}
1255
1256
	/**
1257
	 * Does the user have read permission on the share
1258
	 *
1259
	 * @param \OCP\Share\IShare $share the share to check
1260
	 * @param boolean $checkGroups check groups as well?
1261
	 * @return boolean
1262
	 * @throws NotFoundException
1263
	 *
1264
	 * @suppress PhanUndeclaredClassMethod
1265
	 */
1266
	protected function canAccessShare(\OCP\Share\IShare $share, bool $checkGroups = true): bool {
1267
		// A file with permissions 0 can't be accessed by us. So Don't show it
1268
		if ($share->getPermissions() === 0) {
1269
			return false;
1270
		}
1271
1272
		// Owner of the file and the sharer of the file can always get share
1273
		if ($share->getShareOwner() === $this->currentUser
1274
			|| $share->getSharedBy() === $this->currentUser) {
1275
			return true;
1276
		}
1277
1278
		// If the share is shared with you, you can access it!
1279
		if ($share->getShareType() === IShare::TYPE_USER
1280
			&& $share->getSharedWith() === $this->currentUser) {
1281
			return true;
1282
		}
1283
1284
		// Have reshare rights on the shared file/folder ?
1285
		// Does the currentUser have access to the shared file?
1286
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
1287
		$files = $userFolder->getById($share->getNodeId());
1288
		if (!empty($files) && $this->shareProviderResharingRights($this->currentUser, $share, $files[0])) {
1289
			return true;
1290
		}
1291
1292
		// If in the recipient group, you can see the share
1293
		if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
1294
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1295
			$user = $this->userManager->get($this->currentUser);
1296
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1297
				return true;
1298
			}
1299
		}
1300
1301
		if ($share->getShareType() === IShare::TYPE_CIRCLE) {
1302
			// TODO: have a sanity check like above?
1303
			return true;
1304
		}
1305
1306
		if ($share->getShareType() === IShare::TYPE_ROOM) {
1307
			try {
1308
				return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
1309
			} catch (QueryException $e) {
1310
				return false;
1311
			}
1312
		}
1313
1314
		if ($share->getShareType() === IShare::TYPE_DECK) {
1315
			try {
1316
				return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
1317
			} catch (QueryException $e) {
1318
				return false;
1319
			}
1320
		}
1321
1322
		return false;
1323
	}
1324
1325
	/**
1326
	 * Does the user have edit permission on the share
1327
	 *
1328
	 * @param \OCP\Share\IShare $share the share to check
1329
	 * @return boolean
1330
	 */
1331
	protected function canEditShare(\OCP\Share\IShare $share): bool {
1332
		// A file with permissions 0 can't be accessed by us. So Don't show it
1333
		if ($share->getPermissions() === 0) {
1334
			return false;
1335
		}
1336
1337
		// The owner of the file and the creator of the share
1338
		// can always edit the share
1339
		if ($share->getShareOwner() === $this->currentUser ||
1340
			$share->getSharedBy() === $this->currentUser
1341
		) {
1342
			return true;
1343
		}
1344
1345
		//! we do NOT support some kind of `admin` in groups.
1346
		//! You cannot edit shares shared to a group you're
1347
		//! a member of if you're not the share owner or the file owner!
1348
1349
		return false;
1350
	}
1351
1352
	/**
1353
	 * Does the user have delete permission on the share
1354
	 *
1355
	 * @param \OCP\Share\IShare $share the share to check
1356
	 * @return boolean
1357
	 */
1358
	protected function canDeleteShare(\OCP\Share\IShare $share): bool {
1359
		// A file with permissions 0 can't be accessed by us. So Don't show it
1360
		if ($share->getPermissions() === 0) {
1361
			return false;
1362
		}
1363
1364
		// if the user is the recipient, i can unshare
1365
		// the share with self
1366
		if ($share->getShareType() === IShare::TYPE_USER &&
1367
			$share->getSharedWith() === $this->currentUser
1368
		) {
1369
			return true;
1370
		}
1371
1372
		// The owner of the file and the creator of the share
1373
		// can always delete the share
1374
		if ($share->getShareOwner() === $this->currentUser ||
1375
			$share->getSharedBy() === $this->currentUser
1376
		) {
1377
			return true;
1378
		}
1379
1380
		return false;
1381
	}
1382
1383
	/**
1384
	 * Does the user have delete permission on the share
1385
	 * This differs from the canDeleteShare function as it only
1386
	 * remove the share for the current user. It does NOT
1387
	 * completely delete the share but only the mount point.
1388
	 * It can then be restored from the deleted shares section.
1389
	 *
1390
	 * @param \OCP\Share\IShare $share the share to check
1391
	 * @return boolean
1392
	 *
1393
	 * @suppress PhanUndeclaredClassMethod
1394
	 */
1395
	protected function canDeleteShareFromSelf(\OCP\Share\IShare $share): bool {
1396
		if ($share->getShareType() !== IShare::TYPE_GROUP &&
1397
			$share->getShareType() !== IShare::TYPE_ROOM &&
1398
			$share->getShareType() !== IShare::TYPE_DECK
1399
		) {
1400
			return false;
1401
		}
1402
1403
		if ($share->getShareOwner() === $this->currentUser ||
1404
			$share->getSharedBy() === $this->currentUser
1405
		) {
1406
			// Delete the whole share, not just for self
1407
			return false;
1408
		}
1409
1410
		// If in the recipient group, you can delete the share from self
1411
		if ($share->getShareType() === IShare::TYPE_GROUP) {
1412
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1413
			$user = $this->userManager->get($this->currentUser);
1414
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1415
				return true;
1416
			}
1417
		}
1418
1419
		if ($share->getShareType() === IShare::TYPE_ROOM) {
1420
			try {
1421
				return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
1422
			} catch (QueryException $e) {
1423
				return false;
1424
			}
1425
		}
1426
1427
		if ($share->getShareType() === IShare::TYPE_DECK) {
1428
			try {
1429
				return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
1430
			} catch (QueryException $e) {
1431
				return false;
1432
			}
1433
		}
1434
1435
		return false;
1436
	}
1437
1438
	/**
1439
	 * Make sure that the passed date is valid ISO 8601
1440
	 * So YYYY-MM-DD
1441
	 * If not throw an exception
1442
	 *
1443
	 * @param string $expireDate
1444
	 *
1445
	 * @throws \Exception
1446
	 * @return \DateTime
1447
	 */
1448
	private function parseDate(string $expireDate): \DateTime {
1449
		try {
1450
			$date = new \DateTime($expireDate);
1451
		} catch (\Exception $e) {
1452
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
1453
		}
1454
1455
		if ($date === false) {
0 ignored issues
show
introduced by
The condition $date === false is always false.
Loading history...
1456
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
1457
		}
1458
1459
		$date->setTime(0, 0, 0);
1460
1461
		return $date;
1462
	}
1463
1464
	/**
1465
	 * Since we have multiple providers but the OCS Share API v1 does
1466
	 * not support this we need to check all backends.
1467
	 *
1468
	 * @param string $id
1469
	 * @return \OCP\Share\IShare
1470
	 * @throws ShareNotFound
1471
	 */
1472
	private function getShareById(string $id): IShare {
1473
		$share = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $share is dead and can be removed.
Loading history...
1474
1475
		// First check if it is an internal share.
1476
		try {
1477
			$share = $this->shareManager->getShareById('ocinternal:' . $id, $this->currentUser);
1478
			return $share;
1479
		} catch (ShareNotFound $e) {
1480
			// Do nothing, just try the other share type
1481
		}
1482
1483
1484
		try {
1485
			if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1486
				$share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->currentUser);
1487
				return $share;
1488
			}
1489
		} catch (ShareNotFound $e) {
1490
			// Do nothing, just try the other share type
1491
		}
1492
1493
		try {
1494
			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1495
				$share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->currentUser);
1496
				return $share;
1497
			}
1498
		} catch (ShareNotFound $e) {
1499
			// Do nothing, just try the other share type
1500
		}
1501
1502
		try {
1503
			$share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->currentUser);
1504
			return $share;
1505
		} catch (ShareNotFound $e) {
1506
			// Do nothing, just try the other share type
1507
		}
1508
1509
		try {
1510
			if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1511
				$share = $this->shareManager->getShareById('deck:' . $id, $this->currentUser);
1512
				return $share;
1513
			}
1514
		} catch (ShareNotFound $e) {
1515
			// Do nothing, just try the other share type
1516
		}
1517
1518
		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1519
			throw new ShareNotFound();
1520
		}
1521
		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->currentUser);
1522
1523
		return $share;
1524
	}
1525
1526
	/**
1527
	 * Lock a Node
1528
	 *
1529
	 * @param \OCP\Files\Node $node
1530
	 * @throws LockedException
1531
	 */
1532
	private function lock(\OCP\Files\Node $node) {
1533
		$node->lock(ILockingProvider::LOCK_SHARED);
1534
		$this->lockedNode = $node;
1535
	}
1536
1537
	/**
1538
	 * Cleanup the remaining locks
1539
	 * @throws LockedException
1540
	 */
1541
	public function cleanup() {
1542
		if ($this->lockedNode !== null) {
1543
			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1544
		}
1545
	}
1546
1547
	/**
1548
	 * Returns the helper of ShareAPIController for room shares.
1549
	 *
1550
	 * If the Talk application is not enabled or the helper is not available
1551
	 * a QueryException is thrown instead.
1552
	 *
1553
	 * @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...
1554
	 * @throws QueryException
1555
	 */
1556
	private function getRoomShareHelper() {
1557
		if (!$this->appManager->isEnabledForUser('spreed')) {
1558
			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

1558
			throw /** @scrutinizer ignore-deprecated */ new QueryException();
Loading history...
1559
		}
1560
1561
		return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
1562
	}
1563
1564
	/**
1565
	 * Returns the helper of ShareAPIHelper for deck shares.
1566
	 *
1567
	 * If the Deck application is not enabled or the helper is not available
1568
	 * a QueryException is thrown instead.
1569
	 *
1570
	 * @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...
1571
	 * @throws QueryException
1572
	 */
1573
	private function getDeckShareHelper() {
1574
		if (!$this->appManager->isEnabledForUser('deck')) {
1575
			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

1575
			throw /** @scrutinizer ignore-deprecated */ new QueryException();
Loading history...
1576
		}
1577
1578
		return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
1579
	}
1580
1581
	/**
1582
	 * @param string $viewer
1583
	 * @param Node $node
1584
	 * @param bool $reShares
1585
	 *
1586
	 * @return IShare[]
1587
	 */
1588
	private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
1589
		$providers = [
1590
			IShare::TYPE_USER,
1591
			IShare::TYPE_GROUP,
1592
			IShare::TYPE_LINK,
1593
			IShare::TYPE_EMAIL,
1594
			IShare::TYPE_EMAIL,
1595
			IShare::TYPE_CIRCLE,
1596
			IShare::TYPE_ROOM,
1597
			IShare::TYPE_DECK
1598
		];
1599
1600
		// Should we assume that the (currentUser) viewer is the owner of the node !?
1601
		$shares = [];
1602
		foreach ($providers as $provider) {
1603
			if (!$this->shareManager->shareProviderExists($provider)) {
1604
				continue;
1605
			}
1606
1607
			$providerShares =
1608
				$this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
1609
			$shares = array_merge($shares, $providerShares);
1610
		}
1611
1612
		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1613
			$federatedShares = $this->shareManager->getSharesBy(
1614
				$this->currentUser, IShare::TYPE_REMOTE, $node, $reShares, -1, 0
1615
			);
1616
			$shares = array_merge($shares, $federatedShares);
1617
		}
1618
1619
		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1620
			$federatedShares = $this->shareManager->getSharesBy(
1621
				$this->currentUser, IShare::TYPE_REMOTE_GROUP, $node, $reShares, -1, 0
1622
			);
1623
			$shares = array_merge($shares, $federatedShares);
1624
		}
1625
1626
		return $shares;
1627
	}
1628
1629
1630
	/**
1631
	 * @param Node $node
1632
	 *
1633
	 * @throws SharingRightsException
1634
	 */
1635
	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...
1636
		if (!$this->hasResharingRights($this->currentUser, $node)) {
1637
			throw new SharingRightsException('no sharing rights on this item');
1638
		}
1639
	}
1640
1641
1642
	/**
1643
	 * @param string $viewer
1644
	 * @param Node $node
1645
	 *
1646
	 * @return bool
1647
	 */
1648
	private function hasResharingRights($viewer, $node): bool {
1649
		if ($viewer === $node->getOwner()->getUID()) {
1650
			return true;
1651
		}
1652
1653
		foreach ([$node, $node->getParent()] as $node) {
0 ignored issues
show
introduced by
$node is overwriting one of the parameters of this function.
Loading history...
1654
			$shares = $this->getSharesFromNode($viewer, $node, true);
1655
			foreach ($shares as $share) {
1656
				try {
1657
					if ($this->shareProviderResharingRights($viewer, $share, $node)) {
1658
						return true;
1659
					}
1660
				} 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...
1661
				}
1662
			}
1663
		}
1664
1665
		return false;
1666
	}
1667
1668
1669
	/**
1670
	 * Returns if we can find resharing rights in an IShare object for a specific user.
1671
	 *
1672
	 * @suppress PhanUndeclaredClassMethod
1673
	 *
1674
	 * @param string $userId
1675
	 * @param IShare $share
1676
	 * @param Node $node
1677
	 *
1678
	 * @return bool
1679
	 * @throws NotFoundException
1680
	 * @throws InvalidPathException
1681
	 */
1682
	private function shareProviderResharingRights(string $userId, IShare $share, $node): bool {
1683
		if ($share->getShareOwner() === $userId) {
1684
			return true;
1685
		}
1686
1687
		// we check that current user have parent resharing rights on the current file
1688
		if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) {
1689
			return true;
1690
		}
1691
1692
		if ((\OCP\Constants::PERMISSION_SHARE & $share->getPermissions()) === 0) {
1693
			return false;
1694
		}
1695
1696
		if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $userId) {
1697
			return true;
1698
		}
1699
1700
		if ($share->getShareType() === IShare::TYPE_GROUP && $this->groupManager->isInGroup($userId, $share->getSharedWith())) {
1701
			return true;
1702
		}
1703
1704
		if ($share->getShareType() === IShare::TYPE_CIRCLE && \OC::$server->getAppManager()->isEnabledForUser('circles')
1705
			&& class_exists('\OCA\Circles\Api\v1\Circles')) {
1706
			$hasCircleId = (substr($share->getSharedWith(), -1) === ']');
1707
			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
1708
			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
1709
			if ($shareWithLength === false) {
0 ignored issues
show
introduced by
The condition $shareWithLength === false is always false.
Loading history...
1710
				$sharedWith = substr($share->getSharedWith(), $shareWithStart);
1711
			} else {
1712
				$sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
1713
			}
1714
			try {
1715
				$member = \OCA\Circles\Api\v1\Circles::getMember($sharedWith, $userId, 1);
1716
				if ($member->getLevel() >= 4) {
1717
					return true;
1718
				}
1719
				return false;
1720
			} catch (QueryException $e) {
1721
				return false;
1722
			}
1723
		}
1724
1725
		return false;
1726
	}
1727
1728
	/**
1729
	 * Get all the shares for the current user
1730
	 *
1731
	 * @param Node|null $path
1732
	 * @param boolean $reshares
1733
	 * @return IShare[]
1734
	 */
1735
	private function getAllShares(?Node $path = null, bool $reshares = false) {
1736
		// Get all shares
1737
		$userShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_USER, $path, $reshares, -1, 0);
1738
		$groupShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
1739
		$linkShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_LINK, $path, $reshares, -1, 0);
1740
1741
		// EMAIL SHARES
1742
		$mailShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
1743
1744
		// CIRCLE SHARES
1745
		$circleShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
1746
1747
		// TALK SHARES
1748
		$roomShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
1749
1750
		$deckShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_DECK, $path, $reshares, -1, 0);
1751
1752
		// FEDERATION
1753
		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1754
			$federatedShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
1755
		} else {
1756
			$federatedShares = [];
1757
		}
1758
		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1759
			$federatedGroupShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
1760
		} else {
1761
			$federatedGroupShares = [];
1762
		}
1763
1764
		return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $federatedShares, $federatedGroupShares);
1765
	}
1766
1767
1768
	/**
1769
	 * merging already formatted shares.
1770
	 * We'll make an associative array to easily detect duplicate Ids.
1771
	 * Keys _needs_ to be removed after all shares are retrieved and merged.
1772
	 *
1773
	 * @param array $shares
1774
	 * @param array $newShares
1775
	 */
1776
	private function mergeFormattedShares(array &$shares, array $newShares) {
1777
		foreach ($newShares as $newShare) {
1778
			if (!array_key_exists($newShare['id'], $shares)) {
1779
				$shares[$newShare['id']] = $newShare;
1780
			}
1781
		}
1782
	}
1783
}
1784