Completed
Push — master ( 01f420...7bc3c2 )
by Morris
99:18 queued 80:12
created

ShareAPIController::cleanup()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
613
			return $result;
614
		}
615
616
		if ($subfiles === 'true') {
617
			$result = $this->getSharesInDir($path);
0 ignored issues
show
Documentation introduced by
$path is of type object<OCP\Files\Node>|null, but the function expects a object<OCP\Files\Folder>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
618
			return $result;
619
		}
620
621
		if ($reshares === 'true') {
622
			$reshares = true;
623
		} else {
624
			$reshares = false;
625
		}
626
627
		// Get all shares
628
		$userShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_USER, $path, $reshares, -1, 0);
629
		$groupShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_GROUP, $path, $reshares, -1, 0);
630
		$linkShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_LINK, $path, $reshares, -1, 0);
631 View Code Duplication
		if ($this->shareManager->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
632
			$mailShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_EMAIL, $path, $reshares, -1, 0);
633
		} else {
634
			$mailShares = [];
635
		}
636 View Code Duplication
		if ($this->shareManager->shareProviderExists(\OCP\Share::SHARE_TYPE_CIRCLE)) {
637
			$circleShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_CIRCLE, $path, $reshares, -1, 0);
638
		} else {
639
			$circleShares = [];
640
		}
641
642
		$shares = array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares);
643
644 View Code Duplication
		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
645
			$federatedShares = $this->shareManager->getSharesBy($this->currentUser, \OCP\Share::SHARE_TYPE_REMOTE, $path, $reshares, -1, 0);
646
			$shares = array_merge($shares, $federatedShares);
647
		}
648
649
		$formatted = [];
650
		foreach ($shares as $share) {
651
			try {
652
				$formatted[] = $this->formatShare($share, $path);
653
			} catch (NotFoundException $e) {
654
				//Ignore share
655
			}
656
		}
657
658
		if ($include_tags) {
659
			$formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
660
		}
661
662
		return new DataResponse($formatted);
663
	}
664
665
	/**
666
	 * @NoAdminRequired
667
	 *
668
	 * @param int $id
669
	 * @param int $permissions
670
	 * @param string $password
671
	 * @param string $publicUpload
672
	 * @param string $expireDate
673
	 * @return DataResponse
674
	 * @throws OCSNotFoundException
675
	 * @throws OCSBadRequestException
676
	 * @throws OCSForbiddenException
677
	 */
678
	public function updateShare(
679
		$id,
680
		$permissions = null,
681
		$password = null,
682
		$publicUpload = null,
683
		$expireDate = null
684
	) {
685
		try {
686
			$share = $this->getShareById($id);
687
		} catch (ShareNotFound $e) {
688
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
689
		}
690
691
		$this->lock($share->getNode());
692
693
		if (!$this->canAccessShare($share, false)) {
694
			throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
695
		}
696
697
		if ($permissions === null && $password === null && $publicUpload === null && $expireDate === null) {
698
			throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
699
		}
700
701
		/*
702
		 * expirationdate, password and publicUpload only make sense for link shares
703
		 */
704
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
705
706
			$newPermissions = null;
707
			if ($publicUpload === 'true') {
708
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
709
			} else if ($publicUpload === 'false') {
710
				$newPermissions = Constants::PERMISSION_READ;
711
			}
712
713
			if ($permissions !== null) {
714
				$newPermissions = (int)$permissions;
715
				$newPermissions = $newPermissions & ~Constants::PERMISSION_SHARE;
716
			}
717
718
			if ($newPermissions !== null &&
719
				!in_array($newPermissions, [
720
					Constants::PERMISSION_READ,
721
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE, // legacy
722
					Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE, // correct
723
					Constants::PERMISSION_CREATE, // hidden file list
724
					Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE, // allow to edit single files
725
				])
726
			) {
727
				throw new OCSBadRequestException($this->l->t('Can\'t change permissions for public share links'));
728
			}
729
730
			if (
731
				// legacy
732
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
733
				// correct
734
				$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
735
			) {
736
				if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
737
					throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
738
				}
739
740
				if (!($share->getNode() instanceof \OCP\Files\Folder)) {
741
					throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
742
				}
743
744
				// normalize to correct public upload permissions
745
				$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
746
			}
747
748
			if ($newPermissions !== null) {
749
				$share->setPermissions($newPermissions);
750
				$permissions = $newPermissions;
751
			}
752
753 View Code Duplication
			if ($expireDate === '') {
754
				$share->setExpirationDate(null);
755
			} else if ($expireDate !== null) {
756
				try {
757
					$expireDate = $this->parseDate($expireDate);
758
				} catch (\Exception $e) {
759
					throw new OCSBadRequestException($e->getMessage(), $e);
760
				}
761
				$share->setExpirationDate($expireDate);
762
			}
763
764 View Code Duplication
			if ($password === '') {
765
				$share->setPassword(null);
766
			} else if ($password !== null) {
767
				$share->setPassword($password);
768
			}
769
770
		} else {
771
			if ($permissions !== null) {
772
				$permissions = (int)$permissions;
773
				$share->setPermissions($permissions);
774
			}
775
776
			if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
777 View Code Duplication
				if ($password === '') {
778
					$share->setPassword(null);
779
				} else if ($password !== null) {
780
					$share->setPassword($password);
781
				}
782
			}
783
784 View Code Duplication
			if ($expireDate === '') {
785
				$share->setExpirationDate(null);
786
			} else if ($expireDate !== null) {
787
				try {
788
					$expireDate = $this->parseDate($expireDate);
789
				} catch (\Exception $e) {
790
					throw new OCSBadRequestException($e->getMessage(), $e);
791
				}
792
				$share->setExpirationDate($expireDate);
793
			}
794
795
		}
796
797
		if ($permissions !== null && $share->getShareOwner() !== $this->currentUser) {
798
			/* Check if this is an incomming share */
799
			$incomingShares = $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_USER, $share->getNode(), -1, 0);
800
			$incomingShares = array_merge($incomingShares, $this->shareManager->getSharedWith($this->currentUser, \OCP\Share::SHARE_TYPE_GROUP, $share->getNode(), -1, 0));
801
802
			/** @var \OCP\Share\IShare[] $incomingShares */
803
			if (!empty($incomingShares)) {
804
				$maxPermissions = 0;
805
				foreach ($incomingShares as $incomingShare) {
806
					$maxPermissions |= $incomingShare->getPermissions();
807
				}
808
809
				if ($share->getPermissions() & ~$maxPermissions) {
810
					throw new OCSNotFoundException($this->l->t('Cannot increase permissions'));
811
				}
812
			}
813
		}
814
815
816
		try {
817
			$share = $this->shareManager->updateShare($share);
818
		} catch (\Exception $e) {
819
			throw new OCSBadRequestException($e->getMessage(), $e);
820
		}
821
822
		return new DataResponse($this->formatShare($share));
823
	}
824
825
	/**
826
	 * @param \OCP\Share\IShare $share
827
	 * @return bool
828
	 */
829
	protected function canAccessShare(\OCP\Share\IShare $share, $checkGroups = true) {
830
		// A file with permissions 0 can't be accessed by us. So Don't show it
831
		if ($share->getPermissions() === 0) {
832
			return false;
833
		}
834
835
		// Owner of the file and the sharer of the file can always get share
836
		if ($share->getShareOwner() === $this->currentUser ||
837
			$share->getSharedBy() === $this->currentUser
838
		) {
839
			return true;
840
		}
841
842
		// If the share is shared with you (or a group you are a member of)
843
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
844
			$share->getSharedWith() === $this->currentUser
845
		) {
846
			return true;
847
		}
848
849
		if ($checkGroups && $share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
850
			$sharedWith = $this->groupManager->get($share->getSharedWith());
851
			$user = $this->userManager->get($this->currentUser);
852
			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
853
				return true;
854
			}
855
		}
856
857
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_CIRCLE) {
858
			// TODO: have a sanity check like above?
859
			return true;
860
		}
861
862
		return false;
863
	}
864
865
	/**
866
	 * Make sure that the passed date is valid ISO 8601
867
	 * So YYYY-MM-DD
868
	 * If not throw an exception
869
	 *
870
	 * @param string $expireDate
871
	 *
872
	 * @throws \Exception
873
	 * @return \DateTime
874
	 */
875
	private function parseDate($expireDate) {
876
		try {
877
			$date = new \DateTime($expireDate);
878
		} catch (\Exception $e) {
879
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
880
		}
881
882
		if ($date === false) {
883
			throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
884
		}
885
886
		$date->setTime(0, 0, 0);
887
888
		return $date;
889
	}
890
891
	/**
892
	 * Since we have multiple providers but the OCS Share API v1 does
893
	 * not support this we need to check all backends.
894
	 *
895
	 * @param string $id
896
	 * @return \OCP\Share\IShare
897
	 * @throws ShareNotFound
898
	 */
899
	private function getShareById($id) {
900
		$share = null;
901
902
		// First check if it is an internal share.
903
		try {
904
			$share = $this->shareManager->getShareById('ocinternal:' . $id);
905
			return $share;
906
		} catch (ShareNotFound $e) {
907
			// Do nothing, just try the other share type
908
		}
909
910
911
		try {
912 View Code Duplication
			if ($this->shareManager->shareProviderExists(\OCP\Share::SHARE_TYPE_CIRCLE)) {
913
				$share = $this->shareManager->getShareById('ocCircleShare:' . $id);
914
				return $share;
915
			}
916
		} catch (ShareNotFound $e) {
917
			// Do nothing, just try the other share type
918
		}
919
920
		try {
921 View Code Duplication
			if ($this->shareManager->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
922
				$share = $this->shareManager->getShareById('ocMailShare:' . $id);
923
				return $share;
924
			}
925
		} catch (ShareNotFound $e) {
926
			// Do nothing, just try the other share type
927
		}
928
929
		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
930
			throw new ShareNotFound();
931
		}
932
		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id);
933
934
		return $share;
935
	}
936
937
	/**
938
	 * Lock a Node
939
	 *
940
	 * @param \OCP\Files\Node $node
941
	 */
942
	private function lock(\OCP\Files\Node $node) {
943
		$node->lock(ILockingProvider::LOCK_SHARED);
944
		$this->lockedNode = $node;
945
	}
946
947
	/**
948
	 * Cleanup the remaining locks
949
	 */
950
	public function cleanup() {
951
		if ($this->lockedNode !== null) {
952
			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
953
		}
954
	}
955
}
956