Completed
Push — master ( ca0f2f...6aa6d2 )
by Björn
33:52 queued 15:38
created

ShareAPIController::createShare()   F

Complexity

Conditions 42
Paths 768

Size

Total Lines 195

Duplication

Lines 13
Ratio 6.67 %

Importance

Changes 0
Metric Value
cc 42
nc 768
nop 8
dl 13
loc 195
rs 0.2577
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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

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

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

    return array();
}

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

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

Loading history...
680
			return $result;
681
		}
682
683
		if ($subfiles === 'true') {
684
			$result = $this->getSharesInDir($path);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 660 can be null; however, OCA\Files_Sharing\Contro...oller::getSharesInDir() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
685
			return $result;
686
		}
687
688
		if ($reshares === 'true') {
689
			$reshares = true;
690
		} else {
691
			$reshares = false;
692
		}
693
694
		// Get all shares
695
		$userShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_USER, $path, $reshares, -1, 0);
696
		$groupShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_GROUP, $path, $reshares, -1, 0);
697
		$linkShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_LINK, $path, $reshares, -1, 0);
698 View Code Duplication
		if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_EMAIL)) {
699
			$mailShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_EMAIL, $path, $reshares, -1, 0);
700
		} else {
701
			$mailShares = [];
702
		}
703 View Code Duplication
		if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_CIRCLE)) {
704
			$circleShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_CIRCLE, $path, $reshares, -1, 0);
705
		} else {
706
			$circleShares = [];
707
		}
708
		$roomShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_ROOM, $path, $reshares, -1, 0);
709
710
		$shares = array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares);
711
712 View Code Duplication
		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
713
			$federatedShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_REMOTE, $path, $reshares, -1, 0);
714
			$shares = array_merge($shares, $federatedShares);
715
		}
716
717 View Code Duplication
		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
718
			$federatedShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
719
			$shares = array_merge($shares, $federatedShares);
720
		}
721
722
		$formatted = [];
723
		foreach ($shares as $share) {
724
			try {
725
				$formatted[] = $this->formatShare($share, $path);
726
			} catch (NotFoundException $e) {
727
				//Ignore share
728
			}
729
		}
730
731
		if ($include_tags) {
732
			$formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
733
		}
734
735
		return new DataResponse($formatted);
736
	}
737
738
	/**
739
	 * @NoAdminRequired
740
	 *
741
	 * @param string $id
742
	 * @param int $permissions
743
	 * @param string $password
744
	 * @param string $sendPasswordByTalk
745
	 * @param string $publicUpload
746
	 * @param string $expireDate
747
	 * @param string $note
748
	 * @return DataResponse
749
	 * @throws LockedException
750
	 * @throws NotFoundException
751
	 * @throws OCSBadRequestException
752
	 * @throws OCSForbiddenException
753
	 * @throws OCSNotFoundException
754
	 */
755
	public function updateShare(
756
		string $id,
757
		int $permissions = null,
758
		string $password = null,
759
		string $sendPasswordByTalk = null,
760
		string $publicUpload = null,
761
		string $expireDate = null,
762
		string $note = null
763
	): DataResponse {
764
		try {
765
			$share = $this->getShareById($id);
766
		} catch (ShareNotFound $e) {
767
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
768
		}
769
770
		$this->lock($share->getNode());
771
772
		if (!$this->canAccessShare($share, false)) {
773
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
774
		}
775
776
		if ($permissions === null && $password === null && $sendPasswordByTalk === null && $publicUpload === null && $expireDate === null && $note === null) {
777
			throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
778
		}
779
780
		if($note !== null) {
781
			$share->setNote($note);
782
		}
783
784
		/*
785
		 * expirationdate, password and publicUpload only make sense for link shares
786
		 */
787
		if ($share->getShareType() === Share::SHARE_TYPE_LINK) {
788
789
			$newPermissions = null;
790
			if ($publicUpload === 'true') {
791
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
792
			} else if ($publicUpload === 'false') {
793
				$newPermissions = Constants::PERMISSION_READ;
794
			}
795
796
			if ($permissions !== null) {
797
				$newPermissions = (int)$permissions;
798
				$newPermissions = $newPermissions & ~Constants::PERMISSION_SHARE;
799
			}
800
801
			if ($newPermissions !== null &&
802
				!in_array($newPermissions, [
803
					Constants::PERMISSION_READ,
804
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE, // legacy
805
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE, // correct
806
					Constants::PERMISSION_CREATE, // hidden file list
807
					Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE, // allow to edit single files
808
				], true)
809
			) {
810
				throw new OCSBadRequestException($this->l->t('Can\'t change permissions for public share links'));
811
			}
812
813
			if (
814
				// legacy
815
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
816
				// correct
817
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
818
			) {
819
				if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
820
					throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
821
				}
822
823
				if (!($share->getNode() instanceof \OCP\Files\Folder)) {
824
					throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
825
				}
826
827
				// normalize to correct public upload permissions
828
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
829
			}
830
831
			if ($newPermissions !== null) {
832
				$share->setPermissions($newPermissions);
833
				$permissions = $newPermissions;
834
			}
835
836 View Code Duplication
			if ($expireDate === '') {
837
				$share->setExpirationDate(null);
838
			} else if ($expireDate !== null) {
839
				try {
840
					$expireDate = $this->parseDate($expireDate);
841
				} catch (\Exception $e) {
842
					throw new OCSBadRequestException($e->getMessage(), $e);
843
				}
844
				$share->setExpirationDate($expireDate);
845
			}
846
847 View Code Duplication
			if ($password === '') {
848
				$share->setPassword(null);
849
			} else if ($password !== null) {
850
				$share->setPassword($password);
851
			}
852
853
		} else {
854
			if ($permissions !== null) {
855
				$permissions = (int)$permissions;
856
				$share->setPermissions($permissions);
857
			}
858
859
			if ($share->getShareType() === Share::SHARE_TYPE_EMAIL) {
860 View Code Duplication
				if ($password === '') {
861
					$share->setPassword(null);
862
				} else if ($password !== null) {
863
					$share->setPassword($password);
864
				}
865
866 View Code Duplication
				if ($sendPasswordByTalk === 'true') {
867
					if (!$this->appManager->isEnabledForUser('spreed')) {
868
						throw new OCSForbiddenException($this->l->t('Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled'));
869
					}
870
871
					$share->setSendPasswordByTalk(true);
872
				} else {
873
					$share->setSendPasswordByTalk(false);
874
				}
875
			}
876
877 View Code Duplication
			if ($expireDate === '') {
878
				$share->setExpirationDate(null);
879
			} else if ($expireDate !== null) {
880
				try {
881
					$expireDate = $this->parseDate($expireDate);
882
				} catch (\Exception $e) {
883
					throw new OCSBadRequestException($e->getMessage(), $e);
884
				}
885
				$share->setExpirationDate($expireDate);
886
			}
887
888
		}
889
890
		if ($permissions !== null && $share->getShareOwner() !== $this->currentUser) {
891
			/* Check if this is an incomming share */
892
			$incomingShares = $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_USER, $share->getNode(), -1, 0);
893
			$incomingShares = array_merge($incomingShares, $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_GROUP, $share->getNode(), -1, 0));
894
			$incomingShares = array_merge($incomingShares, $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_ROOM, $share->getNode(), -1, 0));
895
896
			/** @var \OCP\Share\IShare[] $incomingShares */
897
			if (!empty($incomingShares)) {
898
				$maxPermissions = 0;
899
				foreach ($incomingShares as $incomingShare) {
900
					$maxPermissions |= $incomingShare->getPermissions();
901
				}
902
903
				if ($share->getPermissions() & ~$maxPermissions) {
904
					throw new OCSNotFoundException($this->l->t('Cannot increase permissions'));
905
				}
906
			}
907
		}
908
909
910
		try {
911
			$share = $this->shareManager->updateShare($share);
912
		} catch (\Exception $e) {
913
			throw new OCSBadRequestException($e->getMessage(), $e);
914
		}
915
916
		return new DataResponse($this->formatShare($share));
917
	}
918
919
	/**
920
	 * @suppress PhanUndeclaredClassMethod
921
	 */
922
	protected function canAccessShare(\OCP\Share\IShare $share, bool $checkGroups = true): bool {
923
		// A file with permissions 0 can't be accessed by us. So Don't show it
924
		if ($share->getPermissions() === 0) {
925
			return false;
926
		}
927
928
		// Owner of the file and the sharer of the file can always get share
929
		if ($share->getShareOwner() === $this->currentUser ||
930
			$share->getSharedBy() === $this->currentUser
931
		) {
932
			return true;
933
		}
934
935
		// If the share is shared with you (or a group you are a member of)
936
		if ($share->getShareType() === Share::SHARE_TYPE_USER &&
937
			$share->getSharedWith() === $this->currentUser
938
		) {
939
			return true;
940
		}
941
942
		if ($checkGroups && $share->getShareType() === Share::SHARE_TYPE_GROUP) {
943
			$sharedWith = $this->groupManager->get($share->getSharedWith());
944
			$user = $this->userManager->get($this->currentUser);
945
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
946
				return true;
947
			}
948
		}
949
950
		if ($share->getShareType() === Share::SHARE_TYPE_CIRCLE) {
951
			// TODO: have a sanity check like above?
952
			return true;
953
		}
954
955
		if ($share->getShareType() === Share::SHARE_TYPE_ROOM) {
956
			try {
957
				return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
958
			} catch (QueryException $e) {
959
				return false;
960
			}
961
		}
962
963
		return false;
964
	}
965
966
	/**
967
	 * Make sure that the passed date is valid ISO 8601
968
	 * So YYYY-MM-DD
969
	 * If not throw an exception
970
	 *
971
	 * @param string $expireDate
972
	 *
973
	 * @throws \Exception
974
	 * @return \DateTime
975
	 */
976
	private function parseDate(string $expireDate): \DateTime {
977
		try {
978
			$date = new \DateTime($expireDate);
979
		} catch (\Exception $e) {
980
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
981
		}
982
983
		if ($date === false) {
984
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
985
		}
986
987
		$date->setTime(0, 0, 0);
988
989
		return $date;
990
	}
991
992
	/**
993
	 * Since we have multiple providers but the OCS Share API v1 does
994
	 * not support this we need to check all backends.
995
	 *
996
	 * @param string $id
997
	 * @return \OCP\Share\IShare
998
	 * @throws ShareNotFound
999
	 */
1000
	private function getShareById(string $id): IShare {
1001
		$share = null;
1002
1003
		// First check if it is an internal share.
1004
		try {
1005
			$share = $this->shareManager->getShareById('ocinternal:' . $id, $this->currentUser);
1006
			return $share;
1007
		} catch (ShareNotFound $e) {
1008
			// Do nothing, just try the other share type
1009
		}
1010
1011
1012
		try {
1013 View Code Duplication
			if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_CIRCLE)) {
1014
				$share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->currentUser);
1015
				return $share;
1016
			}
1017
		} catch (ShareNotFound $e) {
1018
			// Do nothing, just try the other share type
1019
		}
1020
1021
		try {
1022 View Code Duplication
			if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_EMAIL)) {
1023
				$share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->currentUser);
1024
				return $share;
1025
			}
1026
		} catch (ShareNotFound $e) {
1027
			// Do nothing, just try the other share type
1028
		}
1029
1030
		try {
1031
			$share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->currentUser);
1032
			return $share;
1033
		} catch (ShareNotFound $e) {
1034
			// Do nothing, just try the other share type
1035
		}
1036
1037
		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1038
			throw new ShareNotFound();
1039
		}
1040
		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->currentUser);
1041
1042
		return $share;
1043
	}
1044
1045
	/**
1046
	 * Lock a Node
1047
	 *
1048
	 * @param \OCP\Files\Node $node
1049
	 * @throws LockedException
1050
	 */
1051
	private function lock(\OCP\Files\Node $node) {
1052
		$node->lock(ILockingProvider::LOCK_SHARED);
1053
		$this->lockedNode = $node;
1054
	}
1055
1056
	/**
1057
	 * Cleanup the remaining locks
1058
	 * @throws @LockedException
1059
	 */
1060
	public function cleanup() {
1061
		if ($this->lockedNode !== null) {
1062
			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1063
		}
1064
	}
1065
1066
	/**
1067
	 * Returns the helper of ShareAPIController for room shares.
1068
	 *
1069
	 * If the Talk application is not enabled or the helper is not available
1070
	 * a QueryException is thrown instead.
1071
	 *
1072
	 * @return \OCA\Spreed\Share\Helper\ShareAPIController
1073
	 * @throws QueryException
1074
	 */
1075
	private function getRoomShareHelper() {
1076
		if (!$this->appManager->isEnabledForUser('spreed')) {
1077
			throw new QueryException();
1078
		}
1079
1080
		return $this->serverContainer->query('\OCA\Spreed\Share\Helper\ShareAPIController');
1081
	}
1082
}
1083