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

Manager::shareApiLinkDefaultExpireDate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
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