Passed
Push — master ( 0f0dfc...2f5810 )
by Roeland
23:19 queued 13:50
created

Manager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 34
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 17
nc 1
nop 15
dl 0
loc 34
rs 9.7
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
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 Daniel Calviño Sánchez <[email protected]>
9
 * @author Jan-Christoph Borchardt <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Julius Härtl <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Maxence Lange <[email protected]>
14
 * @author Maxence Lange <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Pauli Järvinen <[email protected]>
17
 * @author Robin Appelman <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Stephan Müller <[email protected]>
20
 * @author Vincent Petry <[email protected]>
21
 *
22
 * @license AGPL-3.0
23
 *
24
 * This code is free software: you can redistribute it and/or modify
25
 * it under the terms of the GNU Affero General Public License, version 3,
26
 * as published by the Free Software Foundation.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License, version 3,
34
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
35
 *
36
 */
37
38
namespace OC\Share20;
39
40
use OC\Cache\CappedMemoryCache;
41
use OC\Files\Mount\MoveableMount;
42
use OC\HintException;
43
use OC\Share20\Exception\ProviderException;
44
use OCP\Files\File;
45
use OCP\Files\Folder;
46
use OCP\Files\IRootFolder;
47
use OCP\Files\Mount\IMountManager;
48
use OCP\Files\Node;
49
use OCP\IConfig;
50
use OCP\IGroupManager;
51
use OCP\IL10N;
52
use OCP\ILogger;
53
use OCP\IURLGenerator;
54
use OCP\IUser;
55
use OCP\IUserManager;
56
use OCP\L10N\IFactory;
57
use OCP\Mail\IMailer;
58
use OCP\Security\IHasher;
59
use OCP\Security\ISecureRandom;
60
use OCP\Share\Exceptions\GenericShareException;
61
use OCP\Share\Exceptions\ShareNotFound;
62
use OCP\Share\IManager;
63
use OCP\Share\IProviderFactory;
64
use OCP\Share\IShare;
65
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
66
use Symfony\Component\EventDispatcher\GenericEvent;
67
use OCP\Share\IShareProvider;
68
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...
69
70
/**
71
 * This class is the communication hub for all sharing related operations.
72
 */
73
class Manager implements IManager {
74
75
	/** @var IProviderFactory */
76
	private $factory;
77
	/** @var ILogger */
78
	private $logger;
79
	/** @var IConfig */
80
	private $config;
81
	/** @var ISecureRandom */
82
	private $secureRandom;
83
	/** @var IHasher */
84
	private $hasher;
85
	/** @var IMountManager */
86
	private $mountManager;
87
	/** @var IGroupManager */
88
	private $groupManager;
89
	/** @var IL10N */
90
	private $l;
91
	/** @var IFactory */
92
	private $l10nFactory;
93
	/** @var IUserManager */
94
	private $userManager;
95
	/** @var IRootFolder */
96
	private $rootFolder;
97
	/** @var CappedMemoryCache */
98
	private $sharingDisabledForUsersCache;
99
	/** @var EventDispatcherInterface */
100
	private $eventDispatcher;
101
	/** @var LegacyHooks */
102
	private $legacyHooks;
103
	/** @var IMailer */
104
	private $mailer;
105
	/** @var IURLGenerator */
106
	private $urlGenerator;
107
	/** @var \OC_Defaults */
108
	private $defaults;
109
110
111
	/**
112
	 * Manager constructor.
113
	 *
114
	 * @param ILogger $logger
115
	 * @param IConfig $config
116
	 * @param ISecureRandom $secureRandom
117
	 * @param IHasher $hasher
118
	 * @param IMountManager $mountManager
119
	 * @param IGroupManager $groupManager
120
	 * @param IL10N $l
121
	 * @param IFactory $l10nFactory
122
	 * @param IProviderFactory $factory
123
	 * @param IUserManager $userManager
124
	 * @param IRootFolder $rootFolder
125
	 * @param EventDispatcherInterface $eventDispatcher
126
	 * @param IMailer $mailer
127
	 * @param IURLGenerator $urlGenerator
128
	 * @param \OC_Defaults $defaults
129
	 */
130
	public function __construct(
131
			ILogger $logger,
132
			IConfig $config,
133
			ISecureRandom $secureRandom,
134
			IHasher $hasher,
135
			IMountManager $mountManager,
136
			IGroupManager $groupManager,
137
			IL10N $l,
138
			IFactory $l10nFactory,
139
			IProviderFactory $factory,
140
			IUserManager $userManager,
141
			IRootFolder $rootFolder,
142
			EventDispatcherInterface $eventDispatcher,
143
			IMailer $mailer,
144
			IURLGenerator $urlGenerator,
145
			\OC_Defaults $defaults
146
	) {
147
		$this->logger = $logger;
148
		$this->config = $config;
149
		$this->secureRandom = $secureRandom;
150
		$this->hasher = $hasher;
151
		$this->mountManager = $mountManager;
152
		$this->groupManager = $groupManager;
153
		$this->l = $l;
154
		$this->l10nFactory = $l10nFactory;
155
		$this->factory = $factory;
156
		$this->userManager = $userManager;
157
		$this->rootFolder = $rootFolder;
158
		$this->eventDispatcher = $eventDispatcher;
159
		$this->sharingDisabledForUsersCache = new CappedMemoryCache();
160
		$this->legacyHooks = new LegacyHooks($this->eventDispatcher);
161
		$this->mailer = $mailer;
162
		$this->urlGenerator = $urlGenerator;
163
		$this->defaults = $defaults;
164
	}
165
166
	/**
167
	 * Convert from a full share id to a tuple (providerId, shareId)
168
	 *
169
	 * @param string $id
170
	 * @return string[]
171
	 */
172
	private function splitFullId($id) {
173
		return explode(':', $id, 2);
174
	}
175
176
	/**
177
	 * Verify if a password meets all requirements
178
	 *
179
	 * @param string $password
180
	 * @throws \Exception
181
	 */
182
	protected function verifyPassword($password) {
183
		if ($password === null) {
0 ignored issues
show
introduced by
The condition $password === null is always false.
Loading history...
184
			// No password is set, check if this is allowed.
185
			if ($this->shareApiLinkEnforcePassword()) {
186
				throw new \InvalidArgumentException('Passwords are enforced for link shares');
187
			}
188
189
			return;
190
		}
191
192
		// Let others verify the password
193
		try {
194
			$event = new GenericEvent($password);
195
			$this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
196
		} catch (HintException $e) {
197
			throw new \Exception($e->getHint());
198
		}
199
	}
200
201
	/**
202
	 * Check for generic requirements before creating a share
203
	 *
204
	 * @param \OCP\Share\IShare $share
205
	 * @throws \InvalidArgumentException
206
	 * @throws GenericShareException
207
	 *
208
	 * @suppress PhanUndeclaredClassMethod
209
	 */
210
	protected function generalCreateChecks(\OCP\Share\IShare $share) {
211
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
212
			// We expect a valid user as sharedWith for user shares
213
			if (!$this->userManager->userExists($share->getSharedWith())) {
214
				throw new \InvalidArgumentException('SharedWith is not a valid user');
215
			}
216
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
217
			// We expect a valid group as sharedWith for group shares
218
			if (!$this->groupManager->groupExists($share->getSharedWith())) {
219
				throw new \InvalidArgumentException('SharedWith is not a valid group');
220
			}
221
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
222
			if ($share->getSharedWith() !== null) {
0 ignored issues
show
introduced by
The condition $share->getSharedWith() !== null is always true.
Loading history...
223
				throw new \InvalidArgumentException('SharedWith should be empty');
224
			}
225
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
226
			if ($share->getSharedWith() === null) {
0 ignored issues
show
introduced by
The condition $share->getSharedWith() === null is always false.
Loading history...
227
				throw new \InvalidArgumentException('SharedWith should not be empty');
228
			}
229
		}  else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE_GROUP) {
230
			if ($share->getSharedWith() === null) {
0 ignored issues
show
introduced by
The condition $share->getSharedWith() === null is always false.
Loading history...
231
				throw new \InvalidArgumentException('SharedWith should not be empty');
232
			}
233
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
234
			if ($share->getSharedWith() === null) {
0 ignored issues
show
introduced by
The condition $share->getSharedWith() === null is always false.
Loading history...
235
				throw new \InvalidArgumentException('SharedWith should not be empty');
236
			}
237
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_CIRCLE) {
238
			$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...
239
			if ($circle === null) {
240
				throw new \InvalidArgumentException('SharedWith is not a valid circle');
241
			}
242
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_ROOM) {
243
		} else {
244
			// We can't handle other types yet
245
			throw new \InvalidArgumentException('unknown share type');
246
		}
247
248
		// Verify the initiator of the share is set
249
		if ($share->getSharedBy() === null) {
0 ignored issues
show
introduced by
The condition $share->getSharedBy() === null is always false.
Loading history...
250
			throw new \InvalidArgumentException('SharedBy should be set');
251
		}
252
253
		// Cannot share with yourself
254
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
255
			$share->getSharedWith() === $share->getSharedBy()) {
256
			throw new \InvalidArgumentException('Can’t share with yourself');
257
		}
258
259
		// The path should be set
260
		if ($share->getNode() === null) {
261
			throw new \InvalidArgumentException('Path should be set');
262
		}
263
264
		// And it should be a file or a folder
265
		if (!($share->getNode() instanceof \OCP\Files\File) &&
266
				!($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...
267
			throw new \InvalidArgumentException('Path should be either a file or a folder');
268
		}
269
270
		// And you can't share your rootfolder
271
		if ($this->userManager->userExists($share->getSharedBy())) {
272
			$sharedPath = $this->rootFolder->getUserFolder($share->getSharedBy())->getPath();
273
		} else {
274
			$sharedPath = $this->rootFolder->getUserFolder($share->getShareOwner())->getPath();
275
		}
276
		if ($sharedPath === $share->getNode()->getPath()) {
277
			throw new \InvalidArgumentException('You can’t share your root folder');
278
		}
279
280
		// Check if we actually have share permissions
281
		if (!$share->getNode()->isShareable()) {
282
			$message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getPath()]);
283
			throw new GenericShareException($message_t, $message_t, 404);
284
		}
285
286
		// Permissions should be set
287
		if ($share->getPermissions() === null) {
0 ignored issues
show
introduced by
The condition $share->getPermissions() === null is always false.
Loading history...
288
			throw new \InvalidArgumentException('A share requires permissions');
289
		}
290
291
		/*
292
		 * Quick fix for #23536
293
		 * Non moveable mount points do not have update and delete permissions
294
		 * while we 'most likely' do have that on the storage.
295
		 */
296
		$permissions = $share->getNode()->getPermissions();
297
		$mount = $share->getNode()->getMountPoint();
298
		if (!($mount instanceof MoveableMount)) {
299
			$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
300
		}
301
302
		// Check that we do not share with more permissions than we have
303
		if ($share->getPermissions() & ~$permissions) {
304
			$message_t = $this->l->t('Can’t increase permissions of %s', [$share->getNode()->getPath()]);
305
			throw new GenericShareException($message_t, $message_t, 404);
306
		}
307
308
309
		// Check that read permissions are always set
310
		// Link shares are allowed to have no read permissions to allow upload to hidden folders
311
		$noReadPermissionRequired = $share->getShareType() === \OCP\Share::SHARE_TYPE_LINK
312
			|| $share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL;
313
		if (!$noReadPermissionRequired &&
314
			($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
315
			throw new \InvalidArgumentException('Shares need at least read permissions');
316
		}
317
318
		if ($share->getNode() instanceof \OCP\Files\File) {
319
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
320
				$message_t = $this->l->t('Files can’t be shared with delete permissions');
321
				throw new GenericShareException($message_t);
322
			}
323
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
324
				$message_t = $this->l->t('Files can’t be shared with create permissions');
325
				throw new GenericShareException($message_t);
326
			}
327
		}
328
	}
329
330
	/**
331
	 * Validate if the expiration date fits the system settings
332
	 *
333
	 * @param \OCP\Share\IShare $share The share to validate the expiration date of
334
	 * @return \OCP\Share\IShare The modified share object
335
	 * @throws GenericShareException
336
	 * @throws \InvalidArgumentException
337
	 * @throws \Exception
338
	 */
339
	protected function validateExpirationDate(\OCP\Share\IShare $share) {
340
341
		$expirationDate = $share->getExpirationDate();
342
343
		if ($expirationDate !== null) {
344
			//Make sure the expiration date is a date
345
			$expirationDate->setTime(0, 0, 0);
346
347
			$date = new \DateTime();
348
			$date->setTime(0, 0, 0);
349
			if ($date >= $expirationDate) {
350
				$message = $this->l->t('Expiration date is in the past');
351
				throw new GenericShareException($message, $message, 404);
352
			}
353
		}
354
355
		// If expiredate is empty set a default one if there is a default
356
		$fullId = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $fullId is dead and can be removed.
Loading history...
357
		try {
358
			$fullId = $share->getFullId();
359
		} catch (\UnexpectedValueException $e) {
360
			// This is a new share
361
		}
362
363
		if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
0 ignored issues
show
introduced by
The condition $fullId === null is always false.
Loading history...
364
			$expirationDate = new \DateTime();
365
			$expirationDate->setTime(0,0,0);
366
			$expirationDate->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
367
		}
368
369
		// If we enforce the expiration date check that is does not exceed
370
		if ($this->shareApiLinkDefaultExpireDateEnforced()) {
371
			if ($expirationDate === null) {
372
				throw new \InvalidArgumentException('Expiration date is enforced');
373
			}
374
375
			$date = new \DateTime();
376
			$date->setTime(0, 0, 0);
377
			$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
378
			if ($date < $expirationDate) {
379
				$message = $this->l->t('Can’t set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
380
				throw new GenericShareException($message, $message, 404);
381
			}
382
		}
383
384
		$accepted = true;
385
		$message = '';
386
		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
387
			'expirationDate' => &$expirationDate,
388
			'accepted' => &$accepted,
389
			'message' => &$message,
390
			'passwordSet' => $share->getPassword() !== null,
391
		]);
392
393
		if (!$accepted) {
0 ignored issues
show
introduced by
The condition $accepted is always true.
Loading history...
394
			throw new \Exception($message);
395
		}
396
397
		$share->setExpirationDate($expirationDate);
398
399
		return $share;
400
	}
401
402
	/**
403
	 * Check for pre share requirements for user shares
404
	 *
405
	 * @param \OCP\Share\IShare $share
406
	 * @throws \Exception
407
	 */
408
	protected function userCreateChecks(\OCP\Share\IShare $share) {
409
		// Check if we can share with group members only
410
		if ($this->shareWithGroupMembersOnly()) {
411
			$sharedBy = $this->userManager->get($share->getSharedBy());
412
			$sharedWith = $this->userManager->get($share->getSharedWith());
413
			// Verify we can share with this user
414
			$groups = array_intersect(
415
					$this->groupManager->getUserGroupIds($sharedBy),
416
					$this->groupManager->getUserGroupIds($sharedWith)
417
			);
418
			if (empty($groups)) {
419
				throw new \Exception('Sharing is only allowed with group members');
420
			}
421
		}
422
423
		/*
424
		 * TODO: Could be costly, fix
425
		 *
426
		 * Also this is not what we want in the future.. then we want to squash identical shares.
427
		 */
428
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_USER);
429
		$existingShares = $provider->getSharesByPath($share->getNode());
430
		foreach($existingShares as $existingShare) {
431
			// Ignore if it is the same share
432
			try {
433
				if ($existingShare->getFullId() === $share->getFullId()) {
434
					continue;
435
				}
436
			} catch (\UnexpectedValueException $e) {
437
				//Shares are not identical
438
			}
439
440
			// Identical share already existst
441
			if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
442
				throw new \Exception('Path is already shared with this user');
443
			}
444
445
			// The share is already shared with this user via a group share
446
			if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
447
				$group = $this->groupManager->get($existingShare->getSharedWith());
448
				if (!is_null($group)) {
449
					$user = $this->userManager->get($share->getSharedWith());
450
451
					if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
452
						throw new \Exception('Path is already shared with this user');
453
					}
454
				}
455
			}
456
		}
457
	}
458
459
	/**
460
	 * Check for pre share requirements for group shares
461
	 *
462
	 * @param \OCP\Share\IShare $share
463
	 * @throws \Exception
464
	 */
465
	protected function groupCreateChecks(\OCP\Share\IShare $share) {
466
		// Verify group shares are allowed
467
		if (!$this->allowGroupSharing()) {
468
			throw new \Exception('Group sharing is now allowed');
469
		}
470
471
		// Verify if the user can share with this group
472
		if ($this->shareWithGroupMembersOnly()) {
473
			$sharedBy = $this->userManager->get($share->getSharedBy());
474
			$sharedWith = $this->groupManager->get($share->getSharedWith());
475
			if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) {
476
				throw new \Exception('Sharing is only allowed within your own groups');
477
			}
478
		}
479
480
		/*
481
		 * TODO: Could be costly, fix
482
		 *
483
		 * Also this is not what we want in the future.. then we want to squash identical shares.
484
		 */
485
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
486
		$existingShares = $provider->getSharesByPath($share->getNode());
487
		foreach($existingShares as $existingShare) {
488
			try {
489
				if ($existingShare->getFullId() === $share->getFullId()) {
490
					continue;
491
				}
492
			} catch (\UnexpectedValueException $e) {
493
				//It is a new share so just continue
494
			}
495
496
			if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
497
				throw new \Exception('Path is already shared with this group');
498
			}
499
		}
500
	}
501
502
	/**
503
	 * Check for pre share requirements for link shares
504
	 *
505
	 * @param \OCP\Share\IShare $share
506
	 * @throws \Exception
507
	 */
508
	protected function linkCreateChecks(\OCP\Share\IShare $share) {
509
		// Are link shares allowed?
510
		if (!$this->shareApiAllowLinks()) {
511
			throw new \Exception('Link sharing is not allowed');
512
		}
513
514
		// Link shares by definition can't have share permissions
515
		if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) {
516
			throw new \InvalidArgumentException('Link shares can’t have reshare permissions');
517
		}
518
519
		// Check if public upload is allowed
520
		if (!$this->shareApiLinkAllowPublicUpload() &&
521
			($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
522
			throw new \InvalidArgumentException('Public upload is not allowed');
523
		}
524
	}
525
526
	/**
527
	 * To make sure we don't get invisible link shares we set the parent
528
	 * of a link if it is a reshare. This is a quick word around
529
	 * until we can properly display multiple link shares in the UI
530
	 *
531
	 * See: https://github.com/owncloud/core/issues/22295
532
	 *
533
	 * FIXME: Remove once multiple link shares can be properly displayed
534
	 *
535
	 * @param \OCP\Share\IShare $share
536
	 */
537
	protected function setLinkParent(\OCP\Share\IShare $share) {
538
539
		// No sense in checking if the method is not there.
540
		if (method_exists($share, 'setParent')) {
541
			$storage = $share->getNode()->getStorage();
542
			if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
543
				/** @var \OCA\Files_Sharing\SharedStorage $storage */
544
				$share->setParent($storage->getShareId());
545
			}
546
		}
547
	}
548
549
	/**
550
	 * @param File|Folder $path
551
	 */
552
	protected function pathCreateChecks($path) {
553
		// Make sure that we do not share a path that contains a shared mountpoint
554
		if ($path instanceof \OCP\Files\Folder) {
555
			$mounts = $this->mountManager->findIn($path->getPath());
556
			foreach($mounts as $mount) {
557
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
558
					throw new \InvalidArgumentException('Path contains files shared with you');
559
				}
560
			}
561
		}
562
	}
563
564
	/**
565
	 * Check if the user that is sharing can actually share
566
	 *
567
	 * @param \OCP\Share\IShare $share
568
	 * @throws \Exception
569
	 */
570
	protected function canShare(\OCP\Share\IShare $share) {
571
		if (!$this->shareApiEnabled()) {
572
			throw new \Exception('Sharing is disabled');
573
		}
574
575
		if ($this->sharingDisabledForUser($share->getSharedBy())) {
576
			throw new \Exception('Sharing is disabled for you');
577
		}
578
	}
579
580
	/**
581
	 * Share a path
582
	 *
583
	 * @param \OCP\Share\IShare $share
584
	 * @return Share The share object
585
	 * @throws \Exception
586
	 *
587
	 * TODO: handle link share permissions or check them
588
	 */
589
	public function createShare(\OCP\Share\IShare $share) {
590
		$this->canShare($share);
591
592
		$this->generalCreateChecks($share);
593
594
		// Verify if there are any issues with the path
595
		$this->pathCreateChecks($share->getNode());
596
597
		/*
598
		 * On creation of a share the owner is always the owner of the path
599
		 * Except for mounted federated shares.
600
		 */
601
		$storage = $share->getNode()->getStorage();
602
		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
603
			$parent = $share->getNode()->getParent();
604
			while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
605
				$parent = $parent->getParent();
606
			}
607
			$share->setShareOwner($parent->getOwner()->getUID());
608
		} else {
609
			$share->setShareOwner($share->getNode()->getOwner()->getUID());
610
		}
611
612
		//Verify share type
613
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
614
			$this->userCreateChecks($share);
615
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
616
			$this->groupCreateChecks($share);
617
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
618
			$this->linkCreateChecks($share);
619
			$this->setLinkParent($share);
620
621
			/*
622
			 * For now ignore a set token.
623
			 */
624
			$share->setToken(
625
				$this->secureRandom->generate(
626
					\OC\Share\Constants::TOKEN_LENGTH,
627
					\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
628
				)
629
			);
630
631
			//Verify the expiration date
632
			$this->validateExpirationDate($share);
633
634
			//Verify the password
635
			$this->verifyPassword($share->getPassword());
636
637
			// If a password is set. Hash it!
638
			if ($share->getPassword() !== null) {
0 ignored issues
show
introduced by
The condition $share->getPassword() !== null is always true.
Loading history...
639
				$share->setPassword($this->hasher->hash($share->getPassword()));
640
			}
641
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
642
			$share->setToken(
643
				$this->secureRandom->generate(
644
					\OC\Share\Constants::TOKEN_LENGTH,
645
					\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
646
				)
647
			);
648
		}
649
650
		// Cannot share with the owner
651
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
652
			$share->getSharedWith() === $share->getShareOwner()) {
653
			throw new \InvalidArgumentException('Can’t share with the share owner');
654
		}
655
656
		// Generate the target
657
		$target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName();
658
		$target = \OC\Files\Filesystem::normalizePath($target);
659
		$share->setTarget($target);
660
661
		// Pre share event
662
		$event = new GenericEvent($share);
663
		$this->eventDispatcher->dispatch('OCP\Share::preShare', $event);
664
		if ($event->isPropagationStopped() && $event->hasArgument('error')) {
665
			throw new \Exception($event->getArgument('error'));
666
		}
667
668
		$oldShare = $share;
669
		$provider = $this->factory->getProviderForType($share->getShareType());
670
		$share = $provider->create($share);
671
		//reuse the node we already have
672
		$share->setNode($oldShare->getNode());
673
674
		// Post share event
675
		$event = new GenericEvent($share);
676
		$this->eventDispatcher->dispatch('OCP\Share::postShare', $event);
677
678
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
679
			$mailSend = $share->getMailSend();
680
			if($mailSend === true) {
681
				$user = $this->userManager->get($share->getSharedWith());
682
				if ($user !== null) {
683
					$emailAddress = $user->getEMailAddress();
684
					if ($emailAddress !== null && $emailAddress !== '') {
685
						$userLang = $this->config->getUserValue($share->getSharedWith(), 'core', 'lang', null);
686
						$l = $this->l10nFactory->get('lib', $userLang);
687
						$this->sendMailNotification(
688
							$l,
689
							$share->getNode()->getName(),
690
							$this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]),
691
							$share->getSharedBy(),
692
							$emailAddress,
693
							$share->getExpirationDate()
694
						);
695
						$this->logger->debug('Sent share notification to ' . $emailAddress . ' for share with ID ' . $share->getId(), ['app' => 'share']);
696
					} else {
697
						$this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because email address is not set.', ['app' => 'share']);
698
					}
699
				} else {
700
					$this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because user could not be found.', ['app' => 'share']);
701
				}
702
			} else {
703
				$this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']);
704
			}
705
		}
706
707
		return $share;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $share returns the type OCP\Share\IShare which is incompatible with the documented return type OCP\Share.
Loading history...
708
	}
709
710
	/**
711
	 * Send mail notifications
712
	 *
713
	 * This method will catch and log mail transmission errors
714
	 *
715
	 * @param IL10N $l Language of the recipient
716
	 * @param string $filename file/folder name
717
	 * @param string $link link to the file/folder
718
	 * @param string $initiator user ID of share sender
719
	 * @param string $shareWith email address of share receiver
720
	 * @param \DateTime|null $expiration
721
	 */
722
	protected function sendMailNotification(IL10N $l,
723
											$filename,
724
											$link,
725
											$initiator,
726
											$shareWith,
727
											\DateTime $expiration = null) {
728
		$initiatorUser = $this->userManager->get($initiator);
729
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
730
731
		$message = $this->mailer->createMessage();
732
733
		$emailTemplate = $this->mailer->createEMailTemplate('files_sharing.RecipientNotification', [
734
			'filename' => $filename,
735
			'link' => $link,
736
			'initiator' => $initiatorDisplayName,
737
			'expiration' => $expiration,
738
			'shareWith' => $shareWith,
739
		]);
740
741
		$emailTemplate->setSubject($l->t('%1$s shared »%2$s« with you', array($initiatorDisplayName, $filename)));
742
		$emailTemplate->addHeader();
743
		$emailTemplate->addHeading($l->t('%1$s shared »%2$s« with you', [$initiatorDisplayName, $filename]), false);
744
		$text = $l->t('%1$s shared »%2$s« with you.', [$initiatorDisplayName, $filename]);
745
746
		$emailTemplate->addBodyText(
747
			htmlspecialchars($text . ' ' . $l->t('Click the button below to open it.')),
748
			$text
749
		);
750
		$emailTemplate->addBodyButton(
751
			$l->t('Open »%s«', [$filename]),
752
			$link
753
		);
754
755
		$message->setTo([$shareWith]);
756
757
		// The "From" contains the sharers name
758
		$instanceName = $this->defaults->getName();
759
		$senderName = $l->t(
760
			'%1$s via %2$s',
761
			[
762
				$initiatorDisplayName,
763
				$instanceName
764
			]
765
		);
766
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
767
768
		// The "Reply-To" is set to the sharer if an mail address is configured
769
		// also the default footer contains a "Do not reply" which needs to be adjusted.
770
		$initiatorEmail = $initiatorUser->getEMailAddress();
771
		if($initiatorEmail !== null) {
772
			$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
773
			$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
774
		} else {
775
			$emailTemplate->addFooter();
776
		}
777
778
		$message->useTemplate($emailTemplate);
779
		try {
780
			$failedRecipients = $this->mailer->send($message);
781
			if(!empty($failedRecipients)) {
782
				$this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients));
783
				return;
784
			}
785
		} catch (\Exception $e) {
786
			$this->logger->logException($e, ['message' => 'Share notification mail could not be sent']);
787
		}
788
	}
789
790
	/**
791
	 * Update a share
792
	 *
793
	 * @param \OCP\Share\IShare $share
794
	 * @return \OCP\Share\IShare The share object
795
	 * @throws \InvalidArgumentException
796
	 */
797
	public function updateShare(\OCP\Share\IShare $share) {
798
		$expirationDateUpdated = false;
799
800
		$this->canShare($share);
801
802
		try {
803
			$originalShare = $this->getShareById($share->getFullId());
804
		} catch (\UnexpectedValueException $e) {
805
			throw new \InvalidArgumentException('Share does not have a full id');
806
		}
807
808
		// We can't change the share type!
809
		if ($share->getShareType() !== $originalShare->getShareType()) {
810
			throw new \InvalidArgumentException('Can’t change share type');
811
		}
812
813
		// We can only change the recipient on user shares
814
		if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
815
		    $share->getShareType() !== \OCP\Share::SHARE_TYPE_USER) {
816
			throw new \InvalidArgumentException('Can only update recipient on user shares');
817
		}
818
819
		// Cannot share with the owner
820
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
821
			$share->getSharedWith() === $share->getShareOwner()) {
822
			throw new \InvalidArgumentException('Can’t share with the share owner');
823
		}
824
825
		$this->generalCreateChecks($share);
826
827
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
828
			$this->userCreateChecks($share);
829
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
830
			$this->groupCreateChecks($share);
831
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
832
			$this->linkCreateChecks($share);
833
834
			$this->updateSharePasswordIfNeeded($share, $originalShare);
835
836
			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
837
				//Verify the expiration date
838
				$this->validateExpirationDate($share);
839
				$expirationDateUpdated = true;
840
			}
841
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
842
			// The new password is not set again if it is the same as the old
843
			// one, unless when switching from sending by Talk to sending by
844
			// mail.
845
			$plainTextPassword = $share->getPassword();
846
			if (!empty($plainTextPassword) && !$this->updateSharePasswordIfNeeded($share, $originalShare) &&
847
					!($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk())) {
848
				$plainTextPassword = null;
849
			}
850
			if (empty($plainTextPassword) && !$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) {
851
				// If the same password was already sent by mail the recipient
852
				// would already have access to the share without having to call
853
				// the sharer to verify her identity
854
				throw new \InvalidArgumentException('Can’t enable sending the password by Talk without setting a new password');
855
			}
856
		}
857
858
		$this->pathCreateChecks($share->getNode());
859
860
		// Now update the share!
861
		$provider = $this->factory->getProviderForType($share->getShareType());
862
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
863
			$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

863
			/** @scrutinizer ignore-call */ 
864
   $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...
864
		} else {
865
			$share = $provider->update($share);
866
		}
867
868
		if ($expirationDateUpdated === true) {
869
			\OC_Hook::emit(Share::class, 'post_set_expiration_date', [
870
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
871
				'itemSource' => $share->getNode()->getId(),
872
				'date' => $share->getExpirationDate(),
873
				'uidOwner' => $share->getSharedBy(),
874
			]);
875
		}
876
877
		if ($share->getPassword() !== $originalShare->getPassword()) {
878
			\OC_Hook::emit(Share::class, 'post_update_password', [
879
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
880
				'itemSource' => $share->getNode()->getId(),
881
				'uidOwner' => $share->getSharedBy(),
882
				'token' => $share->getToken(),
883
				'disabled' => is_null($share->getPassword()),
884
			]);
885
		}
886
887
		if ($share->getPermissions() !== $originalShare->getPermissions()) {
888
			if ($this->userManager->userExists($share->getShareOwner())) {
889
				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
890
			} else {
891
				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
892
			}
893
			\OC_Hook::emit(Share::class, 'post_update_permissions', array(
894
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
895
				'itemSource' => $share->getNode()->getId(),
896
				'shareType' => $share->getShareType(),
897
				'shareWith' => $share->getSharedWith(),
898
				'uidOwner' => $share->getSharedBy(),
899
				'permissions' => $share->getPermissions(),
900
				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
901
			));
902
		}
903
904
		return $share;
905
	}
906
907
	/**
908
	 * Updates the password of the given share if it is not the same as the
909
	 * password of the original share.
910
	 *
911
	 * @param \OCP\Share\IShare $share the share to update its password.
912
	 * @param \OCP\Share\IShare $originalShare the original share to compare its
913
	 *        password with.
914
	 * @return boolean whether the password was updated or not.
915
	 */
916
	private function updateSharePasswordIfNeeded(\OCP\Share\IShare $share, \OCP\Share\IShare $originalShare) {
917
		// Password updated.
918
		if ($share->getPassword() !== $originalShare->getPassword()) {
919
			//Verify the password
920
			$this->verifyPassword($share->getPassword());
921
922
			// If a password is set. Hash it!
923
			if ($share->getPassword() !== null) {
0 ignored issues
show
introduced by
The condition $share->getPassword() !== null is always true.
Loading history...
924
				$share->setPassword($this->hasher->hash($share->getPassword()));
925
926
				return true;
927
			}
928
		}
929
930
		return false;
931
	}
932
933
	/**
934
	 * Delete all the children of this share
935
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
936
	 *
937
	 * @param \OCP\Share\IShare $share
938
	 * @return \OCP\Share\IShare[] List of deleted shares
939
	 */
940
	protected function deleteChildren(\OCP\Share\IShare $share) {
941
		$deletedShares = [];
942
943
		$provider = $this->factory->getProviderForType($share->getShareType());
944
945
		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

945
		foreach ($provider->/** @scrutinizer ignore-call */ getChildren($share) as $child) {
Loading history...
946
			$deletedChildren = $this->deleteChildren($child);
947
			$deletedShares = array_merge($deletedShares, $deletedChildren);
948
949
			$provider->delete($child);
950
			$deletedShares[] = $child;
951
		}
952
953
		return $deletedShares;
954
	}
955
956
	/**
957
	 * Delete a share
958
	 *
959
	 * @param \OCP\Share\IShare $share
960
	 * @throws ShareNotFound
961
	 * @throws \InvalidArgumentException
962
	 */
963
	public function deleteShare(\OCP\Share\IShare $share) {
964
965
		try {
966
			$share->getFullId();
967
		} catch (\UnexpectedValueException $e) {
968
			throw new \InvalidArgumentException('Share does not have a full id');
969
		}
970
971
		$event = new GenericEvent($share);
972
		$this->eventDispatcher->dispatch('OCP\Share::preUnshare', $event);
973
974
		// Get all children and delete them as well
975
		$deletedShares = $this->deleteChildren($share);
976
977
		// Do the actual delete
978
		$provider = $this->factory->getProviderForType($share->getShareType());
979
		$provider->delete($share);
980
981
		// All the deleted shares caused by this delete
982
		$deletedShares[] = $share;
983
984
		// Emit post hook
985
		$event->setArgument('deletedShares', $deletedShares);
986
		$this->eventDispatcher->dispatch('OCP\Share::postUnshare', $event);
987
	}
988
989
990
	/**
991
	 * Unshare a file as the recipient.
992
	 * This can be different from a regular delete for example when one of
993
	 * the users in a groups deletes that share. But the provider should
994
	 * handle this.
995
	 *
996
	 * @param \OCP\Share\IShare $share
997
	 * @param string $recipientId
998
	 */
999
	public function deleteFromSelf(\OCP\Share\IShare $share, $recipientId) {
1000
		list($providerId, ) = $this->splitFullId($share->getFullId());
1001
		$provider = $this->factory->getProvider($providerId);
1002
1003
		$provider->deleteFromSelf($share, $recipientId);
1004
		$event = new GenericEvent($share);
1005
		$this->eventDispatcher->dispatch('OCP\Share::postUnshareFromSelf', $event);
1006
	}
1007
1008
	public function restoreShare(IShare $share, string $recipientId): IShare {
1009
		list($providerId, ) = $this->splitFullId($share->getFullId());
1010
		$provider = $this->factory->getProvider($providerId);
1011
1012
		return $provider->restore($share, $recipientId);
1013
	}
1014
1015
	/**
1016
	 * @inheritdoc
1017
	 */
1018
	public function moveShare(\OCP\Share\IShare $share, $recipientId) {
1019
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
1020
			throw new \InvalidArgumentException('Can’t change target of link share');
1021
		}
1022
1023
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && $share->getSharedWith() !== $recipientId) {
1024
			throw new \InvalidArgumentException('Invalid recipient');
1025
		}
1026
1027
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
1028
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1029
			if (is_null($sharedWith)) {
1030
				throw new \InvalidArgumentException('Group "' . $share->getSharedWith() . '" does not exist');
1031
			}
1032
			$recipient = $this->userManager->get($recipientId);
1033
			if (!$sharedWith->inGroup($recipient)) {
1034
				throw new \InvalidArgumentException('Invalid recipient');
1035
			}
1036
		}
1037
1038
		list($providerId, ) = $this->splitFullId($share->getFullId());
1039
		$provider = $this->factory->getProvider($providerId);
1040
1041
		$provider->move($share, $recipientId);
1042
	}
1043
1044
	public function getSharesInFolder($userId, Folder $node, $reshares = false) {
1045
		$providers = $this->factory->getAllProviders();
1046
1047
		return array_reduce($providers, function($shares, IShareProvider $provider) use ($userId, $node, $reshares) {
1048
			$newShares = $provider->getSharesInFolder($userId, $node, $reshares);
1049
			foreach ($newShares as $fid => $data) {
1050
				if (!isset($shares[$fid])) {
1051
					$shares[$fid] = [];
1052
				}
1053
1054
				$shares[$fid] = array_merge($shares[$fid], $data);
0 ignored issues
show
Bug introduced by
$data of type OCP\Share\IShare is incompatible with the type array|null expected by parameter $array2 of array_merge(). ( Ignorable by Annotation )

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

1054
				$shares[$fid] = array_merge($shares[$fid], /** @scrutinizer ignore-type */ $data);
Loading history...
1055
			}
1056
			return $shares;
1057
		}, []);
1058
	}
1059
1060
	/**
1061
	 * @inheritdoc
1062
	 */
1063
	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
1064
		if ($path !== null &&
1065
				!($path instanceof \OCP\Files\File) &&
1066
				!($path instanceof \OCP\Files\Folder)) {
1067
			throw new \InvalidArgumentException('invalid path');
1068
		}
1069
1070
		try {
1071
			$provider = $this->factory->getProviderForType($shareType);
1072
		} catch (ProviderException $e) {
1073
			return [];
1074
		}
1075
1076
		$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1077
1078
		/*
1079
		 * Work around so we don't return expired shares but still follow
1080
		 * proper pagination.
1081
		 */
1082
1083
		$shares2 = [];
1084
1085
		while(true) {
1086
			$added = 0;
1087
			foreach ($shares as $share) {
1088
1089
				try {
1090
					$this->checkExpireDate($share);
1091
				} catch (ShareNotFound $e) {
1092
					//Ignore since this basically means the share is deleted
1093
					continue;
1094
				}
1095
1096
				$added++;
1097
				$shares2[] = $share;
1098
1099
				if (count($shares2) === $limit) {
1100
					break;
1101
				}
1102
			}
1103
1104
			// If we did not fetch more shares than the limit then there are no more shares
1105
			if (count($shares) < $limit) {
1106
				break;
1107
			}
1108
1109
			if (count($shares2) === $limit) {
1110
				break;
1111
			}
1112
1113
			// If there was no limit on the select we are done
1114
			if ($limit === -1) {
1115
				break;
1116
			}
1117
1118
			$offset += $added;
1119
1120
			// Fetch again $limit shares
1121
			$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1122
1123
			// No more shares means we are done
1124
			if (empty($shares)) {
1125
				break;
1126
			}
1127
		}
1128
1129
		$shares = $shares2;
1130
1131
		return $shares;
1132
	}
1133
1134
	/**
1135
	 * @inheritdoc
1136
	 */
1137
	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1138
		try {
1139
			$provider = $this->factory->getProviderForType($shareType);
1140
		} catch (ProviderException $e) {
1141
			return [];
1142
		}
1143
1144
		$shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1145
1146
		// remove all shares which are already expired
1147
		foreach ($shares as $key => $share) {
1148
			try {
1149
				$this->checkExpireDate($share);
1150
			} catch (ShareNotFound $e) {
1151
				unset($shares[$key]);
1152
			}
1153
		}
1154
1155
		return $shares;
1156
	}
1157
1158
	/**
1159
	 * @inheritdoc
1160
	 */
1161
	public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1162
		$shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1163
1164
		// Only get deleted shares
1165
		$shares = array_filter($shares, function(IShare $share) {
1166
			return $share->getPermissions() === 0;
1167
		});
1168
1169
		// Only get shares where the owner still exists
1170
		$shares = array_filter($shares, function (IShare $share) {
1171
			return $this->userManager->userExists($share->getShareOwner());
1172
		});
1173
1174
		return $shares;
1175
	}
1176
1177
	/**
1178
	 * @inheritdoc
1179
	 */
1180
	public function getShareById($id, $recipient = null) {
1181
		if ($id === null) {
1182
			throw new ShareNotFound();
1183
		}
1184
1185
		list($providerId, $id) = $this->splitFullId($id);
1186
1187
		try {
1188
			$provider = $this->factory->getProvider($providerId);
1189
		} catch (ProviderException $e) {
1190
			throw new ShareNotFound();
1191
		}
1192
1193
		$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

1193
		$share = $provider->getShareById(/** @scrutinizer ignore-type */ $id, $recipient);
Loading history...
1194
1195
		$this->checkExpireDate($share);
1196
1197
		return $share;
1198
	}
1199
1200
	/**
1201
	 * Get all the shares for a given path
1202
	 *
1203
	 * @param \OCP\Files\Node $path
1204
	 * @param int $page
1205
	 * @param int $perPage
1206
	 *
1207
	 * @return Share[]
1208
	 */
1209
	public function getSharesByPath(\OCP\Files\Node $path, $page=0, $perPage=50) {
0 ignored issues
show
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

1209
	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 $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

1209
	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 $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

1209
	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...
1210
		return [];
1211
	}
1212
1213
	/**
1214
	 * Get the share by token possible with password
1215
	 *
1216
	 * @param string $token
1217
	 * @return Share
1218
	 *
1219
	 * @throws ShareNotFound
1220
	 */
1221
	public function getShareByToken($token) {
1222
		// tokens can't be valid local user names
1223
		if ($this->userManager->userExists($token)) {
1224
			throw new ShareNotFound();
1225
		}
1226
		$share = null;
1227
		try {
1228
			if($this->shareApiAllowLinks()) {
1229
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_LINK);
1230
				$share = $provider->getShareByToken($token);
1231
			}
1232
		} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1233
		} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1234
		}
1235
1236
1237
		// If it is not a link share try to fetch a federated share by token
1238
		if ($share === null) {
1239
			try {
1240
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_REMOTE);
1241
				$share = $provider->getShareByToken($token);
1242
			} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1243
			} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1244
			}
1245
		}
1246
1247
		// If it is not a link share try to fetch a mail share by token
1248
		if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
1249
			try {
1250
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_EMAIL);
1251
				$share = $provider->getShareByToken($token);
1252
			} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1253
			} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1254
			}
1255
		}
1256
1257
		if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_CIRCLE)) {
1258
			try {
1259
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_CIRCLE);
1260
				$share = $provider->getShareByToken($token);
1261
			} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1262
			} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1263
			}
1264
		}
1265
1266
		if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_ROOM)) {
1267
			try {
1268
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_ROOM);
1269
				$share = $provider->getShareByToken($token);
1270
			} catch (ProviderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1271
			} catch (ShareNotFound $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1272
			}
1273
		}
1274
1275
		if ($share === null) {
1276
			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1277
		}
1278
1279
		$this->checkExpireDate($share);
1280
1281
		/*
1282
		 * Reduce the permissions for link shares if public upload is not enabled
1283
		 */
1284
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1285
			!$this->shareApiLinkAllowPublicUpload()) {
1286
			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1287
		}
1288
1289
		return $share;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $share returns the type OCP\Share\IShare which is incompatible with the documented return type OCP\Share.
Loading history...
1290
	}
1291
1292
	protected function checkExpireDate($share) {
1293
		if ($share->getExpirationDate() !== null &&
1294
			$share->getExpirationDate() <= new \DateTime()) {
1295
			$this->deleteShare($share);
1296
			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1297
		}
1298
1299
	}
1300
1301
	/**
1302
	 * Verify the password of a public share
1303
	 *
1304
	 * @param \OCP\Share\IShare $share
1305
	 * @param string $password
1306
	 * @return bool
1307
	 */
1308
	public function checkPassword(\OCP\Share\IShare $share, $password) {
1309
		$passwordProtected = $share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK
1310
			|| $share->getShareType() !== \OCP\Share::SHARE_TYPE_EMAIL;
1311
		if (!$passwordProtected) {
1312
			//TODO maybe exception?
1313
			return false;
1314
		}
1315
1316
		if ($password === null || $share->getPassword() === null) {
0 ignored issues
show
introduced by
The condition $share->getPassword() === null is always false.
Loading history...
1317
			return false;
1318
		}
1319
1320
		$newHash = '';
1321
		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1322
			return false;
1323
		}
1324
1325
		if (!empty($newHash)) {
1326
			$share->setPassword($newHash);
1327
			$provider = $this->factory->getProviderForType($share->getShareType());
1328
			$provider->update($share);
1329
		}
1330
1331
		return true;
1332
	}
1333
1334
	/**
1335
	 * @inheritdoc
1336
	 */
1337
	public function userDeleted($uid) {
1338
		$types = [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE, \OCP\Share::SHARE_TYPE_EMAIL];
1339
1340
		foreach ($types as $type) {
1341
			try {
1342
				$provider = $this->factory->getProviderForType($type);
1343
			} catch (ProviderException $e) {
1344
				continue;
1345
			}
1346
			$provider->userDeleted($uid, $type);
1347
		}
1348
	}
1349
1350
	/**
1351
	 * @inheritdoc
1352
	 */
1353
	public function groupDeleted($gid) {
1354
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1355
		$provider->groupDeleted($gid);
1356
	}
1357
1358
	/**
1359
	 * @inheritdoc
1360
	 */
1361
	public function userDeletedFromGroup($uid, $gid) {
1362
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1363
		$provider->userDeletedFromGroup($uid, $gid);
1364
	}
1365
1366
	/**
1367
	 * Get access list to a path. This means
1368
	 * all the users that can access a given path.
1369
	 *
1370
	 * Consider:
1371
	 * -root
1372
	 * |-folder1 (23)
1373
	 *  |-folder2 (32)
1374
	 *   |-fileA (42)
1375
	 *
1376
	 * fileA is shared with user1 and user1@server1
1377
	 * folder2 is shared with group2 (user4 is a member of group2)
1378
	 * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
1379
	 *
1380
	 * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
1381
	 * [
1382
	 *  users  => [
1383
	 *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
1384
	 *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
1385
	 *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
1386
	 *  ],
1387
	 *  remote => [
1388
	 *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
1389
	 *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
1390
	 *  ],
1391
	 *  public => bool
1392
	 *  mail => bool
1393
	 * ]
1394
	 *
1395
	 * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
1396
	 * [
1397
	 *  users  => ['user1', 'user2', 'user4'],
1398
	 *  remote => bool,
1399
	 *  public => bool
1400
	 *  mail => bool
1401
	 * ]
1402
	 *
1403
	 * This is required for encryption/activity
1404
	 *
1405
	 * @param \OCP\Files\Node $path
1406
	 * @param bool $recursive Should we check all parent folders as well
1407
	 * @param bool $currentAccess Ensure the recipient has access to the file (e.g. did not unshare it)
1408
	 * @return array
1409
	 */
1410
	public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1411
		$owner = $path->getOwner();
1412
1413
		if ($owner === null) {
1414
			return [];
1415
		}
1416
1417
		$owner = $owner->getUID();
1418
1419
		if ($currentAccess) {
1420
			$al = ['users' => [], 'remote' => [], 'public' => false];
1421
		} else {
1422
			$al = ['users' => [], 'remote' => false, 'public' => false];
1423
		}
1424
		if (!$this->userManager->userExists($owner)) {
1425
			return $al;
1426
		}
1427
1428
		//Get node for the owner and correct the owner in case of external storages
1429
		$userFolder = $this->rootFolder->getUserFolder($owner);
1430
		if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1431
			$nodes = $userFolder->getById($path->getId());
1432
			$path = array_shift($nodes);
1433
			if ($path->getOwner() === null) {
1434
				return [];
1435
			}
1436
			$owner = $path->getOwner()->getUID();
1437
		}
1438
1439
		$providers = $this->factory->getAllProviders();
1440
1441
		/** @var Node[] $nodes */
1442
		$nodes = [];
1443
1444
1445
		if ($currentAccess) {
1446
			$ownerPath = $path->getPath();
1447
			$ownerPath = explode('/', $ownerPath, 4);
1448
			if (count($ownerPath) < 4) {
1449
				$ownerPath = '';
1450
			} else {
1451
				$ownerPath = $ownerPath[3];
1452
			}
1453
			$al['users'][$owner] = [
1454
				'node_id' => $path->getId(),
1455
				'node_path' => '/' . $ownerPath,
1456
			];
1457
		} else {
1458
			$al['users'][] = $owner;
1459
		}
1460
1461
		// Collect all the shares
1462
		while ($path->getPath() !== $userFolder->getPath()) {
1463
			$nodes[] = $path;
1464
			if (!$recursive) {
1465
				break;
1466
			}
1467
			$path = $path->getParent();
1468
		}
1469
1470
		foreach ($providers as $provider) {
1471
			$tmp = $provider->getAccessList($nodes, $currentAccess);
1472
1473
			foreach ($tmp as $k => $v) {
1474
				if (isset($al[$k])) {
1475
					if (is_array($al[$k])) {
1476
						if ($currentAccess) {
1477
							$al[$k] += $v;
1478
						} else {
1479
							$al[$k] = array_merge($al[$k], $v);
1480
							$al[$k] = array_unique($al[$k]);
1481
							$al[$k] = array_values($al[$k]);
1482
						}
1483
					} else {
1484
						$al[$k] = $al[$k] || $v;
1485
					}
1486
				} else {
1487
					$al[$k] = $v;
1488
				}
1489
			}
1490
		}
1491
1492
		return $al;
1493
	}
1494
1495
	/**
1496
	 * Create a new share
1497
	 * @return \OCP\Share\IShare
1498
	 */
1499
	public function newShare() {
1500
		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1501
	}
1502
1503
	/**
1504
	 * Is the share API enabled
1505
	 *
1506
	 * @return bool
1507
	 */
1508
	public function shareApiEnabled() {
1509
		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1510
	}
1511
1512
	/**
1513
	 * Is public link sharing enabled
1514
	 *
1515
	 * @return bool
1516
	 */
1517
	public function shareApiAllowLinks() {
1518
		return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
1519
	}
1520
1521
	/**
1522
	 * Is password on public link requires
1523
	 *
1524
	 * @return bool
1525
	 */
1526
	public function shareApiLinkEnforcePassword() {
1527
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1528
	}
1529
1530
	/**
1531
	 * Is default expire date enabled
1532
	 *
1533
	 * @return bool
1534
	 */
1535
	public function shareApiLinkDefaultExpireDate() {
1536
		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1537
	}
1538
1539
	/**
1540
	 * Is default expire date enforced
1541
	 *`
1542
	 * @return bool
1543
	 */
1544
	public function shareApiLinkDefaultExpireDateEnforced() {
1545
		return $this->shareApiLinkDefaultExpireDate() &&
1546
			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1547
	}
1548
1549
	/**
1550
	 * Number of default expire days
1551
	 *shareApiLinkAllowPublicUpload
1552
	 * @return int
1553
	 */
1554
	public function shareApiLinkDefaultExpireDays() {
1555
		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1556
	}
1557
1558
	/**
1559
	 * Allow public upload on link shares
1560
	 *
1561
	 * @return bool
1562
	 */
1563
	public function shareApiLinkAllowPublicUpload() {
1564
		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1565
	}
1566
1567
	/**
1568
	 * check if user can only share with group members
1569
	 * @return bool
1570
	 */
1571
	public function shareWithGroupMembersOnly() {
1572
		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1573
	}
1574
1575
	/**
1576
	 * Check if users can share with groups
1577
	 * @return bool
1578
	 */
1579
	public function allowGroupSharing() {
1580
		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1581
	}
1582
1583
	/**
1584
	 * Copied from \OC_Util::isSharingDisabledForUser
1585
	 *
1586
	 * TODO: Deprecate fuction from OC_Util
1587
	 *
1588
	 * @param string $userId
1589
	 * @return bool
1590
	 */
1591
	public function sharingDisabledForUser($userId) {
1592
		if ($userId === null) {
0 ignored issues
show
introduced by
The condition $userId === null is always false.
Loading history...
1593
			return false;
1594
		}
1595
1596
		if (isset($this->sharingDisabledForUsersCache[$userId])) {
1597
			return $this->sharingDisabledForUsersCache[$userId];
1598
		}
1599
1600
		if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
1601
			$groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1602
			$excludedGroups = json_decode($groupsList);
1603
			if (is_null($excludedGroups)) {
1604
				$excludedGroups = explode(',', $groupsList);
1605
				$newValue = json_encode($excludedGroups);
1606
				$this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
1607
			}
1608
			$user = $this->userManager->get($userId);
1609
			$usersGroups = $this->groupManager->getUserGroupIds($user);
1610
			if (!empty($usersGroups)) {
1611
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
1612
				// if the user is only in groups which are disabled for sharing then
1613
				// sharing is also disabled for the user
1614
				if (empty($remainingGroups)) {
1615
					$this->sharingDisabledForUsersCache[$userId] = true;
1616
					return true;
1617
				}
1618
			}
1619
		}
1620
1621
		$this->sharingDisabledForUsersCache[$userId] = false;
1622
		return false;
1623
	}
1624
1625
	/**
1626
	 * @inheritdoc
1627
	 */
1628
	public function outgoingServer2ServerSharesAllowed() {
1629
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1630
	}
1631
1632
	/**
1633
	 * @inheritdoc
1634
	 */
1635
	public function outgoingServer2ServerGroupSharesAllowed() {
1636
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1637
	}
1638
1639
	/**
1640
	 * @inheritdoc
1641
	 */
1642
	public function shareProviderExists($shareType) {
1643
		try {
1644
			$this->factory->getProviderForType($shareType);
1645
		} catch (ProviderException $e) {
1646
			return false;
1647
		}
1648
1649
		return true;
1650
	}
1651
1652
}
1653