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

ShareAPIController::getRoomShareHelper()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

    return array();
}

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

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

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

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

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

function notNullable(stdClass $x) { }

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

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

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