Completed
Push — master ( a89f9a...3036b1 )
by Morris
29:19 queued 12:13
created

ShareAPIController::getShares()   F

Complexity

Conditions 14
Paths 392

Size

Total Lines 80

Duplication

Lines 18
Ratio 22.5 %

Importance

Changes 0
Metric Value
cc 14
nc 392
nop 5
dl 18
loc 80
rs 2.863
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;
52
use OCP\Share\IManager;
53
use OCP\Share\Exceptions\ShareNotFound;
54
use OCP\Share\Exceptions\GenericShareException;
55
use OCP\Lock\ILockingProvider;
56
use OCP\Share\IShare;
57
use OCA\Files_Sharing\External\Storage;
58
59
/**
60
 * Class Share20OCS
61
 *
62
 * @package OCA\Files_Sharing\API
63
 */
64
class ShareAPIController extends OCSController {
65
66
	/** @var IManager */
67
	private $shareManager;
68
	/** @var IGroupManager */
69
	private $groupManager;
70
	/** @var IUserManager */
71
	private $userManager;
72
	/** @var IRootFolder */
73
	private $rootFolder;
74
	/** @var IURLGenerator */
75
	private $urlGenerator;
76
	/** @var string */
77
	private $currentUser;
78
	/** @var IL10N */
79
	private $l;
80
	/** @var \OCP\Files\Node */
81
	private $lockedNode;
82
	/** @var IConfig */
83
	private $config;
84
85
	/**
86
	 * Share20OCS constructor.
87
	 *
88
	 * @param string $appName
89
	 * @param IRequest $request
90
	 * @param IManager $shareManager
91
	 * @param IGroupManager $groupManager
92
	 * @param IUserManager $userManager
93
	 * @param IRootFolder $rootFolder
94
	 * @param IURLGenerator $urlGenerator
95
	 * @param string $userId
96
	 * @param IL10N $l10n
97
	 * @param IConfig $config
98
	 */
99 View Code Duplication
	public function __construct(
100
		string $appName,
101
		IRequest $request,
102
		IManager $shareManager,
103
		IGroupManager $groupManager,
104
		IUserManager $userManager,
105
		IRootFolder $rootFolder,
106
		IURLGenerator $urlGenerator,
107
		string $userId,
108
		IL10N $l10n,
109
		IConfig $config
110
	) {
111
		parent::__construct($appName, $request);
112
113
		$this->shareManager = $shareManager;
114
		$this->userManager = $userManager;
115
		$this->groupManager = $groupManager;
116
		$this->request = $request;
117
		$this->rootFolder = $rootFolder;
118
		$this->urlGenerator = $urlGenerator;
119
		$this->currentUser = $userId;
120
		$this->l = $l10n;
121
		$this->config = $config;
122
	}
123
124
	/**
125
	 * Convert an IShare to an array for OCS output
126
	 *
127
	 * @param \OCP\Share\IShare $share
128
	 * @param Node|null $recipientNode
129
	 * @return array
130
	 * @throws NotFoundException In case the node can't be resolved.
131
	 */
132
	protected function formatShare(\OCP\Share\IShare $share, Node $recipientNode = null): array {
133
		$sharedBy = $this->userManager->get($share->getSharedBy());
134
		$shareOwner = $this->userManager->get($share->getShareOwner());
135
136
		$result = [
137
			'id' => $share->getId(),
138
			'share_type' => $share->getShareType(),
139
			'uid_owner' => $share->getSharedBy(),
140
			'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
141
			'permissions' => $share->getPermissions(),
142
			'stime' => $share->getShareTime()->getTimestamp(),
143
			'parent' => null,
144
			'expiration' => null,
145
			'token' => null,
146
			'uid_file_owner' => $share->getShareOwner(),
147
			'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
148
		];
149
150
		$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
151
		if ($recipientNode) {
152
			$node = $recipientNode;
153
		} else {
154
			$nodes = $userFolder->getById($share->getNodeId());
155 View Code Duplication
			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() === 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() === 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() === 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() === Share::SHARE_TYPE_REMOTE || $share->getShareType() || Share::SHARE_TYPE_REMOTE_GROUP) {
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() === 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() === 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() === 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 === 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 === 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 === 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, 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 === Share::SHARE_TYPE_REMOTE) {
462 View Code Duplication
			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 === Share::SHARE_TYPE_REMOTE_GROUP) {
469 View Code Duplication
			if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
470
				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not allow shares from type %s', [$path->getPath(), $shareType]));
471
			}
472
473
			$share->setSharedWith($shareWith);
474
			$share->setPermissions($permissions);
475
		} else if ($shareType === Share::SHARE_TYPE_EMAIL) {
476
			if ($share->getNodeType() === 'file') {
477
				$share->setPermissions(Constants::PERMISSION_READ);
478
			} else {
479
				$share->setPermissions($permissions);
480
			}
481
			$share->setSharedWith($shareWith);
482
		} else if ($shareType === Share::SHARE_TYPE_CIRCLE) {
483
			if (!\OC::$server->getAppManager()->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
484
				throw new OCSNotFoundException($this->l->t('You cannot share to a Circle if the app is not enabled'));
485
			}
486
487
			$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($shareWith);
488
489
			// Valid circle is required to share
490
			if ($circle === null) {
491
				throw new OCSNotFoundException($this->l->t('Please specify a valid circle'));
492
			}
493
			$share->setSharedWith($shareWith);
494
			$share->setPermissions($permissions);
495
		} else {
496
			throw new OCSBadRequestException($this->l->t('Unknown share type'));
497
		}
498
499
		$share->setShareType($shareType);
500
		$share->setSharedBy($this->currentUser);
501
502
		try {
503
			$share = $this->shareManager->createShare($share);
504
		} catch (GenericShareException $e) {
505
			$code = $e->getCode() === 0 ? 403 : $e->getCode();
506
			throw new OCSException($e->getHint(), $code);
507
		} catch (\Exception $e) {
508
			throw new OCSForbiddenException($e->getMessage(), $e);
509
		}
510
511
		$output = $this->formatShare($share);
512
513
		return new DataResponse($output);
514
	}
515
516
	/**
517
	 * @param \OCP\Files\File|\OCP\Files\Folder $node
518
	 * @param boolean $includeTags
519
	 * @return DataResponse
520
	 */
521
	private function getSharedWithMe($node = null, bool $includeTags): DataResponse {
522
523
		$userShares = $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_USER, $node, -1, 0);
524
		$groupShares = $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_GROUP, $node, -1, 0);
525
		$circleShares = $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_CIRCLE, $node, -1, 0);
526
527
		$shares = array_merge($userShares, $groupShares, $circleShares);
528
529
		$shares = array_filter($shares, function (IShare $share) {
530
			return $share->getShareOwner() !== $this->currentUser;
531
		});
532
533
		$formatted = [];
534
		foreach ($shares as $share) {
535
			if ($this->canAccessShare($share)) {
536
				try {
537
					$formatted[] = $this->formatShare($share);
538
				} catch (NotFoundException $e) {
539
					// Ignore this share
540
				}
541
			}
542
		}
543
544
		if ($includeTags) {
545
			$formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
546
		}
547
548
		return new DataResponse($formatted);
549
	}
550
551
	/**
552
	 * @param \OCP\Files\Folder $folder
553
	 * @return DataResponse
554
	 * @throws OCSBadRequestException
555
	 */
556
	private function getSharesInDir(Node $folder): DataResponse {
557
		if (!($folder instanceof \OCP\Files\Folder)) {
558
			throw new OCSBadRequestException($this->l->t('Not a directory'));
559
		}
560
561
		$nodes = $folder->getDirectoryListing();
562
		/** @var \OCP\Share\IShare[] $shares */
563
		$shares = [];
564
		foreach ($nodes as $node) {
565
			$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_USER, $node, false, -1, 0));
566
			$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_GROUP, $node, false, -1, 0));
567
			$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_LINK, $node, false, -1, 0));
568
			if($this->shareManager->shareProviderExists(Share::SHARE_TYPE_EMAIL)) {
569
				$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_EMAIL, $node, false, -1, 0));
570
			}
571 View Code Duplication
			if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
572
				$shares = array_merge($shares, $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_REMOTE, $node, false, -1, 0));
573
			}
574
		}
575
576
		$formatted = [];
577
		foreach ($shares as $share) {
578
			try {
579
				$formatted[] = $this->formatShare($share);
580
			} catch (NotFoundException $e) {
581
				//Ignore this share
582
			}
583
		}
584
585
		return new DataResponse($formatted);
586
	}
587
588
	/**
589
	 * The getShares function.
590
	 *
591
	 * @NoAdminRequired
592
	 *
593
	 * @param string $shared_with_me
594
	 * @param string $reshares
595
	 * @param string $subfiles
596
	 * @param string $path
597
	 *
598
	 * - Get shares by the current user
599
	 * - Get shares by the current user and reshares (?reshares=true)
600
	 * - Get shares with the current user (?shared_with_me=true)
601
	 * - Get shares for a specific path (?path=...)
602
	 * - Get all shares in a folder (?subfiles=true&path=..)
603
	 *
604
	 * @return DataResponse
605
	 * @throws OCSNotFoundException
606
	 */
607
	public function getShares(
608
		string $shared_with_me = 'false',
609
		string $reshares = 'false',
610
		string $subfiles = 'false',
611
		string $path = null,
612
		string $include_tags = 'false'
613
	): DataResponse {
614
615
		if ($path !== null) {
616
			$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
617
			try {
618
				$path = $userFolder->get($path);
619
				$this->lock($path);
620
			} catch (\OCP\Files\NotFoundException $e) {
621
				throw new OCSNotFoundException($this->l->t('Wrong path, file/folder doesn\'t exist'));
622
			} catch (LockedException $e) {
623
				throw new OCSNotFoundException($this->l->t('Could not lock path'));
624
			}
625
		}
626
627
		$include_tags = $include_tags === 'true';
628
629
		if ($shared_with_me === 'true') {
630
			$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...
631
			return $result;
632
		}
633
634
		if ($subfiles === 'true') {
635
			$result = $this->getSharesInDir($path);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 611 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...
636
			return $result;
637
		}
638
639
		if ($reshares === 'true') {
640
			$reshares = true;
641
		} else {
642
			$reshares = false;
643
		}
644
645
		// Get all shares
646
		$userShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_USER, $path, $reshares, -1, 0);
647
		$groupShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_GROUP, $path, $reshares, -1, 0);
648
		$linkShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_LINK, $path, $reshares, -1, 0);
649 View Code Duplication
		if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_EMAIL)) {
650
			$mailShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_EMAIL, $path, $reshares, -1, 0);
651
		} else {
652
			$mailShares = [];
653
		}
654 View Code Duplication
		if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_CIRCLE)) {
655
			$circleShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_CIRCLE, $path, $reshares, -1, 0);
656
		} else {
657
			$circleShares = [];
658
		}
659
660
		$shares = array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares);
661
662 View Code Duplication
		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
663
			$federatedShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_REMOTE, $path, $reshares, -1, 0);
664
			$shares = array_merge($shares, $federatedShares);
665
		}
666
667 View Code Duplication
		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
668
			$federatedShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
669
			$shares = array_merge($shares, $federatedShares);
670
		}
671
672
		$formatted = [];
673
		foreach ($shares as $share) {
674
			try {
675
				$formatted[] = $this->formatShare($share, $path);
676
			} catch (NotFoundException $e) {
677
				//Ignore share
678
			}
679
		}
680
681
		if ($include_tags) {
682
			$formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
683
		}
684
685
		return new DataResponse($formatted);
686
	}
687
688
	/**
689
	 * @NoAdminRequired
690
	 *
691
	 * @param string $id
692
	 * @param int $permissions
693
	 * @param string $password
694
	 * @param string $publicUpload
695
	 * @param string $expireDate
696
	 * @return DataResponse
697
	 * @throws OCSNotFoundException
698
	 * @throws OCSBadRequestException
699
	 * @throws OCSForbiddenException
700
	 */
701
	public function updateShare(
702
		string $id,
703
		int $permissions = null,
704
		string $password = null,
705
		string $publicUpload = null,
706
		string $expireDate = null
707
	): DataResponse {
708
		try {
709
			$share = $this->getShareById($id);
710
		} catch (ShareNotFound $e) {
711
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
712
		}
713
714
		$this->lock($share->getNode());
715
716
		if (!$this->canAccessShare($share, false)) {
717
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
718
		}
719
720
		if ($permissions === null && $password === null && $publicUpload === null && $expireDate === null) {
721
			throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
722
		}
723
724
		/*
725
		 * expirationdate, password and publicUpload only make sense for link shares
726
		 */
727
		if ($share->getShareType() === Share::SHARE_TYPE_LINK) {
728
729
			$newPermissions = null;
730
			if ($publicUpload === 'true') {
731
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
732
			} else if ($publicUpload === 'false') {
733
				$newPermissions = Constants::PERMISSION_READ;
734
			}
735
736
			if ($permissions !== null) {
737
				$newPermissions = (int)$permissions;
738
				$newPermissions = $newPermissions & ~Constants::PERMISSION_SHARE;
739
			}
740
741
			if ($newPermissions !== null &&
742
				!in_array($newPermissions, [
743
					Constants::PERMISSION_READ,
744
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE, // legacy
745
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE, // correct
746
					Constants::PERMISSION_CREATE, // hidden file list
747
					Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE, // allow to edit single files
748
				], true)
749
			) {
750
				throw new OCSBadRequestException($this->l->t('Can\'t change permissions for public share links'));
751
			}
752
753
			if (
754
				// legacy
755
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
756
				// correct
757
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
758
			) {
759
				if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
760
					throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
761
				}
762
763
				if (!($share->getNode() instanceof \OCP\Files\Folder)) {
764
					throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
765
				}
766
767
				// normalize to correct public upload permissions
768
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
769
			}
770
771
			if ($newPermissions !== null) {
772
				$share->setPermissions($newPermissions);
773
				$permissions = $newPermissions;
774
			}
775
776 View Code Duplication
			if ($expireDate === '') {
777
				$share->setExpirationDate(null);
778
			} else if ($expireDate !== null) {
779
				try {
780
					$expireDate = $this->parseDate($expireDate);
781
				} catch (\Exception $e) {
782
					throw new OCSBadRequestException($e->getMessage(), $e);
783
				}
784
				$share->setExpirationDate($expireDate);
785
			}
786
787 View Code Duplication
			if ($password === '') {
788
				$share->setPassword(null);
789
			} else if ($password !== null) {
790
				$share->setPassword($password);
791
			}
792
793
		} else {
794
			if ($permissions !== null) {
795
				$permissions = (int)$permissions;
796
				$share->setPermissions($permissions);
797
			}
798
799
			if ($share->getShareType() === Share::SHARE_TYPE_EMAIL) {
800 View Code Duplication
				if ($password === '') {
801
					$share->setPassword(null);
802
				} else if ($password !== null) {
803
					$share->setPassword($password);
804
				}
805
			}
806
807 View Code Duplication
			if ($expireDate === '') {
808
				$share->setExpirationDate(null);
809
			} else if ($expireDate !== null) {
810
				try {
811
					$expireDate = $this->parseDate($expireDate);
812
				} catch (\Exception $e) {
813
					throw new OCSBadRequestException($e->getMessage(), $e);
814
				}
815
				$share->setExpirationDate($expireDate);
816
			}
817
818
		}
819
820
		if ($permissions !== null && $share->getShareOwner() !== $this->currentUser) {
821
			/* Check if this is an incomming share */
822
			$incomingShares = $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_USER, $share->getNode(), -1, 0);
823
			$incomingShares = array_merge($incomingShares, $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_GROUP, $share->getNode(), -1, 0));
824
825
			/** @var \OCP\Share\IShare[] $incomingShares */
826
			if (!empty($incomingShares)) {
827
				$maxPermissions = 0;
828
				foreach ($incomingShares as $incomingShare) {
829
					$maxPermissions |= $incomingShare->getPermissions();
830
				}
831
832
				if ($share->getPermissions() & ~$maxPermissions) {
833
					throw new OCSNotFoundException($this->l->t('Cannot increase permissions'));
834
				}
835
			}
836
		}
837
838
839
		try {
840
			$share = $this->shareManager->updateShare($share);
841
		} catch (\Exception $e) {
842
			throw new OCSBadRequestException($e->getMessage(), $e);
843
		}
844
845
		return new DataResponse($this->formatShare($share));
846
	}
847
848
	protected function canAccessShare(\OCP\Share\IShare $share, bool $checkGroups = true): bool {
849
		// A file with permissions 0 can't be accessed by us. So Don't show it
850
		if ($share->getPermissions() === 0) {
851
			return false;
852
		}
853
854
		// Owner of the file and the sharer of the file can always get share
855
		if ($share->getShareOwner() === $this->currentUser ||
856
			$share->getSharedBy() === $this->currentUser
857
		) {
858
			return true;
859
		}
860
861
		// If the share is shared with you (or a group you are a member of)
862
		if ($share->getShareType() === Share::SHARE_TYPE_USER &&
863
			$share->getSharedWith() === $this->currentUser
864
		) {
865
			return true;
866
		}
867
868
		if ($checkGroups && $share->getShareType() === Share::SHARE_TYPE_GROUP) {
869
			$sharedWith = $this->groupManager->get($share->getSharedWith());
870
			$user = $this->userManager->get($this->currentUser);
871
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
872
				return true;
873
			}
874
		}
875
876
		if ($share->getShareType() === Share::SHARE_TYPE_CIRCLE) {
877
			// TODO: have a sanity check like above?
878
			return true;
879
		}
880
881
		return false;
882
	}
883
884
	/**
885
	 * Make sure that the passed date is valid ISO 8601
886
	 * So YYYY-MM-DD
887
	 * If not throw an exception
888
	 *
889
	 * @param string $expireDate
890
	 *
891
	 * @throws \Exception
892
	 * @return \DateTime
893
	 */
894
	private function parseDate(string $expireDate): \DateTime {
895
		try {
896
			$date = new \DateTime($expireDate);
897
		} catch (\Exception $e) {
898
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
899
		}
900
901
		if ($date === false) {
902
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
903
		}
904
905
		$date->setTime(0, 0, 0);
906
907
		return $date;
908
	}
909
910
	/**
911
	 * Since we have multiple providers but the OCS Share API v1 does
912
	 * not support this we need to check all backends.
913
	 *
914
	 * @param string $id
915
	 * @return \OCP\Share\IShare
916
	 * @throws ShareNotFound
917
	 */
918
	private function getShareById(string $id): IShare {
919
		$share = null;
920
921
		// First check if it is an internal share.
922
		try {
923
			$share = $this->shareManager->getShareById('ocinternal:' . $id, $this->currentUser);
924
			return $share;
925
		} catch (ShareNotFound $e) {
926
			// Do nothing, just try the other share type
927
		}
928
929
930
		try {
931 View Code Duplication
			if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_CIRCLE)) {
932
				$share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->currentUser);
933
				return $share;
934
			}
935
		} catch (ShareNotFound $e) {
936
			// Do nothing, just try the other share type
937
		}
938
939
		try {
940 View Code Duplication
			if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_EMAIL)) {
941
				$share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->currentUser);
942
				return $share;
943
			}
944
		} catch (ShareNotFound $e) {
945
			// Do nothing, just try the other share type
946
		}
947
948
		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
949
			throw new ShareNotFound();
950
		}
951
		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->currentUser);
952
953
		return $share;
954
	}
955
956
	/**
957
	 * Lock a Node
958
	 *
959
	 * @param \OCP\Files\Node $node
960
	 * @throws LockedException
961
	 */
962
	private function lock(\OCP\Files\Node $node) {
963
		$node->lock(ILockingProvider::LOCK_SHARED);
964
		$this->lockedNode = $node;
965
	}
966
967
	/**
968
	 * Cleanup the remaining locks
969
	 * @throws @LockedException
970
	 */
971
	public function cleanup() {
972
		if ($this->lockedNode !== null) {
973
			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
974
		}
975
	}
976
}
977