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

ShareAPIController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24

Duplication

Lines 24
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 10
dl 24
loc 24
rs 9.536
c 0
b 0
f 0

How to fix   Many Parameters   

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\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