Passed
Push — master ( 31cc07...56b08c )
by Joas
24:48 queued 11s
created

Manager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 36
rs 9.6666
cc 1
nc 1
nop 16

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
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Daniel Calviño Sánchez <[email protected]>
10
 * @author Daniel Kesselberg <[email protected]>
11
 * @author Jan-Christoph Borchardt <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author John Molakvoæ (skjnldsv) <[email protected]>
14
 * @author Julius Härtl <[email protected]>
15
 * @author Lukas Reschke <[email protected]>
16
 * @author Maxence Lange <[email protected]>
17
 * @author Maxence Lange <[email protected]>
18
 * @author Morris Jobke <[email protected]>
19
 * @author Pauli Järvinen <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author Vincent Petry <[email protected]>
23
 *
24
 * @license AGPL-3.0
25
 *
26
 * This code is free software: you can redistribute it and/or modify
27
 * it under the terms of the GNU Affero General Public License, version 3,
28
 * as published by the Free Software Foundation.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33
 * GNU Affero General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU Affero General Public License, version 3,
36
 * along with this program. If not, see <http://www.gnu.org/licenses/>
37
 *
38
 */
39
40
namespace OC\Share20;
41
42
use OC\Cache\CappedMemoryCache;
43
use OC\Files\Mount\MoveableMount;
44
use OC\HintException;
45
use OC\Share20\Exception\ProviderException;
46
use OCA\Files_Sharing\ISharedStorage;
47
use OCP\EventDispatcher\IEventDispatcher;
48
use OCP\Files\File;
49
use OCP\Files\Folder;
50
use OCP\Files\IRootFolder;
51
use OCP\Files\Mount\IMountManager;
52
use OCP\Files\Node;
53
use OCP\IConfig;
54
use OCP\IGroupManager;
55
use OCP\IL10N;
56
use OCP\ILogger;
57
use OCP\IURLGenerator;
58
use OCP\IUser;
59
use OCP\IUserManager;
60
use OCP\L10N\IFactory;
61
use OCP\Mail\IMailer;
62
use OCP\Security\Events\ValidatePasswordPolicyEvent;
63
use OCP\Security\IHasher;
64
use OCP\Security\ISecureRandom;
65
use OCP\Share;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, OC\Share20\Share. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
66
use OCP\Share\Exceptions\AlreadySharedException;
67
use OCP\Share\Exceptions\GenericShareException;
68
use OCP\Share\Exceptions\ShareNotFound;
69
use OCP\Share\IManager;
70
use OCP\Share\IProviderFactory;
71
use OCP\Share\IShare;
72
use OCP\Share\IShareProvider;
73
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
74
use Symfony\Component\EventDispatcher\GenericEvent;
75
76
/**
77
 * This class is the communication hub for all sharing related operations.
78
 */
79
class Manager implements IManager {
80
81
	/** @var IProviderFactory */
82
	private $factory;
83
	/** @var ILogger */
84
	private $logger;
85
	/** @var IConfig */
86
	private $config;
87
	/** @var ISecureRandom */
88
	private $secureRandom;
89
	/** @var IHasher */
90
	private $hasher;
91
	/** @var IMountManager */
92
	private $mountManager;
93
	/** @var IGroupManager */
94
	private $groupManager;
95
	/** @var IL10N */
96
	private $l;
97
	/** @var IFactory */
98
	private $l10nFactory;
99
	/** @var IUserManager */
100
	private $userManager;
101
	/** @var IRootFolder */
102
	private $rootFolder;
103
	/** @var CappedMemoryCache */
104
	private $sharingDisabledForUsersCache;
105
	/** @var EventDispatcherInterface */
106
	private $legacyDispatcher;
107
	/** @var LegacyHooks */
108
	private $legacyHooks;
109
	/** @var IMailer */
110
	private $mailer;
111
	/** @var IURLGenerator */
112
	private $urlGenerator;
113
	/** @var \OC_Defaults */
114
	private $defaults;
115
	/** @var IEventDispatcher */
116
	private $dispatcher;
117
118
119
	/**
120
	 * Manager constructor.
121
	 *
122
	 * @param ILogger $logger
123
	 * @param IConfig $config
124
	 * @param ISecureRandom $secureRandom
125
	 * @param IHasher $hasher
126
	 * @param IMountManager $mountManager
127
	 * @param IGroupManager $groupManager
128
	 * @param IL10N $l
129
	 * @param IFactory $l10nFactory
130
	 * @param IProviderFactory $factory
131
	 * @param IUserManager $userManager
132
	 * @param IRootFolder $rootFolder
133
	 * @param EventDispatcherInterface $eventDispatcher
134
	 * @param IMailer $mailer
135
	 * @param IURLGenerator $urlGenerator
136
	 * @param \OC_Defaults $defaults
137
	 */
138
	public function __construct(
139
			ILogger $logger,
140
			IConfig $config,
141
			ISecureRandom $secureRandom,
142
			IHasher $hasher,
143
			IMountManager $mountManager,
144
			IGroupManager $groupManager,
145
			IL10N $l,
146
			IFactory $l10nFactory,
147
			IProviderFactory $factory,
148
			IUserManager $userManager,
149
			IRootFolder $rootFolder,
150
			EventDispatcherInterface $legacyDispatcher,
151
			IMailer $mailer,
152
			IURLGenerator $urlGenerator,
153
			\OC_Defaults $defaults,
154
			IEventDispatcher $dispatcher
155
	) {
156
		$this->logger = $logger;
157
		$this->config = $config;
158
		$this->secureRandom = $secureRandom;
159
		$this->hasher = $hasher;
160
		$this->mountManager = $mountManager;
161
		$this->groupManager = $groupManager;
162
		$this->l = $l;
163
		$this->l10nFactory = $l10nFactory;
164
		$this->factory = $factory;
165
		$this->userManager = $userManager;
166
		$this->rootFolder = $rootFolder;
167
		$this->legacyDispatcher = $legacyDispatcher;
168
		$this->sharingDisabledForUsersCache = new CappedMemoryCache();
169
		$this->legacyHooks = new LegacyHooks($this->legacyDispatcher);
170
		$this->mailer = $mailer;
171
		$this->urlGenerator = $urlGenerator;
172
		$this->defaults = $defaults;
173
		$this->dispatcher = $dispatcher;
174
	}
175
176
	/**
177
	 * Convert from a full share id to a tuple (providerId, shareId)
178
	 *
179
	 * @param string $id
180
	 * @return string[]
181
	 */
182
	private function splitFullId($id) {
183
		return explode(':', $id, 2);
184
	}
185
186
	/**
187
	 * Verify if a password meets all requirements
188
	 *
189
	 * @param string $password
190
	 * @throws \Exception
191
	 */
192
	protected function verifyPassword($password) {
193
		if ($password === null) {
0 ignored issues
show
introduced by
The condition $password === null is always false.
Loading history...
194
			// No password is set, check if this is allowed.
195
			if ($this->shareApiLinkEnforcePassword()) {
196
				throw new \InvalidArgumentException('Passwords are enforced for link shares');
197
			}
198
199
			return;
200
		}
201
202
		// Let others verify the password
203
		try {
204
			$this->legacyDispatcher->dispatch(new ValidatePasswordPolicyEvent($password));
205
		} catch (HintException $e) {
206
			throw new \Exception($e->getHint());
207
		}
208
	}
209
210
	/**
211
	 * Check for generic requirements before creating a share
212
	 *
213
	 * @param IShare $share
214
	 * @throws \InvalidArgumentException
215
	 * @throws GenericShareException
216
	 *
217
	 * @suppress PhanUndeclaredClassMethod
218
	 */
219
	protected function generalCreateChecks(IShare $share) {
220
		if ($share->getShareType() === IShare::TYPE_USER) {
221
			// We expect a valid user as sharedWith for user shares
222
			if (!$this->userManager->userExists($share->getSharedWith())) {
223
				throw new \InvalidArgumentException('SharedWith is not a valid user');
224
			}
225
		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
226
			// We expect a valid group as sharedWith for group shares
227
			if (!$this->groupManager->groupExists($share->getSharedWith())) {
228
				throw new \InvalidArgumentException('SharedWith is not a valid group');
229
			}
230
		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
231
			if ($share->getSharedWith() !== null) {
0 ignored issues
show
introduced by
The condition $share->getSharedWith() !== null is always true.
Loading history...
232
				throw new \InvalidArgumentException('SharedWith should be empty');
233
			}
234
		} elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
235
			if ($share->getSharedWith() === null) {
0 ignored issues
show
introduced by
The condition $share->getSharedWith() === null is always false.
Loading history...
236
				throw new \InvalidArgumentException('SharedWith should not be empty');
237
			}
238
		} elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
239
			if ($share->getSharedWith() === null) {
0 ignored issues
show
introduced by
The condition $share->getSharedWith() === null is always false.
Loading history...
240
				throw new \InvalidArgumentException('SharedWith should not be empty');
241
			}
242
		} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
243
			if ($share->getSharedWith() === null) {
0 ignored issues
show
introduced by
The condition $share->getSharedWith() === null is always false.
Loading history...
244
				throw new \InvalidArgumentException('SharedWith should not be empty');
245
			}
246
		} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
247
			$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith());
0 ignored issues
show
Bug introduced by
The type OCA\Circles\Api\v1\Circles was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
248
			if ($circle === null) {
249
				throw new \InvalidArgumentException('SharedWith is not a valid circle');
250
			}
251
		} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
252
		} elseif ($share->getShareType() === IShare::TYPE_DECK) {
253
		} else {
254
			// We can't handle other types yet
255
			throw new \InvalidArgumentException('unknown share type');
256
		}
257
258
		// Verify the initiator of the share is set
259
		if ($share->getSharedBy() === null) {
0 ignored issues
show
introduced by
The condition $share->getSharedBy() === null is always false.
Loading history...
260
			throw new \InvalidArgumentException('SharedBy should be set');
261
		}
262
263
		// Cannot share with yourself
264
		if ($share->getShareType() === IShare::TYPE_USER &&
265
			$share->getSharedWith() === $share->getSharedBy()) {
266
			throw new \InvalidArgumentException('Can’t share with yourself');
267
		}
268
269
		// The path should be set
270
		if ($share->getNode() === null) {
271
			throw new \InvalidArgumentException('Path should be set');
272
		}
273
274
		// And it should be a file or a folder
275
		if (!($share->getNode() instanceof \OCP\Files\File) &&
276
				!($share->getNode() instanceof \OCP\Files\Folder)) {
0 ignored issues
show
introduced by
$share->getNode() is always a sub-type of OCP\Files\Folder.
Loading history...
277
			throw new \InvalidArgumentException('Path should be either a file or a folder');
278
		}
279
280
		// And you can't share your rootfolder
281
		if ($this->userManager->userExists($share->getSharedBy())) {
282
			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
283
		} else {
284
			$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
285
		}
286
		if ($userFolder->getId() === $share->getNode()->getId()) {
287
			throw new \InvalidArgumentException('You can’t share your root folder');
288
		}
289
290
		// Check if we actually have share permissions
291
		if (!$share->getNode()->isShareable()) {
292
			$message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getName()]);
293
			throw new GenericShareException($message_t, $message_t, 404);
294
		}
295
296
		// Permissions should be set
297
		if ($share->getPermissions() === null) {
0 ignored issues
show
introduced by
The condition $share->getPermissions() === null is always false.
Loading history...
298
			throw new \InvalidArgumentException('A share requires permissions');
299
		}
300
301
		$isFederatedShare = $share->getNode()->getStorage()->instanceOfStorage('\OCA\Files_Sharing\External\Storage');
302
		$permissions = 0;
303
304
		if (!$isFederatedShare && $share->getNode()->getOwner() && $share->getNode()->getOwner()->getUID() !== $share->getSharedBy()) {
305
			$userMounts = array_filter($userFolder->getById($share->getNode()->getId()), function ($mount) {
306
				// We need to filter since there might be other mountpoints that contain the file
307
				// e.g. if the user has access to the same external storage that the file is originating from
308
				return $mount->getStorage()->instanceOfStorage(ISharedStorage::class);
309
			});
310
			$userMount = array_shift($userMounts);
311
			if ($userMount === null) {
312
				throw new GenericShareException('Could not get proper share mount for ' . $share->getNode()->getId() . '. Failing since else the next calls are called with null');
313
			}
314
			$mount = $userMount->getMountPoint();
315
			// When it's a reshare use the parent share permissions as maximum
316
			$userMountPointId = $mount->getStorageRootId();
317
			$userMountPoints = $userFolder->getById($userMountPointId);
318
			$userMountPoint = array_shift($userMountPoints);
319
320
			if ($userMountPoint === null) {
321
				throw new GenericShareException('Could not get proper user mount for ' . $userMountPointId . '. Failing since else the next calls are called with null');
322
			}
323
324
			/* Check if this is an incoming share */
325
			$incomingShares = $this->getSharedWith($share->getSharedBy(), IShare::TYPE_USER, $userMountPoint, -1, 0);
326
			$incomingShares = array_merge($incomingShares, $this->getSharedWith($share->getSharedBy(), IShare::TYPE_GROUP, $userMountPoint, -1, 0));
327
			$incomingShares = array_merge($incomingShares, $this->getSharedWith($share->getSharedBy(), IShare::TYPE_CIRCLE, $userMountPoint, -1, 0));
328
			$incomingShares = array_merge($incomingShares, $this->getSharedWith($share->getSharedBy(), IShare::TYPE_ROOM, $userMountPoint, -1, 0));
329
330
			/** @var IShare[] $incomingShares */
331
			if (!empty($incomingShares)) {
332
				foreach ($incomingShares as $incomingShare) {
333
					$permissions |= $incomingShare->getPermissions();
334
				}
335
			}
336
		} else {
337
			/*
338
			 * Quick fix for #23536
339
			 * Non moveable mount points do not have update and delete permissions
340
			 * while we 'most likely' do have that on the storage.
341
			 */
342
			$permissions = $share->getNode()->getPermissions();
343
			if (!($share->getNode()->getMountPoint() instanceof MoveableMount)) {
344
				$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
345
			}
346
		}
347
348
		// Check that we do not share with more permissions than we have
349
		if ($share->getPermissions() & ~$permissions) {
350
			$path = $userFolder->getRelativePath($share->getNode()->getPath());
351
			$message_t = $this->l->t('Can’t increase permissions of %s', [$path]);
352
			throw new GenericShareException($message_t, $message_t, 404);
353
		}
354
355
356
		// Check that read permissions are always set
357
		// Link shares are allowed to have no read permissions to allow upload to hidden folders
358
		$noReadPermissionRequired = $share->getShareType() === IShare::TYPE_LINK
359
			|| $share->getShareType() === IShare::TYPE_EMAIL;
360
		if (!$noReadPermissionRequired &&
361
			($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
362
			throw new \InvalidArgumentException('Shares need at least read permissions');
363
		}
364
365
		if ($share->getNode() instanceof \OCP\Files\File) {
366
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
367
				$message_t = $this->l->t('Files can’t be shared with delete permissions');
368
				throw new GenericShareException($message_t);
369
			}
370
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
371
				$message_t = $this->l->t('Files can’t be shared with create permissions');
372
				throw new GenericShareException($message_t);
373
			}
374
		}
375
	}
376
377
	/**
378
	 * Validate if the expiration date fits the system settings
379
	 *
380
	 * @param IShare $share The share to validate the expiration date of
381
	 * @return IShare The modified share object
382
	 * @throws GenericShareException
383
	 * @throws \InvalidArgumentException
384
	 * @throws \Exception
385
	 */
386
	protected function validateExpirationDateInternal(IShare $share) {
387
		$expirationDate = $share->getExpirationDate();
388
389
		if ($expirationDate !== null) {
390
			//Make sure the expiration date is a date
391
			$expirationDate->setTime(0, 0, 0);
392
393
			$date = new \DateTime();
394
			$date->setTime(0, 0, 0);
395
			if ($date >= $expirationDate) {
396
				$message = $this->l->t('Expiration date is in the past');
397
				throw new GenericShareException($message, $message, 404);
398
			}
399
		}
400
401
		// If expiredate is empty set a default one if there is a default
402
		$fullId = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $fullId is dead and can be removed.
Loading history...
403
		try {
404
			$fullId = $share->getFullId();
405
		} catch (\UnexpectedValueException $e) {
406
			// This is a new share
407
		}
408
409
		if ($fullId === null && $expirationDate === null && $this->shareApiInternalDefaultExpireDate()) {
0 ignored issues
show
introduced by
The condition $fullId === null is always false.
Loading history...
410
			$expirationDate = new \DateTime();
411
			$expirationDate->setTime(0,0,0);
412
413
			$days = (int)$this->config->getAppValue('core', 'internal_defaultExpDays', (string)$this->shareApiInternalDefaultExpireDays());
414
			if ($days > $this->shareApiInternalDefaultExpireDays()) {
415
				$days = $this->shareApiInternalDefaultExpireDays();
416
			}
417
			$expirationDate->add(new \DateInterval('P'.$days.'D'));
418
		}
419
420
		// If we enforce the expiration date check that is does not exceed
421
		if ($this->shareApiInternalDefaultExpireDateEnforced()) {
422
			if ($expirationDate === null) {
423
				throw new \InvalidArgumentException('Expiration date is enforced');
424
			}
425
426
			$date = new \DateTime();
427
			$date->setTime(0, 0, 0);
428
			$date->add(new \DateInterval('P' . $this->shareApiInternalDefaultExpireDays() . 'D'));
429
			if ($date < $expirationDate) {
430
				$message = $this->l->t('Can’t set expiration date more than %s days in the future', [$this->shareApiInternalDefaultExpireDays()]);
431
				throw new GenericShareException($message, $message, 404);
432
			}
433
		}
434
435
		$accepted = true;
436
		$message = '';
437
		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
438
			'expirationDate' => &$expirationDate,
439
			'accepted' => &$accepted,
440
			'message' => &$message,
441
			'passwordSet' => $share->getPassword() !== null,
442
		]);
443
444
		if (!$accepted) {
0 ignored issues
show
introduced by
The condition $accepted is always true.
Loading history...
445
			throw new \Exception($message);
446
		}
447
448
		$share->setExpirationDate($expirationDate);
449
450
		return $share;
451
	}
452
453
	/**
454
	 * Validate if the expiration date fits the system settings
455
	 *
456
	 * @param IShare $share The share to validate the expiration date of
457
	 * @return IShare The modified share object
458
	 * @throws GenericShareException
459
	 * @throws \InvalidArgumentException
460
	 * @throws \Exception
461
	 */
462
	protected function validateExpirationDate(IShare $share) {
463
		$expirationDate = $share->getExpirationDate();
464
465
		if ($expirationDate !== null) {
466
			//Make sure the expiration date is a date
467
			$expirationDate->setTime(0, 0, 0);
468
469
			$date = new \DateTime();
470
			$date->setTime(0, 0, 0);
471
			if ($date >= $expirationDate) {
472
				$message = $this->l->t('Expiration date is in the past');
473
				throw new GenericShareException($message, $message, 404);
474
			}
475
		}
476
477
		// If expiredate is empty set a default one if there is a default
478
		$fullId = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $fullId is dead and can be removed.
Loading history...
479
		try {
480
			$fullId = $share->getFullId();
481
		} catch (\UnexpectedValueException $e) {
482
			// This is a new share
483
		}
484
485
		if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
0 ignored issues
show
introduced by
The condition $fullId === null is always false.
Loading history...
486
			$expirationDate = new \DateTime();
487
			$expirationDate->setTime(0,0,0);
488
489
			$days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', $this->shareApiLinkDefaultExpireDays());
490
			if ($days > $this->shareApiLinkDefaultExpireDays()) {
491
				$days = $this->shareApiLinkDefaultExpireDays();
492
			}
493
			$expirationDate->add(new \DateInterval('P'.$days.'D'));
494
		}
495
496
		// If we enforce the expiration date check that is does not exceed
497
		if ($this->shareApiLinkDefaultExpireDateEnforced()) {
498
			if ($expirationDate === null) {
499
				throw new \InvalidArgumentException('Expiration date is enforced');
500
			}
501
502
			$date = new \DateTime();
503
			$date->setTime(0, 0, 0);
504
			$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
505
			if ($date < $expirationDate) {
506
				$message = $this->l->t('Can’t set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
507
				throw new GenericShareException($message, $message, 404);
508
			}
509
		}
510
511
		$accepted = true;
512
		$message = '';
513
		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
514
			'expirationDate' => &$expirationDate,
515
			'accepted' => &$accepted,
516
			'message' => &$message,
517
			'passwordSet' => $share->getPassword() !== null,
518
		]);
519
520
		if (!$accepted) {
0 ignored issues
show
introduced by
The condition $accepted is always true.
Loading history...
521
			throw new \Exception($message);
522
		}
523
524
		$share->setExpirationDate($expirationDate);
525
526
		return $share;
527
	}
528
529
	/**
530
	 * Check for pre share requirements for user shares
531
	 *
532
	 * @param IShare $share
533
	 * @throws \Exception
534
	 */
535
	protected function userCreateChecks(IShare $share) {
536
		// Check if we can share with group members only
537
		if ($this->shareWithGroupMembersOnly()) {
538
			$sharedBy = $this->userManager->get($share->getSharedBy());
539
			$sharedWith = $this->userManager->get($share->getSharedWith());
540
			// Verify we can share with this user
541
			$groups = array_intersect(
542
					$this->groupManager->getUserGroupIds($sharedBy),
543
					$this->groupManager->getUserGroupIds($sharedWith)
544
			);
545
			if (empty($groups)) {
546
				$message_t = $this->l->t('Sharing is only allowed with group members');
547
				throw new \Exception($message_t);
548
			}
549
		}
550
551
		/*
552
		 * TODO: Could be costly, fix
553
		 *
554
		 * Also this is not what we want in the future.. then we want to squash identical shares.
555
		 */
556
		$provider = $this->factory->getProviderForType(IShare::TYPE_USER);
557
		$existingShares = $provider->getSharesByPath($share->getNode());
558
		foreach ($existingShares as $existingShare) {
559
			// Ignore if it is the same share
560
			try {
561
				if ($existingShare->getFullId() === $share->getFullId()) {
562
					continue;
563
				}
564
			} catch (\UnexpectedValueException $e) {
565
				//Shares are not identical
566
			}
567
568
			// Identical share already existst
569
			if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
570
				throw new AlreadySharedException('Path is already shared with this user', $existingShare);
571
			}
572
573
			// The share is already shared with this user via a group share
574
			if ($existingShare->getShareType() === IShare::TYPE_GROUP) {
575
				$group = $this->groupManager->get($existingShare->getSharedWith());
576
				if (!is_null($group)) {
577
					$user = $this->userManager->get($share->getSharedWith());
578
579
					if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
580
						throw new AlreadySharedException('Path is already shared with this user', $existingShare);
581
					}
582
				}
583
			}
584
		}
585
	}
586
587
	/**
588
	 * Check for pre share requirements for group shares
589
	 *
590
	 * @param IShare $share
591
	 * @throws \Exception
592
	 */
593
	protected function groupCreateChecks(IShare $share) {
594
		// Verify group shares are allowed
595
		if (!$this->allowGroupSharing()) {
596
			throw new \Exception('Group sharing is now allowed');
597
		}
598
599
		// Verify if the user can share with this group
600
		if ($this->shareWithGroupMembersOnly()) {
601
			$sharedBy = $this->userManager->get($share->getSharedBy());
602
			$sharedWith = $this->groupManager->get($share->getSharedWith());
603
			if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) {
604
				throw new \Exception('Sharing is only allowed within your own groups');
605
			}
606
		}
607
608
		/*
609
		 * TODO: Could be costly, fix
610
		 *
611
		 * Also this is not what we want in the future.. then we want to squash identical shares.
612
		 */
613
		$provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
614
		$existingShares = $provider->getSharesByPath($share->getNode());
615
		foreach ($existingShares as $existingShare) {
616
			try {
617
				if ($existingShare->getFullId() === $share->getFullId()) {
618
					continue;
619
				}
620
			} catch (\UnexpectedValueException $e) {
621
				//It is a new share so just continue
622
			}
623
624
			if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
625
				throw new AlreadySharedException('Path is already shared with this group', $existingShare);
626
			}
627
		}
628
	}
629
630
	/**
631
	 * Check for pre share requirements for link shares
632
	 *
633
	 * @param IShare $share
634
	 * @throws \Exception
635
	 */
636
	protected function linkCreateChecks(IShare $share) {
637
		// Are link shares allowed?
638
		if (!$this->shareApiAllowLinks()) {
639
			throw new \Exception('Link sharing is not allowed');
640
		}
641
642
		// Check if public upload is allowed
643
		if (!$this->shareApiLinkAllowPublicUpload() &&
644
			($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
645
			throw new \InvalidArgumentException('Public upload is not allowed');
646
		}
647
	}
648
649
	/**
650
	 * To make sure we don't get invisible link shares we set the parent
651
	 * of a link if it is a reshare. This is a quick word around
652
	 * until we can properly display multiple link shares in the UI
653
	 *
654
	 * See: https://github.com/owncloud/core/issues/22295
655
	 *
656
	 * FIXME: Remove once multiple link shares can be properly displayed
657
	 *
658
	 * @param IShare $share
659
	 */
660
	protected function setLinkParent(IShare $share) {
661
662
		// No sense in checking if the method is not there.
663
		if (method_exists($share, 'setParent')) {
664
			$storage = $share->getNode()->getStorage();
665
			if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
666
				/** @var \OCA\Files_Sharing\SharedStorage $storage */
667
				$share->setParent($storage->getShareId());
668
			}
669
		}
670
	}
671
672
	/**
673
	 * @param File|Folder $path
674
	 */
675
	protected function pathCreateChecks($path) {
676
		// Make sure that we do not share a path that contains a shared mountpoint
677
		if ($path instanceof \OCP\Files\Folder) {
678
			$mounts = $this->mountManager->findIn($path->getPath());
679
			foreach ($mounts as $mount) {
680
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
681
					throw new \InvalidArgumentException('Path contains files shared with you');
682
				}
683
			}
684
		}
685
	}
686
687
	/**
688
	 * Check if the user that is sharing can actually share
689
	 *
690
	 * @param IShare $share
691
	 * @throws \Exception
692
	 */
693
	protected function canShare(IShare $share) {
694
		if (!$this->shareApiEnabled()) {
695
			throw new \Exception('Sharing is disabled');
696
		}
697
698
		if ($this->sharingDisabledForUser($share->getSharedBy())) {
699
			throw new \Exception('Sharing is disabled for you');
700
		}
701
	}
702
703
	/**
704
	 * Share a path
705
	 *
706
	 * @param IShare $share
707
	 * @return IShare The share object
708
	 * @throws \Exception
709
	 *
710
	 * TODO: handle link share permissions or check them
711
	 */
712
	public function createShare(IShare $share) {
713
		$this->canShare($share);
714
715
		$this->generalCreateChecks($share);
716
717
		// Verify if there are any issues with the path
718
		$this->pathCreateChecks($share->getNode());
719
720
		/*
721
		 * On creation of a share the owner is always the owner of the path
722
		 * Except for mounted federated shares.
723
		 */
724
		$storage = $share->getNode()->getStorage();
725
		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
726
			$parent = $share->getNode()->getParent();
727
			while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
728
				$parent = $parent->getParent();
729
			}
730
			$share->setShareOwner($parent->getOwner()->getUID());
731
		} else {
732
			if ($share->getNode()->getOwner()) {
733
				$share->setShareOwner($share->getNode()->getOwner()->getUID());
734
			} else {
735
				$share->setShareOwner($share->getSharedBy());
736
			}
737
		}
738
739
		try {
740
			//Verify share type
741
			if ($share->getShareType() === IShare::TYPE_USER) {
742
				$this->userCreateChecks($share);
743
744
				//Verify the expiration date
745
				$share = $this->validateExpirationDateInternal($share);
746
			} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
747
				$this->groupCreateChecks($share);
748
749
				//Verify the expiration date
750
				$share = $this->validateExpirationDateInternal($share);
751
			} elseif ($share->getShareType() === IShare::TYPE_LINK) {
752
				$this->linkCreateChecks($share);
753
				$this->setLinkParent($share);
754
755
				/*
756
				 * For now ignore a set token.
757
				 */
758
				$share->setToken(
759
					$this->secureRandom->generate(
760
						\OC\Share\Constants::TOKEN_LENGTH,
761
						\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
762
					)
763
				);
764
765
				//Verify the expiration date
766
				$share = $this->validateExpirationDate($share);
767
768
				//Verify the password
769
				$this->verifyPassword($share->getPassword());
770
771
				// If a password is set. Hash it!
772
				if ($share->getPassword() !== null) {
0 ignored issues
show
introduced by
The condition $share->getPassword() !== null is always true.
Loading history...
773
					$share->setPassword($this->hasher->hash($share->getPassword()));
774
				}
775
			} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
776
				$share->setToken(
777
					$this->secureRandom->generate(
778
						\OC\Share\Constants::TOKEN_LENGTH,
779
						\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
780
					)
781
				);
782
			}
783
784
			// Cannot share with the owner
785
			if ($share->getShareType() === IShare::TYPE_USER &&
786
				$share->getSharedWith() === $share->getShareOwner()) {
787
				throw new \InvalidArgumentException('Can’t share with the share owner');
788
			}
789
790
			// Generate the target
791
			$target = $this->config->getSystemValue('share_folder', '/') . '/' . $share->getNode()->getName();
792
			$target = \OC\Files\Filesystem::normalizePath($target);
793
			$share->setTarget($target);
794
795
			// Pre share event
796
			$event = new GenericEvent($share);
797
			$this->legacyDispatcher->dispatch('OCP\Share::preShare', $event);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

797
			$this->legacyDispatcher->/** @scrutinizer ignore-call */ 
798
                            dispatch('OCP\Share::preShare', $event);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
'OCP\Share::preShare' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

797
			$this->legacyDispatcher->dispatch(/** @scrutinizer ignore-type */ 'OCP\Share::preShare', $event);
Loading history...
798
			if ($event->isPropagationStopped() && $event->hasArgument('error')) {
799
				throw new \Exception($event->getArgument('error'));
800
			}
801
802
			$oldShare = $share;
803
			$provider = $this->factory->getProviderForType($share->getShareType());
804
			$share = $provider->create($share);
805
			//reuse the node we already have
806
			$share->setNode($oldShare->getNode());
807
808
			// Reset the target if it is null for the new share
809
			if ($share->getTarget() === '') {
810
				$share->setTarget($target);
811
			}
812
		} catch (AlreadySharedException $e) {
813
			// if a share for the same target already exists, dont create a new one, but do trigger the hooks and notifications again
814
			$share = $e->getExistingShare();
815
		}
816
817
		// Post share event
818
		$event = new GenericEvent($share);
819
		$this->legacyDispatcher->dispatch('OCP\Share::postShare', $event);
820
821
		$this->dispatcher->dispatchTyped(new Share\Events\ShareCreatedEvent($share));
822
823
		if ($this->config->getSystemValueBool('sharing.enable_share_mail', true)
824
			&& $share->getShareType() === IShare::TYPE_USER) {
825
			$mailSend = $share->getMailSend();
826
			if ($mailSend === true) {
827
				$user = $this->userManager->get($share->getSharedWith());
828
				if ($user !== null) {
829
					$emailAddress = $user->getEMailAddress();
830
					if ($emailAddress !== null && $emailAddress !== '') {
831
						$userLang = $this->l10nFactory->getUserLanguage($user);
832
						$l = $this->l10nFactory->get('lib', $userLang);
833
						$this->sendMailNotification(
834
							$l,
835
							$share->getNode()->getName(),
836
							$this->urlGenerator->linkToRouteAbsolute('files_sharing.Accept.accept', ['shareId' => $share->getFullId()]),
837
							$share->getSharedBy(),
838
							$emailAddress,
839
							$share->getExpirationDate()
840
						);
841
						$this->logger->debug('Sent share notification to ' . $emailAddress . ' for share with ID ' . $share->getId(), ['app' => 'share']);
842
					} else {
843
						$this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because email address is not set.', ['app' => 'share']);
844
					}
845
				} else {
846
					$this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because user could not be found.', ['app' => 'share']);
847
				}
848
			} else {
849
				$this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']);
850
			}
851
		}
852
853
		return $share;
854
	}
855
856
	/**
857
	 * Send mail notifications
858
	 *
859
	 * This method will catch and log mail transmission errors
860
	 *
861
	 * @param IL10N $l Language of the recipient
862
	 * @param string $filename file/folder name
863
	 * @param string $link link to the file/folder
864
	 * @param string $initiator user ID of share sender
865
	 * @param string $shareWith email address of share receiver
866
	 * @param \DateTime|null $expiration
867
	 */
868
	protected function sendMailNotification(IL10N $l,
869
											$filename,
870
											$link,
871
											$initiator,
872
											$shareWith,
873
											\DateTime $expiration = null) {
874
		$initiatorUser = $this->userManager->get($initiator);
875
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
876
877
		$message = $this->mailer->createMessage();
878
879
		$emailTemplate = $this->mailer->createEMailTemplate('files_sharing.RecipientNotification', [
880
			'filename' => $filename,
881
			'link' => $link,
882
			'initiator' => $initiatorDisplayName,
883
			'expiration' => $expiration,
884
			'shareWith' => $shareWith,
885
		]);
886
887
		$emailTemplate->setSubject($l->t('%1$s shared »%2$s« with you', [$initiatorDisplayName, $filename]));
888
		$emailTemplate->addHeader();
889
		$emailTemplate->addHeading($l->t('%1$s shared »%2$s« with you', [$initiatorDisplayName, $filename]), false);
890
		$text = $l->t('%1$s shared »%2$s« with you.', [$initiatorDisplayName, $filename]);
891
892
		$emailTemplate->addBodyText(
893
			htmlspecialchars($text . ' ' . $l->t('Click the button below to open it.')),
894
			$text
895
		);
896
		$emailTemplate->addBodyButton(
897
			$l->t('Open »%s«', [$filename]),
898
			$link
899
		);
900
901
		$message->setTo([$shareWith]);
902
903
		// The "From" contains the sharers name
904
		$instanceName = $this->defaults->getName();
905
		$senderName = $l->t(
906
			'%1$s via %2$s',
907
			[
908
				$initiatorDisplayName,
909
				$instanceName
910
			]
911
		);
912
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
913
914
		// The "Reply-To" is set to the sharer if an mail address is configured
915
		// also the default footer contains a "Do not reply" which needs to be adjusted.
916
		$initiatorEmail = $initiatorUser->getEMailAddress();
917
		if ($initiatorEmail !== null) {
918
			$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
919
			$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan($l->getLanguageCode()) !== '' ? ' - ' . $this->defaults->getSlogan($l->getLanguageCode()) : ''));
920
		} else {
921
			$emailTemplate->addFooter('', $l->getLanguageCode());
922
		}
923
924
		$message->useTemplate($emailTemplate);
925
		try {
926
			$failedRecipients = $this->mailer->send($message);
927
			if (!empty($failedRecipients)) {
928
				$this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients));
929
				return;
930
			}
931
		} catch (\Exception $e) {
932
			$this->logger->logException($e, ['message' => 'Share notification mail could not be sent']);
933
		}
934
	}
935
936
	/**
937
	 * Update a share
938
	 *
939
	 * @param IShare $share
940
	 * @return IShare The share object
941
	 * @throws \InvalidArgumentException
942
	 */
943
	public function updateShare(IShare $share) {
944
		$expirationDateUpdated = false;
945
946
		$this->canShare($share);
947
948
		try {
949
			$originalShare = $this->getShareById($share->getFullId());
950
		} catch (\UnexpectedValueException $e) {
951
			throw new \InvalidArgumentException('Share does not have a full id');
952
		}
953
954
		// We can't change the share type!
955
		if ($share->getShareType() !== $originalShare->getShareType()) {
956
			throw new \InvalidArgumentException('Can’t change share type');
957
		}
958
959
		// We can only change the recipient on user shares
960
		if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
961
			$share->getShareType() !== IShare::TYPE_USER) {
962
			throw new \InvalidArgumentException('Can only update recipient on user shares');
963
		}
964
965
		// Cannot share with the owner
966
		if ($share->getShareType() === IShare::TYPE_USER &&
967
			$share->getSharedWith() === $share->getShareOwner()) {
968
			throw new \InvalidArgumentException('Can’t share with the share owner');
969
		}
970
971
		$this->generalCreateChecks($share);
972
973
		if ($share->getShareType() === IShare::TYPE_USER) {
974
			$this->userCreateChecks($share);
975
976
			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
977
				//Verify the expiration date
978
				$this->validateExpirationDate($share);
979
				$expirationDateUpdated = true;
980
			}
981
		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
982
			$this->groupCreateChecks($share);
983
984
			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
985
				//Verify the expiration date
986
				$this->validateExpirationDate($share);
987
				$expirationDateUpdated = true;
988
			}
989
		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
990
			$this->linkCreateChecks($share);
991
992
			$plainTextPassword = $share->getPassword();
993
994
			$this->updateSharePasswordIfNeeded($share, $originalShare);
995
996
			if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) {
997
				throw new \InvalidArgumentException('Can’t enable sending the password by Talk with an empty password');
998
			}
999
1000
			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
1001
				//Verify the expiration date
1002
				$this->validateExpirationDate($share);
1003
				$expirationDateUpdated = true;
1004
			}
1005
		} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
1006
			// The new password is not set again if it is the same as the old
1007
			// one.
1008
			$plainTextPassword = $share->getPassword();
1009
			if (!empty($plainTextPassword) && !$this->updateSharePasswordIfNeeded($share, $originalShare)) {
1010
				$plainTextPassword = null;
1011
			}
1012
			if (empty($plainTextPassword) && !$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) {
1013
				// If the same password was already sent by mail the recipient
1014
				// would already have access to the share without having to call
1015
				// the sharer to verify her identity
1016
				throw new \InvalidArgumentException('Can’t enable sending the password by Talk without setting a new password');
1017
			} elseif (empty($plainTextPassword) && $originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) {
1018
				throw new \InvalidArgumentException('Can’t disable sending the password by Talk without setting a new password');
1019
			}
1020
		}
1021
1022
		$this->pathCreateChecks($share->getNode());
1023
1024
		// Now update the share!
1025
		$provider = $this->factory->getProviderForType($share->getShareType());
1026
		if ($share->getShareType() === IShare::TYPE_EMAIL) {
1027
			$share = $provider->update($share, $plainTextPassword);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $plainTextPassword does not seem to be defined for all execution paths leading up to this point.
Loading history...
Unused Code introduced by
The call to OCP\Share\IShareProvider::update() has too many arguments starting with $plainTextPassword. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1027
			/** @scrutinizer ignore-call */ 
1028
   $share = $provider->update($share, $plainTextPassword);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1028
		} else {
1029
			$share = $provider->update($share);
1030
		}
1031
1032
		if ($expirationDateUpdated === true) {
1033
			\OC_Hook::emit(Share::class, 'post_set_expiration_date', [
1034
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
1035
				'itemSource' => $share->getNode()->getId(),
1036
				'date' => $share->getExpirationDate(),
1037
				'uidOwner' => $share->getSharedBy(),
1038
			]);
1039
		}
1040
1041
		if ($share->getPassword() !== $originalShare->getPassword()) {
1042
			\OC_Hook::emit(Share::class, 'post_update_password', [
1043
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
1044
				'itemSource' => $share->getNode()->getId(),
1045
				'uidOwner' => $share->getSharedBy(),
1046
				'token' => $share->getToken(),
1047
				'disabled' => is_null($share->getPassword()),
1048
			]);
1049
		}
1050
1051
		if ($share->getPermissions() !== $originalShare->getPermissions()) {
1052
			if ($this->userManager->userExists($share->getShareOwner())) {
1053
				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
1054
			} else {
1055
				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1056
			}
1057
			\OC_Hook::emit(Share::class, 'post_update_permissions', [
1058
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
1059
				'itemSource' => $share->getNode()->getId(),
1060
				'shareType' => $share->getShareType(),
1061
				'shareWith' => $share->getSharedWith(),
1062
				'uidOwner' => $share->getSharedBy(),
1063
				'permissions' => $share->getPermissions(),
1064
				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
1065
			]);
1066
		}
1067
1068
		return $share;
1069
	}
1070
1071
	/**
1072
	 * Accept a share.
1073
	 *
1074
	 * @param IShare $share
1075
	 * @param string $recipientId
1076
	 * @return IShare The share object
1077
	 * @throws \InvalidArgumentException
1078
	 * @since 9.0.0
1079
	 */
1080
	public function acceptShare(IShare $share, string $recipientId): IShare {
1081
		[$providerId, ] = $this->splitFullId($share->getFullId());
1082
		$provider = $this->factory->getProvider($providerId);
1083
1084
		if (!method_exists($provider, 'acceptShare')) {
1085
			// TODO FIX ME
1086
			throw new \InvalidArgumentException('Share provider does not support accepting');
1087
		}
1088
		$provider->acceptShare($share, $recipientId);
1089
		$event = new GenericEvent($share);
1090
		$this->legacyDispatcher->dispatch('OCP\Share::postAcceptShare', $event);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1090
		$this->legacyDispatcher->/** @scrutinizer ignore-call */ 
1091
                           dispatch('OCP\Share::postAcceptShare', $event);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
'OCP\Share::postAcceptShare' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1090
		$this->legacyDispatcher->dispatch(/** @scrutinizer ignore-type */ 'OCP\Share::postAcceptShare', $event);
Loading history...
1091
1092
		return $share;
1093
	}
1094
1095
	/**
1096
	 * Updates the password of the given share if it is not the same as the
1097
	 * password of the original share.
1098
	 *
1099
	 * @param IShare $share the share to update its password.
1100
	 * @param IShare $originalShare the original share to compare its
1101
	 *        password with.
1102
	 * @return boolean whether the password was updated or not.
1103
	 */
1104
	private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare) {
1105
		$passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword()) &&
1106
									(($share->getPassword() !== null && $originalShare->getPassword() === null) ||
0 ignored issues
show
introduced by
The condition $originalShare->getPassword() === null is always false.
Loading history...
1107
									 ($share->getPassword() === null && $originalShare->getPassword() !== null) ||
0 ignored issues
show
introduced by
The condition $share->getPassword() === null is always false.
Loading history...
1108
									 ($share->getPassword() !== null && $originalShare->getPassword() !== null &&
1109
										!$this->hasher->verify($share->getPassword(), $originalShare->getPassword())));
1110
1111
		// Password updated.
1112
		if ($passwordsAreDifferent) {
1113
			//Verify the password
1114
			$this->verifyPassword($share->getPassword());
1115
1116
			// If a password is set. Hash it!
1117
			if ($share->getPassword() !== null) {
0 ignored issues
show
introduced by
The condition $share->getPassword() !== null is always true.
Loading history...
1118
				$share->setPassword($this->hasher->hash($share->getPassword()));
1119
1120
				return true;
1121
			}
1122
		} else {
1123
			// Reset the password to the original one, as it is either the same
1124
			// as the "new" password or a hashed version of it.
1125
			$share->setPassword($originalShare->getPassword());
1126
		}
1127
1128
		return false;
1129
	}
1130
1131
	/**
1132
	 * Delete all the children of this share
1133
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
1134
	 *
1135
	 * @param IShare $share
1136
	 * @return IShare[] List of deleted shares
1137
	 */
1138
	protected function deleteChildren(IShare $share) {
1139
		$deletedShares = [];
1140
1141
		$provider = $this->factory->getProviderForType($share->getShareType());
1142
1143
		foreach ($provider->getChildren($share) as $child) {
0 ignored issues
show
Bug introduced by
The method getChildren() does not exist on OCP\Share\IShareProvider. Since it exists in all sub-types, consider adding an abstract or default implementation to OCP\Share\IShareProvider. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1143
		foreach ($provider->/** @scrutinizer ignore-call */ getChildren($share) as $child) {
Loading history...
1144
			$deletedChildren = $this->deleteChildren($child);
1145
			$deletedShares = array_merge($deletedShares, $deletedChildren);
1146
1147
			$provider->delete($child);
1148
			$this->dispatcher->dispatchTyped(new Share\Events\ShareDeletedEvent($child));
1149
			$deletedShares[] = $child;
1150
		}
1151
1152
		return $deletedShares;
1153
	}
1154
1155
	/**
1156
	 * Delete a share
1157
	 *
1158
	 * @param IShare $share
1159
	 * @throws ShareNotFound
1160
	 * @throws \InvalidArgumentException
1161
	 */
1162
	public function deleteShare(IShare $share) {
1163
		try {
1164
			$share->getFullId();
1165
		} catch (\UnexpectedValueException $e) {
1166
			throw new \InvalidArgumentException('Share does not have a full id');
1167
		}
1168
1169
		$event = new GenericEvent($share);
1170
		$this->legacyDispatcher->dispatch('OCP\Share::preUnshare', $event);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1170
		$this->legacyDispatcher->/** @scrutinizer ignore-call */ 
1171
                           dispatch('OCP\Share::preUnshare', $event);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
'OCP\Share::preUnshare' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1170
		$this->legacyDispatcher->dispatch(/** @scrutinizer ignore-type */ 'OCP\Share::preUnshare', $event);
Loading history...
1171
1172
		// Get all children and delete them as well
1173
		$deletedShares = $this->deleteChildren($share);
1174
1175
		// Do the actual delete
1176
		$provider = $this->factory->getProviderForType($share->getShareType());
1177
		$provider->delete($share);
1178
1179
		$this->dispatcher->dispatchTyped(new Share\Events\ShareDeletedEvent($share));
1180
1181
		// All the deleted shares caused by this delete
1182
		$deletedShares[] = $share;
1183
1184
		// Emit post hook
1185
		$event->setArgument('deletedShares', $deletedShares);
1186
		$this->legacyDispatcher->dispatch('OCP\Share::postUnshare', $event);
1187
	}
1188
1189
1190
	/**
1191
	 * Unshare a file as the recipient.
1192
	 * This can be different from a regular delete for example when one of
1193
	 * the users in a groups deletes that share. But the provider should
1194
	 * handle this.
1195
	 *
1196
	 * @param IShare $share
1197
	 * @param string $recipientId
1198
	 */
1199
	public function deleteFromSelf(IShare $share, $recipientId) {
1200
		[$providerId, ] = $this->splitFullId($share->getFullId());
1201
		$provider = $this->factory->getProvider($providerId);
1202
1203
		$provider->deleteFromSelf($share, $recipientId);
1204
		$event = new GenericEvent($share);
1205
		$this->legacyDispatcher->dispatch('OCP\Share::postUnshareFromSelf', $event);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1205
		$this->legacyDispatcher->/** @scrutinizer ignore-call */ 
1206
                           dispatch('OCP\Share::postUnshareFromSelf', $event);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
'OCP\Share::postUnshareFromSelf' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1205
		$this->legacyDispatcher->dispatch(/** @scrutinizer ignore-type */ 'OCP\Share::postUnshareFromSelf', $event);
Loading history...
1206
	}
1207
1208
	public function restoreShare(IShare $share, string $recipientId): IShare {
1209
		[$providerId, ] = $this->splitFullId($share->getFullId());
1210
		$provider = $this->factory->getProvider($providerId);
1211
1212
		return $provider->restore($share, $recipientId);
1213
	}
1214
1215
	/**
1216
	 * @inheritdoc
1217
	 */
1218
	public function moveShare(IShare $share, $recipientId) {
1219
		if ($share->getShareType() === IShare::TYPE_LINK) {
1220
			throw new \InvalidArgumentException('Can’t change target of link share');
1221
		}
1222
1223
		if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) {
1224
			throw new \InvalidArgumentException('Invalid recipient');
1225
		}
1226
1227
		if ($share->getShareType() === IShare::TYPE_GROUP) {
1228
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1229
			if (is_null($sharedWith)) {
1230
				throw new \InvalidArgumentException('Group "' . $share->getSharedWith() . '" does not exist');
1231
			}
1232
			$recipient = $this->userManager->get($recipientId);
1233
			if (!$sharedWith->inGroup($recipient)) {
1234
				throw new \InvalidArgumentException('Invalid recipient');
1235
			}
1236
		}
1237
1238
		[$providerId, ] = $this->splitFullId($share->getFullId());
1239
		$provider = $this->factory->getProvider($providerId);
1240
1241
		return $provider->move($share, $recipientId);
1242
	}
1243
1244
	public function getSharesInFolder($userId, Folder $node, $reshares = false) {
1245
		$providers = $this->factory->getAllProviders();
1246
1247
		return array_reduce($providers, function ($shares, IShareProvider $provider) use ($userId, $node, $reshares) {
1248
			$newShares = $provider->getSharesInFolder($userId, $node, $reshares);
1249
			foreach ($newShares as $fid => $data) {
1250
				if (!isset($shares[$fid])) {
1251
					$shares[$fid] = [];
1252
				}
1253
1254
				$shares[$fid] = array_merge($shares[$fid], $data);
1255
			}
1256
			return $shares;
1257
		}, []);
1258
	}
1259
1260
	/**
1261
	 * @inheritdoc
1262
	 */
1263
	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
1264
		if ($path !== null &&
1265
				!($path instanceof \OCP\Files\File) &&
1266
				!($path instanceof \OCP\Files\Folder)) {
1267
			throw new \InvalidArgumentException('invalid path');
1268
		}
1269
1270
		try {
1271
			$provider = $this->factory->getProviderForType($shareType);
1272
		} catch (ProviderException $e) {
1273
			return [];
1274
		}
1275
1276
		$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1277
1278
		/*
1279
		 * Work around so we don't return expired shares but still follow
1280
		 * proper pagination.
1281
		 */
1282
1283
		$shares2 = [];
1284
1285
		while (true) {
1286
			$added = 0;
1287
			foreach ($shares as $share) {
1288
				try {
1289
					$this->checkExpireDate($share);
1290
				} catch (ShareNotFound $e) {
1291
					//Ignore since this basically means the share is deleted
1292
					continue;
1293
				}
1294
1295
				$added++;
1296
				$shares2[] = $share;
1297
1298
				if (count($shares2) === $limit) {
1299
					break;
1300
				}
1301
			}
1302
1303
			// If we did not fetch more shares than the limit then there are no more shares
1304
			if (count($shares) < $limit) {
1305
				break;
1306
			}
1307
1308
			if (count($shares2) === $limit) {
1309
				break;
1310
			}
1311
1312
			// If there was no limit on the select we are done
1313
			if ($limit === -1) {
1314
				break;
1315
			}
1316
1317
			$offset += $added;
1318
1319
			// Fetch again $limit shares
1320
			$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1321
1322
			// No more shares means we are done
1323
			if (empty($shares)) {
1324
				break;
1325
			}
1326
		}
1327
1328
		$shares = $shares2;
1329
1330
		return $shares;
1331
	}
1332
1333
	/**
1334
	 * @inheritdoc
1335
	 */
1336
	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1337
		try {
1338
			$provider = $this->factory->getProviderForType($shareType);
1339
		} catch (ProviderException $e) {
1340
			return [];
1341
		}
1342
1343
		$shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1344
1345
		// remove all shares which are already expired
1346
		foreach ($shares as $key => $share) {
1347
			try {
1348
				$this->checkExpireDate($share);
1349
			} catch (ShareNotFound $e) {
1350
				unset($shares[$key]);
1351
			}
1352
		}
1353
1354
		return $shares;
1355
	}
1356
1357
	/**
1358
	 * @inheritdoc
1359
	 */
1360
	public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1361
		$shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1362
1363
		// Only get deleted shares
1364
		$shares = array_filter($shares, function (IShare $share) {
1365
			return $share->getPermissions() === 0;
1366
		});
1367
1368
		// Only get shares where the owner still exists
1369
		$shares = array_filter($shares, function (IShare $share) {
1370
			return $this->userManager->userExists($share->getShareOwner());
1371
		});
1372
1373
		return $shares;
1374
	}
1375
1376
	/**
1377
	 * @inheritdoc
1378
	 */
1379
	public function getShareById($id, $recipient = null) {
1380
		if ($id === null) {
1381
			throw new ShareNotFound();
1382
		}
1383
1384
		[$providerId, $id] = $this->splitFullId($id);
1385
1386
		try {
1387
			$provider = $this->factory->getProvider($providerId);
1388
		} catch (ProviderException $e) {
1389
			throw new ShareNotFound();
1390
		}
1391
1392
		$share = $provider->getShareById($id, $recipient);
0 ignored issues
show
Bug introduced by
$id of type string is incompatible with the type integer expected by parameter $id of OCP\Share\IShareProvider::getShareById(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1392
		$share = $provider->getShareById(/** @scrutinizer ignore-type */ $id, $recipient);
Loading history...
1393
1394
		$this->checkExpireDate($share);
1395
1396
		return $share;
1397
	}
1398
1399
	/**
1400
	 * Get all the shares for a given path
1401
	 *
1402
	 * @param \OCP\Files\Node $path
1403
	 * @param int $page
1404
	 * @param int $perPage
1405
	 *
1406
	 * @return Share[]
1407
	 */
1408
	public function getSharesByPath(\OCP\Files\Node $path, $page = 0, $perPage = 50) {
0 ignored issues
show
Unused Code introduced by
The parameter $perPage is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1408
	public function getSharesByPath(\OCP\Files\Node $path, $page = 0, /** @scrutinizer ignore-unused */ $perPage = 50) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $page is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1408
	public function getSharesByPath(\OCP\Files\Node $path, /** @scrutinizer ignore-unused */ $page = 0, $perPage = 50) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $path is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1408
	public function getSharesByPath(/** @scrutinizer ignore-unused */ \OCP\Files\Node $path, $page = 0, $perPage = 50) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1409
		return [];
1410
	}
1411
1412
	/**
1413
	 * Get the share by token possible with password
1414
	 *
1415
	 * @param string $token
1416
	 * @return IShare
1417
	 *
1418
	 * @throws ShareNotFound
1419
	 */
1420
	public function getShareByToken($token) {
1421
		// tokens can't be valid local user names
1422
		if ($this->userManager->userExists($token)) {
1423
			throw new ShareNotFound();
1424
		}
1425
		$share = null;
1426
		try {
1427
			if ($this->shareApiAllowLinks()) {
1428
				$provider = $this->factory->getProviderForType(IShare::TYPE_LINK);
1429
				$share = $provider->getShareByToken($token);
1430
			}
1431
		} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1432
		} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1433
		}
1434
1435
1436
		// If it is not a link share try to fetch a federated share by token
1437
		if ($share === null) {
1438
			try {
1439
				$provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE);
1440
				$share = $provider->getShareByToken($token);
1441
			} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1442
			} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1443
			}
1444
		}
1445
1446
		// If it is not a link share try to fetch a mail share by token
1447
		if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
1448
			try {
1449
				$provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL);
1450
				$share = $provider->getShareByToken($token);
1451
			} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1452
			} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1453
			}
1454
		}
1455
1456
		if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
1457
			try {
1458
				$provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE);
1459
				$share = $provider->getShareByToken($token);
1460
			} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1461
			} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1462
			}
1463
		}
1464
1465
		if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) {
1466
			try {
1467
				$provider = $this->factory->getProviderForType(IShare::TYPE_ROOM);
1468
				$share = $provider->getShareByToken($token);
1469
			} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1470
			} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1471
			}
1472
		}
1473
1474
		if ($share === null) {
1475
			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1476
		}
1477
1478
		$this->checkExpireDate($share);
1479
1480
		/*
1481
		 * Reduce the permissions for link shares if public upload is not enabled
1482
		 */
1483
		if ($share->getShareType() === IShare::TYPE_LINK &&
1484
			!$this->shareApiLinkAllowPublicUpload()) {
1485
			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1486
		}
1487
1488
		return $share;
1489
	}
1490
1491
	protected function checkExpireDate($share) {
1492
		if ($share->isExpired()) {
1493
			$this->deleteShare($share);
1494
			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1495
		}
1496
	}
1497
1498
	/**
1499
	 * Verify the password of a public share
1500
	 *
1501
	 * @param IShare $share
1502
	 * @param string $password
1503
	 * @return bool
1504
	 */
1505
	public function checkPassword(IShare $share, $password) {
1506
		$passwordProtected = $share->getShareType() !== IShare::TYPE_LINK
1507
							 || $share->getShareType() !== IShare::TYPE_EMAIL
1508
							 || $share->getShareType() !== IShare::TYPE_CIRCLE;
1509
		if (!$passwordProtected) {
1510
			//TODO maybe exception?
1511
			return false;
1512
		}
1513
1514
		if ($password === null || $share->getPassword() === null) {
0 ignored issues
show
introduced by
The condition $share->getPassword() === null is always false.
Loading history...
1515
			return false;
1516
		}
1517
1518
		$newHash = '';
1519
		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1520
			return false;
1521
		}
1522
1523
		if (!empty($newHash)) {
1524
			$share->setPassword($newHash);
1525
			$provider = $this->factory->getProviderForType($share->getShareType());
1526
			$provider->update($share);
1527
		}
1528
1529
		return true;
1530
	}
1531
1532
	/**
1533
	 * @inheritdoc
1534
	 */
1535
	public function userDeleted($uid) {
1536
		$types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
1537
1538
		foreach ($types as $type) {
1539
			try {
1540
				$provider = $this->factory->getProviderForType($type);
1541
			} catch (ProviderException $e) {
1542
				continue;
1543
			}
1544
			$provider->userDeleted($uid, $type);
1545
		}
1546
	}
1547
1548
	/**
1549
	 * @inheritdoc
1550
	 */
1551
	public function groupDeleted($gid) {
1552
		$provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
1553
		$provider->groupDeleted($gid);
1554
1555
		$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1556
		if ($excludedGroups === '') {
1557
			return;
1558
		}
1559
1560
		$excludedGroups = json_decode($excludedGroups, true);
1561
		if (json_last_error() !== JSON_ERROR_NONE) {
1562
			return;
1563
		}
1564
1565
		$excludedGroups = array_diff($excludedGroups, [$gid]);
1566
		$this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
1567
	}
1568
1569
	/**
1570
	 * @inheritdoc
1571
	 */
1572
	public function userDeletedFromGroup($uid, $gid) {
1573
		$provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
1574
		$provider->userDeletedFromGroup($uid, $gid);
1575
	}
1576
1577
	/**
1578
	 * Get access list to a path. This means
1579
	 * all the users that can access a given path.
1580
	 *
1581
	 * Consider:
1582
	 * -root
1583
	 * |-folder1 (23)
1584
	 *  |-folder2 (32)
1585
	 *   |-fileA (42)
1586
	 *
1587
	 * fileA is shared with user1 and user1@server1
1588
	 * folder2 is shared with group2 (user4 is a member of group2)
1589
	 * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
1590
	 *
1591
	 * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
1592
	 * [
1593
	 *  users  => [
1594
	 *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
1595
	 *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
1596
	 *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
1597
	 *  ],
1598
	 *  remote => [
1599
	 *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
1600
	 *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
1601
	 *  ],
1602
	 *  public => bool
1603
	 *  mail => bool
1604
	 * ]
1605
	 *
1606
	 * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
1607
	 * [
1608
	 *  users  => ['user1', 'user2', 'user4'],
1609
	 *  remote => bool,
1610
	 *  public => bool
1611
	 *  mail => bool
1612
	 * ]
1613
	 *
1614
	 * This is required for encryption/activity
1615
	 *
1616
	 * @param \OCP\Files\Node $path
1617
	 * @param bool $recursive Should we check all parent folders as well
1618
	 * @param bool $currentAccess Ensure the recipient has access to the file (e.g. did not unshare it)
1619
	 * @return array
1620
	 */
1621
	public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1622
		$owner = $path->getOwner();
1623
1624
		if ($owner === null) {
1625
			return [];
1626
		}
1627
1628
		$owner = $owner->getUID();
1629
1630
		if ($currentAccess) {
1631
			$al = ['users' => [], 'remote' => [], 'public' => false];
1632
		} else {
1633
			$al = ['users' => [], 'remote' => false, 'public' => false];
1634
		}
1635
		if (!$this->userManager->userExists($owner)) {
1636
			return $al;
1637
		}
1638
1639
		//Get node for the owner and correct the owner in case of external storages
1640
		$userFolder = $this->rootFolder->getUserFolder($owner);
1641
		if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1642
			$nodes = $userFolder->getById($path->getId());
1643
			$path = array_shift($nodes);
1644
			if ($path->getOwner() === null) {
1645
				return [];
1646
			}
1647
			$owner = $path->getOwner()->getUID();
1648
		}
1649
1650
		$providers = $this->factory->getAllProviders();
1651
1652
		/** @var Node[] $nodes */
1653
		$nodes = [];
1654
1655
1656
		if ($currentAccess) {
1657
			$ownerPath = $path->getPath();
1658
			$ownerPath = explode('/', $ownerPath, 4);
1659
			if (count($ownerPath) < 4) {
1660
				$ownerPath = '';
1661
			} else {
1662
				$ownerPath = $ownerPath[3];
1663
			}
1664
			$al['users'][$owner] = [
1665
				'node_id' => $path->getId(),
1666
				'node_path' => '/' . $ownerPath,
1667
			];
1668
		} else {
1669
			$al['users'][] = $owner;
1670
		}
1671
1672
		// Collect all the shares
1673
		while ($path->getPath() !== $userFolder->getPath()) {
1674
			$nodes[] = $path;
1675
			if (!$recursive) {
1676
				break;
1677
			}
1678
			$path = $path->getParent();
1679
		}
1680
1681
		foreach ($providers as $provider) {
1682
			$tmp = $provider->getAccessList($nodes, $currentAccess);
1683
1684
			foreach ($tmp as $k => $v) {
1685
				if (isset($al[$k])) {
1686
					if (is_array($al[$k])) {
1687
						if ($currentAccess) {
1688
							$al[$k] += $v;
1689
						} else {
1690
							$al[$k] = array_merge($al[$k], $v);
1691
							$al[$k] = array_unique($al[$k]);
1692
							$al[$k] = array_values($al[$k]);
1693
						}
1694
					} else {
1695
						$al[$k] = $al[$k] || $v;
1696
					}
1697
				} else {
1698
					$al[$k] = $v;
1699
				}
1700
			}
1701
		}
1702
1703
		return $al;
1704
	}
1705
1706
	/**
1707
	 * Create a new share
1708
	 *
1709
	 * @return IShare
1710
	 */
1711
	public function newShare() {
1712
		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1713
	}
1714
1715
	/**
1716
	 * Is the share API enabled
1717
	 *
1718
	 * @return bool
1719
	 */
1720
	public function shareApiEnabled() {
1721
		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1722
	}
1723
1724
	/**
1725
	 * Is public link sharing enabled
1726
	 *
1727
	 * @return bool
1728
	 */
1729
	public function shareApiAllowLinks() {
1730
		return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
1731
	}
1732
1733
	/**
1734
	 * Is password on public link requires
1735
	 *
1736
	 * @return bool
1737
	 */
1738
	public function shareApiLinkEnforcePassword() {
1739
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1740
	}
1741
1742
	/**
1743
	 * Is default link expire date enabled
1744
	 *
1745
	 * @return bool
1746
	 */
1747
	public function shareApiLinkDefaultExpireDate() {
1748
		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1749
	}
1750
1751
	/**
1752
	 * Is default link expire date enforced
1753
	 *`
1754
	 * @return bool
1755
	 */
1756
	public function shareApiLinkDefaultExpireDateEnforced() {
1757
		return $this->shareApiLinkDefaultExpireDate() &&
1758
			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1759
	}
1760
1761
1762
	/**
1763
	 * Number of default link expire days
1764
	 * @return int
1765
	 */
1766
	public function shareApiLinkDefaultExpireDays() {
1767
		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1768
	}
1769
1770
	/**
1771
	 * Is default internal expire date enabled
1772
	 *
1773
	 * @return bool
1774
	 */
1775
	public function shareApiInternalDefaultExpireDate(): bool {
1776
		return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
1777
	}
1778
1779
	/**
1780
	 * Is default expire date enforced
1781
	 *`
1782
	 * @return bool
1783
	 */
1784
	public function shareApiInternalDefaultExpireDateEnforced(): bool {
1785
		return $this->shareApiInternalDefaultExpireDate() &&
1786
			$this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
1787
	}
1788
1789
1790
	/**
1791
	 * Number of default expire days
1792
	 * @return int
1793
	 */
1794
	public function shareApiInternalDefaultExpireDays(): int {
1795
		return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
1796
	}
1797
1798
	/**
1799
	 * Allow public upload on link shares
1800
	 *
1801
	 * @return bool
1802
	 */
1803
	public function shareApiLinkAllowPublicUpload() {
1804
		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1805
	}
1806
1807
	/**
1808
	 * check if user can only share with group members
1809
	 * @return bool
1810
	 */
1811
	public function shareWithGroupMembersOnly() {
1812
		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1813
	}
1814
1815
	/**
1816
	 * Check if users can share with groups
1817
	 * @return bool
1818
	 */
1819
	public function allowGroupSharing() {
1820
		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1821
	}
1822
1823
	public function allowEnumeration(): bool {
1824
		return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
1825
	}
1826
1827
	public function limitEnumerationToGroups(): bool {
1828
		return $this->allowEnumeration() &&
1829
			$this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
1830
	}
1831
1832
	public function limitEnumerationToPhone(): bool {
1833
		return $this->allowEnumeration() &&
1834
			$this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
1835
	}
1836
1837
	public function allowEnumerationFullMatch(): bool {
1838
		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
1839
	}
1840
1841
	/**
1842
	 * Copied from \OC_Util::isSharingDisabledForUser
1843
	 *
1844
	 * TODO: Deprecate fuction from OC_Util
1845
	 *
1846
	 * @param string $userId
1847
	 * @return bool
1848
	 */
1849
	public function sharingDisabledForUser($userId) {
1850
		if ($userId === null) {
0 ignored issues
show
introduced by
The condition $userId === null is always false.
Loading history...
1851
			return false;
1852
		}
1853
1854
		if (isset($this->sharingDisabledForUsersCache[$userId])) {
1855
			return $this->sharingDisabledForUsersCache[$userId];
1856
		}
1857
1858
		if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
1859
			$groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1860
			$excludedGroups = json_decode($groupsList);
1861
			if (is_null($excludedGroups)) {
1862
				$excludedGroups = explode(',', $groupsList);
1863
				$newValue = json_encode($excludedGroups);
1864
				$this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
1865
			}
1866
			$user = $this->userManager->get($userId);
1867
			$usersGroups = $this->groupManager->getUserGroupIds($user);
1868
			if (!empty($usersGroups)) {
1869
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
1870
				// if the user is only in groups which are disabled for sharing then
1871
				// sharing is also disabled for the user
1872
				if (empty($remainingGroups)) {
1873
					$this->sharingDisabledForUsersCache[$userId] = true;
1874
					return true;
1875
				}
1876
			}
1877
		}
1878
1879
		$this->sharingDisabledForUsersCache[$userId] = false;
1880
		return false;
1881
	}
1882
1883
	/**
1884
	 * @inheritdoc
1885
	 */
1886
	public function outgoingServer2ServerSharesAllowed() {
1887
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1888
	}
1889
1890
	/**
1891
	 * @inheritdoc
1892
	 */
1893
	public function outgoingServer2ServerGroupSharesAllowed() {
1894
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1895
	}
1896
1897
	/**
1898
	 * @inheritdoc
1899
	 */
1900
	public function shareProviderExists($shareType) {
1901
		try {
1902
			$this->factory->getProviderForType($shareType);
1903
		} catch (ProviderException $e) {
1904
			return false;
1905
		}
1906
1907
		return true;
1908
	}
1909
1910
	public function registerShareProvider(string $shareProviderClass): void {
1911
		$this->factory->registerProvider($shareProviderClass);
1912
	}
1913
1914
	public function getAllShares(): iterable {
1915
		$providers = $this->factory->getAllProviders();
1916
1917
		foreach ($providers as $provider) {
1918
			yield from $provider->getAllShares();
1919
		}
1920
	}
1921
}
1922