Completed
Push — master ( 1ceb08...093983 )
by Joas
41:44 queued 18:22
created

ShareAPIController::getShares()   D

Complexity

Conditions 13
Paths 200

Size

Total Lines 75
Code Lines 50

Duplication

Lines 14
Ratio 18.67 %

Importance

Changes 0
Metric Value
cc 13
eloc 50
nc 200
nop 5
dl 14
loc 75
rs 4.94
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
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\AppFramework\Http\DataResponse;
34
use OCP\AppFramework\OCS\OCSBadRequestException;
35
use OCP\AppFramework\OCS\OCSException;
36
use OCP\AppFramework\OCS\OCSForbiddenException;
37
use OCP\AppFramework\OCS\OCSNotFoundException;
38
use OCP\AppFramework\OCSController;
39
use OCP\Constants;
40
use OCP\Files\Folder;
41
use OCP\Files\Node;
42
use OCP\Files\NotFoundException;
43
use OCP\IConfig;
44
use OCP\IGroupManager;
45
use OCP\IL10N;
46
use OCP\IUserManager;
47
use OCP\IRequest;
48
use OCP\IURLGenerator;
49
use OCP\Files\IRootFolder;
50
use OCP\Lock\LockedException;
51
use OCP\Share\IManager;
52
use OCP\Share\Exceptions\ShareNotFound;
53
use OCP\Share\Exceptions\GenericShareException;
54
use OCP\Lock\ILockingProvider;
55
use OCP\Share\IShare;
56
use OCA\Files_Sharing\External\Storage;
57
58
/**
59
 * Class Share20OCS
60
 *
61
 * @package OCA\Files_Sharing\API
62
 */
63
class ShareAPIController extends OCSController {
64
65
	/** @var IManager */
66
	private $shareManager;
67
	/** @var IGroupManager */
68
	private $groupManager;
69
	/** @var IUserManager */
70
	private $userManager;
71
	/** @var IRootFolder */
72
	private $rootFolder;
73
	/** @var IURLGenerator */
74
	private $urlGenerator;
75
	/** @var string */
76
	private $currentUser;
77
	/** @var IL10N */
78
	private $l;
79
	/** @var \OCP\Files\Node */
80
	private $lockedNode;
81
	/** @var IConfig */
82
	private $config;
83
84
	/**
85
	 * Share20OCS constructor.
86
	 *
87
	 * @param string $appName
88
	 * @param IRequest $request
89
	 * @param IManager $shareManager
90
	 * @param IGroupManager $groupManager
91
	 * @param IUserManager $userManager
92
	 * @param IRootFolder $rootFolder
93
	 * @param IURLGenerator $urlGenerator
94
	 * @param string $userId
95
	 * @param IL10N $l10n
96
	 * @param IConfig $config
97
	 */
98
	public function __construct(
99
		string $appName,
100
		IRequest $request,
101
		IManager $shareManager,
102
		IGroupManager $groupManager,
103
		IUserManager $userManager,
104
		IRootFolder $rootFolder,
105
		IURLGenerator $urlGenerator,
106
		string $userId,
107
		IL10N $l10n,
108
		IConfig $config
109
	) {
110
		parent::__construct($appName, $request);
111
112
		$this->shareManager = $shareManager;
113
		$this->userManager = $userManager;
114
		$this->groupManager = $groupManager;
115
		$this->request = $request;
116
		$this->rootFolder = $rootFolder;
117
		$this->urlGenerator = $urlGenerator;
118
		$this->currentUser = $userId;
119
		$this->l = $l10n;
120
		$this->config = $config;
121
	}
122
123
	/**
124
	 * Convert an IShare to an array for OCS output
125
	 *
126
	 * @param \OCP\Share\IShare $share
127
	 * @param Node|null $recipientNode
128
	 * @return array
129
	 * @throws NotFoundException In case the node can't be resolved.
130
	 */
131
	protected function formatShare(\OCP\Share\IShare $share, Node $recipientNode = null): array {
132
		$sharedBy = $this->userManager->get($share->getSharedBy());
133
		$shareOwner = $this->userManager->get($share->getShareOwner());
134
135
		$result = [
136
			'id' => $share->getId(),
137
			'share_type' => $share->getShareType(),
138
			'uid_owner' => $share->getSharedBy(),
139
			'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
140
			'permissions' => $share->getPermissions(),
141
			'stime' => $share->getShareTime()->getTimestamp(),
142
			'parent' => null,
143
			'expiration' => null,
144
			'token' => null,
145
			'uid_file_owner' => $share->getShareOwner(),
146
			'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
147
		];
148
149
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
150
		if ($recipientNode) {
151
			$node = $recipientNode;
152
		} else {
153
			$nodes = $userFolder->getById($share->getNodeId());
154
155
			if (empty($nodes)) {
156
				// fallback to guessing the path
157
				$node = $userFolder->get($share->getTarget());
158
				if ($node === null || $share->getTarget() === '') {
159
					throw new NotFoundException();
160
				}
161
			} else {
162
				$node = $nodes[0];
163
			}
164
		}
165
166
		$result['path'] = $userFolder->getRelativePath($node->getPath());
167
		if ($node instanceOf \OCP\Files\Folder) {
168
			$result['item_type'] = 'folder';
169
		} else {
170
			$result['item_type'] = 'file';
171
		}
172
		$result['mimetype'] = $node->getMimetype();
173
		$result['storage_id'] = $node->getStorage()->getId();
174
		$result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
175
		$result['item_source'] = $node->getId();
176
		$result['file_source'] = $node->getId();
177
		$result['file_parent'] = $node->getParent()->getId();
178
		$result['file_target'] = $share->getTarget();
179
180
		$expiration = $share->getExpirationDate();
181
		if ($expiration !== null) {
182
			$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
183
		}
184
185
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
186
			$sharedWith = $this->userManager->get($share->getSharedWith());
187
			$result['share_with'] = $share->getSharedWith();
188
			$result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
189
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
190
			$group = $this->groupManager->get($share->getSharedWith());
191
			$result['share_with'] = $share->getSharedWith();
192
			$result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
193
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
194
195
			$result['share_with'] = $share->getPassword();
196
			$result['share_with_displayname'] = $share->getPassword();
197
198
			$result['token'] = $share->getToken();
199
			$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
200
201
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
202
			$result['share_with'] = $share->getSharedWith();
203
			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
204
			$result['token'] = $share->getToken();
205
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
206
			$result['share_with'] = $share->getSharedWith();
207
			$result['password'] = $share->getPassword();
208
			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
209
			$result['token'] = $share->getToken();
210
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_CIRCLE) {
211
			// getSharedWith() returns either "name (type, owner)" or
212
			// "name (type, owner) [id]", depending on the Circles app version.
213
			$hasCircleId = (substr($share->getSharedWith(), -1) === ']');
214
215
			$displayNameLength = ($hasCircleId? strrpos($share->getSharedWith(), ' '): strlen($share->getSharedWith()));
216
			$result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
217
218
			$shareWithStart = ($hasCircleId? strrpos($share->getSharedWith(), '[') + 1: 0);
219
			$shareWithLength = ($hasCircleId? -1: strpos($share->getSharedWith(), ' '));
220
			$result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
221
		}
222
223
224
		$result['mail_send'] = $share->getMailSend() ? 1 : 0;
225
226
		return $result;
227
	}
228
229
	/**
230
	 * Check if one of the users address books knows the exact property, if
231
	 * yes we return the full name.
232
	 *
233
	 * @param string $query
234
	 * @param string $property
235
	 * @return string
236
	 */
237
	private function getDisplayNameFromAddressBook(string $query, string $property): string {
238
		// FIXME: If we inject the contacts manager it gets initialized bofore any address books are registered
239
		$result = \OC::$server->getContactsManager()->search($query, [$property]);
240
		foreach ($result as $r) {
241
			foreach($r[$property] as $value) {
242
				if ($value === $query) {
243
					return $r['FN'];
244
				}
245
			}
246
		}
247
248
		return $query;
249
	}
250
251
	/**
252
	 * Get a specific share by id
253
	 *
254
	 * @NoAdminRequired
255
	 *
256
	 * @param string $id
257
	 * @return DataResponse
258
	 * @throws OCSNotFoundException
259
	 */
260
	public function getShare(string $id): DataResponse {
261
		try {
262
			$share = $this->getShareById($id);
263
		} catch (ShareNotFound $e) {
264
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
265
		}
266
267
		if ($this->canAccessShare($share)) {
268
			try {
269
				$share = $this->formatShare($share);
270
				return new DataResponse([$share]);
271
			} catch (NotFoundException $e) {
272
				//Fall trough
273
			}
274
		}
275
276
		throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
277
	}
278
279
	/**
280
	 * Delete a share
281
	 *
282
	 * @NoAdminRequired
283
	 *
284
	 * @param string $id
285
	 * @return DataResponse
286
	 * @throws OCSNotFoundException
287
	 */
288
	public function deleteShare(string $id): DataResponse {
289
		try {
290
			$share = $this->getShareById($id);
291
		} catch (ShareNotFound $e) {
292
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
293
		}
294
295
		try {
296
			$this->lock($share->getNode());
297
		} catch (LockedException $e) {
298
			throw new OCSNotFoundException($this->l->t('could not delete share'));
299
		}
300
301
		if (!$this->canAccessShare($share)) {
302
			throw new OCSNotFoundException($this->l->t('Could not delete share'));
303
		}
304
305
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP &&
306
			$share->getShareOwner() !== $this->currentUser &&
307
			$share->getSharedBy() !== $this->currentUser) {
308
			$this->shareManager->deleteFromSelf($share, $this->currentUser);
309
		} else {
310
			$this->shareManager->deleteShare($share);
311
		}
312
313
		return new DataResponse();
314
	}
315
316
	/**
317
	 * @NoAdminRequired
318
	 *
319
	 * @param string $path
320
	 * @param int $permissions
321
	 * @param int $shareType
322
	 * @param string $shareWith
323
	 * @param string $publicUpload
324
	 * @param string $password
325
	 * @param string $expireDate
326
	 *
327
	 * @return DataResponse
328
	 * @throws OCSNotFoundException
329
	 * @throws OCSForbiddenException
330
	 * @throws OCSBadRequestException
331
	 * @throws OCSException
332
	 *
333
	 * @suppress PhanUndeclaredClassMethod
334
	 */
335
	public function createShare(
336
		string $path = null,
337
		int $permissions = null,
338
		int $shareType = -1,
339
		string $shareWith = null,
340
		string $publicUpload = 'false',
341
		string $password = '',
342
		string $expireDate = ''
343
	): DataResponse {
344
		$share = $this->shareManager->newShare();
345
346
		if ($permissions === null) {
347
			$permissions = $this->config->getAppValue('core', 'shareapi_default_permissions', Constants::PERMISSION_ALL);
348
		}
349
350
		// Verify path
351
		if ($path === null) {
352
			throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
353
		}
354
355
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
356
		try {
357
			$path = $userFolder->get($path);
358
		} catch (NotFoundException $e) {
359
			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder doesn\'t exist'));
360
		}
361
362
		$share->setNode($path);
363
364
		try {
365
			$this->lock($share->getNode());
366
		} catch (LockedException $e) {
367
			throw new OCSNotFoundException($this->l->t('Could not create share'));
368
		}
369
370
		if ($permissions < 0 || $permissions > Constants::PERMISSION_ALL) {
371
			throw new OCSNotFoundException($this->l->t('invalid permissions'));
372
		}
373
374
		// Shares always require read permissions
375
		$permissions |= Constants::PERMISSION_READ;
376
377
		if ($path instanceof \OCP\Files\File) {
378
			// Single file shares should never have delete or create permissions
379
			$permissions &= ~Constants::PERMISSION_DELETE;
380
			$permissions &= ~Constants::PERMISSION_CREATE;
381
		}
382
383
		/*
384
		 * Hack for https://github.com/owncloud/core/issues/22587
385
		 * We check the permissions via webdav. But the permissions of the mount point
386
		 * do not equal the share permissions. Here we fix that for federated mounts.
387
		 */
388
		if ($path->getStorage()->instanceOfStorage(Storage::class)) {
389
			$permissions &= ~($permissions & ~$path->getPermissions());
390
		}
391
392
		if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
393
			// Valid user is required to share
394
			if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
395
				throw new OCSNotFoundException($this->l->t('Please specify a valid user'));
396
			}
397
			$share->setSharedWith($shareWith);
398
			$share->setPermissions($permissions);
399
		} else if ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
400
			if (!$this->shareManager->allowGroupSharing()) {
401
				throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
402
			}
403
404
			// Valid group is required to share
405
			if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
406
				throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
407
			}
408
			$share->setSharedWith($shareWith);
409
			$share->setPermissions($permissions);
410
		} else if ($shareType === \OCP\Share::SHARE_TYPE_LINK) {
411
			//Can we even share links?
412
			if (!$this->shareManager->shareApiAllowLinks()) {
413
				throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
414
			}
415
416
			/*
417
			 * For now we only allow 1 link share.
418
			 * Return the existing link share if this is a duplicate
419
			 */
420
			$existingShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_LINK, $path, false, 1, 0);
421
			if (!empty($existingShares)) {
422
				return new DataResponse($this->formatShare($existingShares[0]));
423
			}
424
425
			if ($publicUpload === 'true') {
426
				// Check if public upload is allowed
427
				if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
428
					throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
429
				}
430
431
				// Public upload can only be set for folders
432
				if ($path instanceof \OCP\Files\File) {
433
					throw new OCSNotFoundException($this->l->t('Public upload is only possible for publicly shared folders'));
434
				}
435
436
				$share->setPermissions(
437
					Constants::PERMISSION_READ |
438
					Constants::PERMISSION_CREATE |
439
					Constants::PERMISSION_UPDATE |
440
					Constants::PERMISSION_DELETE
441
				);
442
			} else {
443
				$share->setPermissions(Constants::PERMISSION_READ);
444
			}
445
446
			// Set password
447
			if ($password !== '') {
448
				$share->setPassword($password);
449
			}
450
451
			//Expire date
452
			if ($expireDate !== '') {
453
				try {
454
					$expireDate = $this->parseDate($expireDate);
455
					$share->setExpirationDate($expireDate);
456
				} catch (\Exception $e) {
457
					throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
458
				}
459
			}
460
461
		} else if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) {
462
			if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
463
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not allow shares from type %s', [$path->getPath(), $shareType]));
464
			}
465
466
			$share->setSharedWith($shareWith);
467
			$share->setPermissions($permissions);
468
		} else if ($shareType === \OCP\Share::SHARE_TYPE_EMAIL) {
469
			if ($share->getNodeType() === 'file') {
470
				$share->setPermissions(Constants::PERMISSION_READ);
471
			} else {
472
				$share->setPermissions($permissions);
473
			}
474
			$share->setSharedWith($shareWith);
475
		} else if ($shareType === \OCP\Share::SHARE_TYPE_CIRCLE) {
476
			if (!\OC::$server->getAppManager()->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
477
				throw new OCSNotFoundException($this->l->t('You cannot share to a Circle if the app is not enabled'));
478
			}
479
480
			$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($shareWith);
481
482
			// Valid circle is required to share
483
			if ($circle === null) {
484
				throw new OCSNotFoundException($this->l->t('Please specify a valid circle'));
485
			}
486
			$share->setSharedWith($shareWith);
487
			$share->setPermissions($permissions);
488
		} else {
489
			throw new OCSBadRequestException($this->l->t('Unknown share type'));
490
		}
491
492
		$share->setShareType($shareType);
493
		$share->setSharedBy($this->currentUser);
494
495
		try {
496
			$share = $this->shareManager->createShare($share);
497
		} catch (GenericShareException $e) {
498
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
499
			throw new OCSException($e->getHint(), $code);
500
		} catch (\Exception $e) {
501
			throw new OCSForbiddenException($e->getMessage(), $e);
502
		}
503
504
		$output = $this->formatShare($share);
505
506
		return new DataResponse($output);
507
	}
508
509
	/**
510
	 * @param \OCP\Files\File|\OCP\Files\Folder $node
511
	 * @param boolean $includeTags
512
	 * @return DataResponse
513
	 */
514
	private function getSharedWithMe($node = null, bool $includeTags): DataResponse {
515
516
		$userShares = $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_USER, $node, -1, 0);
517
		$groupShares = $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_GROUP, $node, -1, 0);
518
		$circleShares = $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_CIRCLE, $node, -1, 0);
519
520
		$shares = array_merge($userShares, $groupShares, $circleShares);
521
522
		$shares = array_filter($shares, function (IShare $share) {
523
			return $share->getShareOwner() !== $this->currentUser;
524
		});
525
526
		$formatted = [];
527
		foreach ($shares as $share) {
528
			if ($this->canAccessShare($share)) {
529
				try {
530
					$formatted[] = $this->formatShare($share);
531
				} catch (NotFoundException $e) {
532
					// Ignore this share
533
				}
534
			}
535
		}
536
537
		if ($includeTags) {
538
			$formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
539
		}
540
541
		return new DataResponse($formatted);
542
	}
543
544
	/**
545
	 * @param \OCP\Files\Folder $folder
546
	 * @return DataResponse
547
	 * @throws OCSBadRequestException
548
	 */
549
	private function getSharesInDir(Node $folder): DataResponse {
550
		if (!($folder instanceof \OCP\Files\Folder)) {
551
			throw new OCSBadRequestException($this->l->t('Not a directory'));
552
		}
553
554
		$nodes = $folder->getDirectoryListing();
555
		/** @var \OCP\Share\IShare[] $shares */
556
		$shares = [];
557
		foreach ($nodes as $node) {
558
			$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_USER, $node, false, -1, 0));
559
			$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_GROUP, $node, false, -1, 0));
560
			$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_LINK, $node, false, -1, 0));
561
			if($this->shareManager->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
562
				$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_EMAIL, $node, false, -1, 0));
563
			}
564 View Code Duplication
			if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
565
				$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_REMOTE, $node, false, -1, 0));
566
			}
567
		}
568
569
		$formatted = [];
570
		foreach ($shares as $share) {
571
			try {
572
				$formatted[] = $this->formatShare($share);
573
			} catch (NotFoundException $e) {
574
				//Ignore this share
575
			}
576
		}
577
578
		return new DataResponse($formatted);
579
	}
580
581
	/**
582
	 * The getShares function.
583
	 *
584
	 * @NoAdminRequired
585
	 *
586
	 * @param string $shared_with_me
587
	 * @param string $reshares
588
	 * @param string $subfiles
589
	 * @param string $path
590
	 *
591
	 * - Get shares by the current user
592
	 * - Get shares by the current user and reshares (?reshares=true)
593
	 * - Get shares with the current user (?shared_with_me=true)
594
	 * - Get shares for a specific path (?path=...)
595
	 * - Get all shares in a folder (?subfiles=true&path=..)
596
	 *
597
	 * @return DataResponse
598
	 * @throws OCSNotFoundException
599
	 */
600
	public function getShares(
601
		string $shared_with_me = 'false',
602
		string $reshares = 'false',
603
		string $subfiles = 'false',
604
		string $path = null,
605
		string $include_tags = 'false'
606
	): DataResponse {
607
608
		if ($path !== null) {
609
			$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
610
			try {
611
				$path = $userFolder->get($path);
612
				$this->lock($path);
613
			} catch (\OCP\Files\NotFoundException $e) {
614
				throw new OCSNotFoundException($this->l->t('Wrong path, file/folder doesn\'t exist'));
615
			} catch (LockedException $e) {
616
				throw new OCSNotFoundException($this->l->t('Could not lock path'));
617
			}
618
		}
619
620
		$include_tags = $include_tags === 'true';
621
622
		if ($shared_with_me === 'true') {
623
			$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...
624
			return $result;
625
		}
626
627
		if ($subfiles === 'true') {
628
			$result = $this->getSharesInDir($path);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 604 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...
629
			return $result;
630
		}
631
632
		if ($reshares === 'true') {
633
			$reshares = true;
634
		} else {
635
			$reshares = false;
636
		}
637
638
		// Get all shares
639
		$userShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_USER, $path, $reshares, -1, 0);
640
		$groupShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_GROUP, $path, $reshares, -1, 0);
641
		$linkShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_LINK, $path, $reshares, -1, 0);
642 View Code Duplication
		if ($this->shareManager->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
643
			$mailShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_EMAIL, $path, $reshares, -1, 0);
644
		} else {
645
			$mailShares = [];
646
		}
647 View Code Duplication
		if ($this->shareManager->shareProviderExists(\OCP\Share::SHARE_TYPE_CIRCLE)) {
648
			$circleShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_CIRCLE, $path, $reshares, -1, 0);
649
		} else {
650
			$circleShares = [];
651
		}
652
653
		$shares = array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares);
654
655 View Code Duplication
		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
656
			$federatedShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_REMOTE, $path, $reshares, -1, 0);
657
			$shares = array_merge($shares, $federatedShares);
658
		}
659
660
		$formatted = [];
661
		foreach ($shares as $share) {
662
			try {
663
				$formatted[] = $this->formatShare($share, $path);
664
			} catch (NotFoundException $e) {
665
				//Ignore share
666
			}
667
		}
668
669
		if ($include_tags) {
670
			$formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
671
		}
672
673
		return new DataResponse($formatted);
674
	}
675
676
	/**
677
	 * @NoAdminRequired
678
	 *
679
	 * @param string $id
680
	 * @param int $permissions
681
	 * @param string $password
682
	 * @param string $publicUpload
683
	 * @param string $expireDate
684
	 * @return DataResponse
685
	 * @throws OCSNotFoundException
686
	 * @throws OCSBadRequestException
687
	 * @throws OCSForbiddenException
688
	 */
689
	public function updateShare(
690
		string $id,
691
		int $permissions = null,
692
		string $password = null,
693
		string $publicUpload = null,
694
		string $expireDate = null
695
	): DataResponse {
696
		try {
697
			$share = $this->getShareById($id);
698
		} catch (ShareNotFound $e) {
699
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
700
		}
701
702
		$this->lock($share->getNode());
703
704
		if (!$this->canAccessShare($share, false)) {
705
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
706
		}
707
708
		if ($permissions === null && $password === null && $publicUpload === null && $expireDate === null) {
709
			throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
710
		}
711
712
		/*
713
		 * expirationdate, password and publicUpload only make sense for link shares
714
		 */
715
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
716
717
			$newPermissions = null;
718
			if ($publicUpload === 'true') {
719
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
720
			} else if ($publicUpload === 'false') {
721
				$newPermissions = Constants::PERMISSION_READ;
722
			}
723
724
			if ($permissions !== null) {
725
				$newPermissions = (int)$permissions;
726
				$newPermissions = $newPermissions & ~Constants::PERMISSION_SHARE;
727
			}
728
729
			if ($newPermissions !== null &&
730
				!in_array($newPermissions, [
731
					Constants::PERMISSION_READ,
732
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE, // legacy
733
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE, // correct
734
					Constants::PERMISSION_CREATE, // hidden file list
735
					Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE, // allow to edit single files
736
				], true)
737
			) {
738
				throw new OCSBadRequestException($this->l->t('Can\'t change permissions for public share links'));
739
			}
740
741
			if (
742
				// legacy
743
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
744
				// correct
745
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
746
			) {
747
				if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
748
					throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
749
				}
750
751
				if (!($share->getNode() instanceof \OCP\Files\Folder)) {
752
					throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
753
				}
754
755
				// normalize to correct public upload permissions
756
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
757
			}
758
759
			if ($newPermissions !== null) {
760
				$share->setPermissions($newPermissions);
761
				$permissions = $newPermissions;
762
			}
763
764 View Code Duplication
			if ($expireDate === '') {
765
				$share->setExpirationDate(null);
766
			} else if ($expireDate !== null) {
767
				try {
768
					$expireDate = $this->parseDate($expireDate);
769
				} catch (\Exception $e) {
770
					throw new OCSBadRequestException($e->getMessage(), $e);
771
				}
772
				$share->setExpirationDate($expireDate);
773
			}
774
775 View Code Duplication
			if ($password === '') {
776
				$share->setPassword(null);
777
			} else if ($password !== null) {
778
				$share->setPassword($password);
779
			}
780
781
		} else {
782
			if ($permissions !== null) {
783
				$permissions = (int)$permissions;
784
				$share->setPermissions($permissions);
785
			}
786
787
			if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
788 View Code Duplication
				if ($password === '') {
789
					$share->setPassword(null);
790
				} else if ($password !== null) {
791
					$share->setPassword($password);
792
				}
793
			}
794
795 View Code Duplication
			if ($expireDate === '') {
796
				$share->setExpirationDate(null);
797
			} else if ($expireDate !== null) {
798
				try {
799
					$expireDate = $this->parseDate($expireDate);
800
				} catch (\Exception $e) {
801
					throw new OCSBadRequestException($e->getMessage(), $e);
802
				}
803
				$share->setExpirationDate($expireDate);
804
			}
805
806
		}
807
808
		if ($permissions !== null && $share->getShareOwner() !== $this->currentUser) {
809
			/* Check if this is an incomming share */
810
			$incomingShares = $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_USER, $share->getNode(), -1, 0);
811
			$incomingShares = array_merge($incomingShares, $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_GROUP, $share->getNode(), -1, 0));
812
813
			/** @var \OCP\Share\IShare[] $incomingShares */
814
			if (!empty($incomingShares)) {
815
				$maxPermissions = 0;
816
				foreach ($incomingShares as $incomingShare) {
817
					$maxPermissions |= $incomingShare->getPermissions();
818
				}
819
820
				if ($share->getPermissions() & ~$maxPermissions) {
821
					throw new OCSNotFoundException($this->l->t('Cannot increase permissions'));
822
				}
823
			}
824
		}
825
826
827
		try {
828
			$share = $this->shareManager->updateShare($share);
829
		} catch (\Exception $e) {
830
			throw new OCSBadRequestException($e->getMessage(), $e);
831
		}
832
833
		return new DataResponse($this->formatShare($share));
834
	}
835
836
	protected function canAccessShare(\OCP\Share\IShare $share, bool $checkGroups = true): bool {
837
		// A file with permissions 0 can't be accessed by us. So Don't show it
838
		if ($share->getPermissions() === 0) {
839
			return false;
840
		}
841
842
		// Owner of the file and the sharer of the file can always get share
843
		if ($share->getShareOwner() === $this->currentUser ||
844
			$share->getSharedBy() === $this->currentUser
845
		) {
846
			return true;
847
		}
848
849
		// If the share is shared with you (or a group you are a member of)
850
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
851
			$share->getSharedWith() === $this->currentUser
852
		) {
853
			return true;
854
		}
855
856
		if ($checkGroups && $share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
857
			$sharedWith = $this->groupManager->get($share->getSharedWith());
858
			$user = $this->userManager->get($this->currentUser);
859
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
860
				return true;
861
			}
862
		}
863
864
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_CIRCLE) {
865
			// TODO: have a sanity check like above?
866
			return true;
867
		}
868
869
		return false;
870
	}
871
872
	/**
873
	 * Make sure that the passed date is valid ISO 8601
874
	 * So YYYY-MM-DD
875
	 * If not throw an exception
876
	 *
877
	 * @param string $expireDate
878
	 *
879
	 * @throws \Exception
880
	 * @return \DateTime
881
	 */
882
	private function parseDate(string $expireDate): \DateTime {
883
		try {
884
			$date = new \DateTime($expireDate);
885
		} catch (\Exception $e) {
886
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
887
		}
888
889
		if ($date === false) {
890
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
891
		}
892
893
		$date->setTime(0, 0, 0);
894
895
		return $date;
896
	}
897
898
	/**
899
	 * Since we have multiple providers but the OCS Share API v1 does
900
	 * not support this we need to check all backends.
901
	 *
902
	 * @param string $id
903
	 * @return \OCP\Share\IShare
904
	 * @throws ShareNotFound
905
	 */
906
	private function getShareById(string $id): IShare {
907
		$share = null;
908
909
		// First check if it is an internal share.
910
		try {
911
			$share = $this->shareManager->getShareById('ocinternal:' . $id);
912
			return $share;
913
		} catch (ShareNotFound $e) {
914
			// Do nothing, just try the other share type
915
		}
916
917
918
		try {
919 View Code Duplication
			if ($this->shareManager->shareProviderExists(\OCP\Share::SHARE_TYPE_CIRCLE)) {
920
				$share = $this->shareManager->getShareById('ocCircleShare:' . $id);
921
				return $share;
922
			}
923
		} catch (ShareNotFound $e) {
924
			// Do nothing, just try the other share type
925
		}
926
927
		try {
928 View Code Duplication
			if ($this->shareManager->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
929
				$share = $this->shareManager->getShareById('ocMailShare:' . $id);
930
				return $share;
931
			}
932
		} catch (ShareNotFound $e) {
933
			// Do nothing, just try the other share type
934
		}
935
936
		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
937
			throw new ShareNotFound();
938
		}
939
		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id);
940
941
		return $share;
942
	}
943
944
	/**
945
	 * Lock a Node
946
	 *
947
	 * @param \OCP\Files\Node $node
948
	 * @throws LockedException
949
	 */
950
	private function lock(\OCP\Files\Node $node) {
951
		$node->lock(ILockingProvider::LOCK_SHARED);
952
		$this->lockedNode = $node;
953
	}
954
955
	/**
956
	 * Cleanup the remaining locks
957
	 * @throws @LockedException
958
	 */
959
	public function cleanup() {
960
		if ($this->lockedNode !== null) {
961
			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
962
		}
963
	}
964
}
965