Passed
Push — master ( f8859e...b72d27 )
by Roeland
13:06
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\EventDispatcher;
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 EventDispatcher */
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 EventDispatcher $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
			EventDispatcher $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
			$owner = $path->getOwner()->getUID();
1434
		}
1435
1436
		$providers = $this->factory->getAllProviders();
1437
1438
		/** @var Node[] $nodes */
1439
		$nodes = [];
1440
1441
1442
		if ($currentAccess) {
1443
			$ownerPath = $path->getPath();
1444
			$ownerPath = explode('/', $ownerPath, 4);
1445
			if (count($ownerPath) < 4) {
1446
				$ownerPath = '';
1447
			} else {
1448
				$ownerPath = $ownerPath[3];
1449
			}
1450
			$al['users'][$owner] = [
1451
				'node_id' => $path->getId(),
1452
				'node_path' => '/' . $ownerPath,
1453
			];
1454
		} else {
1455
			$al['users'][] = $owner;
1456
		}
1457
1458
		// Collect all the shares
1459
		while ($path->getPath() !== $userFolder->getPath()) {
1460
			$nodes[] = $path;
1461
			if (!$recursive) {
1462
				break;
1463
			}
1464
			$path = $path->getParent();
1465
		}
1466
1467
		foreach ($providers as $provider) {
1468
			$tmp = $provider->getAccessList($nodes, $currentAccess);
1469
1470
			foreach ($tmp as $k => $v) {
1471
				if (isset($al[$k])) {
1472
					if (is_array($al[$k])) {
1473
						if ($currentAccess) {
1474
							$al[$k] += $v;
1475
						} else {
1476
							$al[$k] = array_merge($al[$k], $v);
1477
							$al[$k] = array_unique($al[$k]);
1478
							$al[$k] = array_values($al[$k]);
1479
						}
1480
					} else {
1481
						$al[$k] = $al[$k] || $v;
1482
					}
1483
				} else {
1484
					$al[$k] = $v;
1485
				}
1486
			}
1487
		}
1488
1489
		return $al;
1490
	}
1491
1492
	/**
1493
	 * Create a new share
1494
	 * @return \OCP\Share\IShare
1495
	 */
1496
	public function newShare() {
1497
		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1498
	}
1499
1500
	/**
1501
	 * Is the share API enabled
1502
	 *
1503
	 * @return bool
1504
	 */
1505
	public function shareApiEnabled() {
1506
		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1507
	}
1508
1509
	/**
1510
	 * Is public link sharing enabled
1511
	 *
1512
	 * @return bool
1513
	 */
1514
	public function shareApiAllowLinks() {
1515
		return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
1516
	}
1517
1518
	/**
1519
	 * Is password on public link requires
1520
	 *
1521
	 * @return bool
1522
	 */
1523
	public function shareApiLinkEnforcePassword() {
1524
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1525
	}
1526
1527
	/**
1528
	 * Is default expire date enabled
1529
	 *
1530
	 * @return bool
1531
	 */
1532
	public function shareApiLinkDefaultExpireDate() {
1533
		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1534
	}
1535
1536
	/**
1537
	 * Is default expire date enforced
1538
	 *`
1539
	 * @return bool
1540
	 */
1541
	public function shareApiLinkDefaultExpireDateEnforced() {
1542
		return $this->shareApiLinkDefaultExpireDate() &&
1543
			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1544
	}
1545
1546
	/**
1547
	 * Number of default expire days
1548
	 *shareApiLinkAllowPublicUpload
1549
	 * @return int
1550
	 */
1551
	public function shareApiLinkDefaultExpireDays() {
1552
		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1553
	}
1554
1555
	/**
1556
	 * Allow public upload on link shares
1557
	 *
1558
	 * @return bool
1559
	 */
1560
	public function shareApiLinkAllowPublicUpload() {
1561
		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1562
	}
1563
1564
	/**
1565
	 * check if user can only share with group members
1566
	 * @return bool
1567
	 */
1568
	public function shareWithGroupMembersOnly() {
1569
		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1570
	}
1571
1572
	/**
1573
	 * Check if users can share with groups
1574
	 * @return bool
1575
	 */
1576
	public function allowGroupSharing() {
1577
		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1578
	}
1579
1580
	/**
1581
	 * Copied from \OC_Util::isSharingDisabledForUser
1582
	 *
1583
	 * TODO: Deprecate fuction from OC_Util
1584
	 *
1585
	 * @param string $userId
1586
	 * @return bool
1587
	 */
1588
	public function sharingDisabledForUser($userId) {
1589
		if ($userId === null) {
0 ignored issues
show
introduced by
The condition $userId === null is always false.
Loading history...
1590
			return false;
1591
		}
1592
1593
		if (isset($this->sharingDisabledForUsersCache[$userId])) {
1594
			return $this->sharingDisabledForUsersCache[$userId];
1595
		}
1596
1597
		if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
1598
			$groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1599
			$excludedGroups = json_decode($groupsList);
1600
			if (is_null($excludedGroups)) {
1601
				$excludedGroups = explode(',', $groupsList);
1602
				$newValue = json_encode($excludedGroups);
1603
				$this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
1604
			}
1605
			$user = $this->userManager->get($userId);
1606
			$usersGroups = $this->groupManager->getUserGroupIds($user);
1607
			if (!empty($usersGroups)) {
1608
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
1609
				// if the user is only in groups which are disabled for sharing then
1610
				// sharing is also disabled for the user
1611
				if (empty($remainingGroups)) {
1612
					$this->sharingDisabledForUsersCache[$userId] = true;
1613
					return true;
1614
				}
1615
			}
1616
		}
1617
1618
		$this->sharingDisabledForUsersCache[$userId] = false;
1619
		return false;
1620
	}
1621
1622
	/**
1623
	 * @inheritdoc
1624
	 */
1625
	public function outgoingServer2ServerSharesAllowed() {
1626
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1627
	}
1628
1629
	/**
1630
	 * @inheritdoc
1631
	 */
1632
	public function outgoingServer2ServerGroupSharesAllowed() {
1633
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1634
	}
1635
1636
	/**
1637
	 * @inheritdoc
1638
	 */
1639
	public function shareProviderExists($shareType) {
1640
		try {
1641
			$this->factory->getProviderForType($shareType);
1642
		} catch (ProviderException $e) {
1643
			return false;
1644
		}
1645
1646
		return true;
1647
	}
1648
1649
}
1650