Completed
Push — master ( 86d952...cbfcfb )
by Morris
24:23
created

Manager::userDeletedFromGroup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
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\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.

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) {
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) {
223
				throw new \InvalidArgumentException('SharedWith should be empty');
224
			}
225
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
226
			if ($share->getSharedWith() === null) {
227
				throw new \InvalidArgumentException('SharedWith should not be empty');
228
			}
229
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
230
			if ($share->getSharedWith() === null) {
231
				throw new \InvalidArgumentException('SharedWith should not be empty');
232
			}
233
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_CIRCLE) {
234
			$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith());
235
			if ($circle === null) {
236
				throw new \InvalidArgumentException('SharedWith is not a valid circle');
237
			}
238
		} else {
239
			// We can't handle other types yet
240
			throw new \InvalidArgumentException('unknown share type');
241
		}
242
243
		// Verify the initiator of the share is set
244
		if ($share->getSharedBy() === null) {
245
			throw new \InvalidArgumentException('SharedBy should be set');
246
		}
247
248
		// Cannot share with yourself
249 View Code Duplication
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
250
			$share->getSharedWith() === $share->getSharedBy()) {
251
			throw new \InvalidArgumentException('Can’t share with yourself');
252
		}
253
254
		// The path should be set
255
		if ($share->getNode() === null) {
256
			throw new \InvalidArgumentException('Path should be set');
257
		}
258
259
		// And it should be a file or a folder
260
		if (!($share->getNode() instanceof \OCP\Files\File) &&
261
				!($share->getNode() instanceof \OCP\Files\Folder)) {
262
			throw new \InvalidArgumentException('Path should be either a file or a folder');
263
		}
264
265
		// And you can't share your rootfolder
266
		if ($this->userManager->userExists($share->getSharedBy())) {
267
			$sharedPath = $this->rootFolder->getUserFolder($share->getSharedBy())->getPath();
268
		} else {
269
			$sharedPath = $this->rootFolder->getUserFolder($share->getShareOwner())->getPath();
270
		}
271
		if ($sharedPath === $share->getNode()->getPath()) {
272
			throw new \InvalidArgumentException('You can’t share your root folder');
273
		}
274
275
		// Check if we actually have share permissions
276 View Code Duplication
		if (!$share->getNode()->isShareable()) {
277
			$message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getPath()]);
278
			throw new GenericShareException($message_t, $message_t, 404);
279
		}
280
281
		// Permissions should be set
282
		if ($share->getPermissions() === null) {
283
			throw new \InvalidArgumentException('A share requires permissions');
284
		}
285
286
		/*
287
		 * Quick fix for #23536
288
		 * Non moveable mount points do not have update and delete permissions
289
		 * while we 'most likely' do have that on the storage.
290
		 */
291
		$permissions = $share->getNode()->getPermissions();
292
		$mount = $share->getNode()->getMountPoint();
293
		if (!($mount instanceof MoveableMount)) {
294
			$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
295
		}
296
297
		// Check that we do not share with more permissions than we have
298 View Code Duplication
		if ($share->getPermissions() & ~$permissions) {
299
			$message_t = $this->l->t('Can’t increase permissions of %s', [$share->getNode()->getPath()]);
300
			throw new GenericShareException($message_t, $message_t, 404);
301
		}
302
303
304
		// Check that read permissions are always set
305
		// Link shares are allowed to have no read permissions to allow upload to hidden folders
306
		$noReadPermissionRequired = $share->getShareType() === \OCP\Share::SHARE_TYPE_LINK
307
			|| $share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL;
308
		if (!$noReadPermissionRequired &&
309
			($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
310
			throw new \InvalidArgumentException('Shares need at least read permissions');
311
		}
312
313
		if ($share->getNode() instanceof \OCP\Files\File) {
314 View Code Duplication
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
315
				$message_t = $this->l->t('Files can’t be shared with delete permissions');
316
				throw new GenericShareException($message_t);
317
			}
318 View Code Duplication
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
319
				$message_t = $this->l->t('Files can’t be shared with create permissions');
320
				throw new GenericShareException($message_t);
321
			}
322
		}
323
	}
324
325
	/**
326
	 * Validate if the expiration date fits the system settings
327
	 *
328
	 * @param \OCP\Share\IShare $share The share to validate the expiration date of
329
	 * @return \OCP\Share\IShare The modified share object
330
	 * @throws GenericShareException
331
	 * @throws \InvalidArgumentException
332
	 * @throws \Exception
333
	 */
334
	protected function validateExpirationDate(\OCP\Share\IShare $share) {
335
336
		$expirationDate = $share->getExpirationDate();
337
338
		if ($expirationDate !== null) {
339
			//Make sure the expiration date is a date
340
			$expirationDate->setTime(0, 0, 0);
341
342
			$date = new \DateTime();
343
			$date->setTime(0, 0, 0);
344 View Code Duplication
			if ($date >= $expirationDate) {
345
				$message = $this->l->t('Expiration date is in the past');
346
				throw new GenericShareException($message, $message, 404);
347
			}
348
		}
349
350
		// If expiredate is empty set a default one if there is a default
351
		$fullId = null;
352
		try {
353
			$fullId = $share->getFullId();
354
		} catch (\UnexpectedValueException $e) {
355
			// This is a new share
356
		}
357
358
		if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
359
			$expirationDate = new \DateTime();
360
			$expirationDate->setTime(0,0,0);
361
			$expirationDate->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
362
		}
363
364
		// If we enforce the expiration date check that is does not exceed
365
		if ($this->shareApiLinkDefaultExpireDateEnforced()) {
366
			if ($expirationDate === null) {
367
				throw new \InvalidArgumentException('Expiration date is enforced');
368
			}
369
370
			$date = new \DateTime();
371
			$date->setTime(0, 0, 0);
372
			$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
373 View Code Duplication
			if ($date < $expirationDate) {
374
				$message = $this->l->t('Can’t set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
375
				throw new GenericShareException($message, $message, 404);
376
			}
377
		}
378
379
		$accepted = true;
380
		$message = '';
381
		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
382
			'expirationDate' => &$expirationDate,
383
			'accepted' => &$accepted,
384
			'message' => &$message,
385
			'passwordSet' => $share->getPassword() !== null,
386
		]);
387
388
		if (!$accepted) {
389
			throw new \Exception($message);
390
		}
391
392
		$share->setExpirationDate($expirationDate);
393
394
		return $share;
395
	}
396
397
	/**
398
	 * Check for pre share requirements for user shares
399
	 *
400
	 * @param \OCP\Share\IShare $share
401
	 * @throws \Exception
402
	 */
403
	protected function userCreateChecks(\OCP\Share\IShare $share) {
404
		// Check if we can share with group members only
405
		if ($this->shareWithGroupMembersOnly()) {
406
			$sharedBy = $this->userManager->get($share->getSharedBy());
407
			$sharedWith = $this->userManager->get($share->getSharedWith());
408
			// Verify we can share with this user
409
			$groups = array_intersect(
410
					$this->groupManager->getUserGroupIds($sharedBy),
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 406 can be null; however, OCP\IGroupManager::getUserGroupIds() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
411
					$this->groupManager->getUserGroupIds($sharedWith)
0 ignored issues
show
Bug introduced by
It seems like $sharedWith defined by $this->userManager->get($share->getSharedWith()) on line 407 can be null; however, OCP\IGroupManager::getUserGroupIds() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
412
			);
413
			if (empty($groups)) {
414
				throw new \Exception('Sharing is only allowed with group members');
415
			}
416
		}
417
418
		/*
419
		 * TODO: Could be costly, fix
420
		 *
421
		 * Also this is not what we want in the future.. then we want to squash identical shares.
422
		 */
423
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_USER);
424
		$existingShares = $provider->getSharesByPath($share->getNode());
425
		foreach($existingShares as $existingShare) {
426
			// Ignore if it is the same share
427
			try {
428
				if ($existingShare->getFullId() === $share->getFullId()) {
429
					continue;
430
				}
431
			} catch (\UnexpectedValueException $e) {
432
				//Shares are not identical
433
			}
434
435
			// Identical share already existst
436
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
437
				throw new \Exception('Path is already shared with this user');
438
			}
439
440
			// The share is already shared with this user via a group share
441
			if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
442
				$group = $this->groupManager->get($existingShare->getSharedWith());
443
				if (!is_null($group)) {
444
					$user = $this->userManager->get($share->getSharedWith());
445
446
					if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($share->getSharedWith()) on line 444 can be null; however, OCP\IGroup::inGroup() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
447
						throw new \Exception('Path is already shared with this user');
448
					}
449
				}
450
			}
451
		}
452
	}
453
454
	/**
455
	 * Check for pre share requirements for group shares
456
	 *
457
	 * @param \OCP\Share\IShare $share
458
	 * @throws \Exception
459
	 */
460
	protected function groupCreateChecks(\OCP\Share\IShare $share) {
461
		// Verify group shares are allowed
462
		if (!$this->allowGroupSharing()) {
463
			throw new \Exception('Group sharing is now allowed');
464
		}
465
466
		// Verify if the user can share with this group
467
		if ($this->shareWithGroupMembersOnly()) {
468
			$sharedBy = $this->userManager->get($share->getSharedBy());
469
			$sharedWith = $this->groupManager->get($share->getSharedWith());
470
			if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) {
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 468 can be null; however, OCP\IGroup::inGroup() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
471
				throw new \Exception('Sharing is only allowed within your own groups');
472
			}
473
		}
474
475
		/*
476
		 * TODO: Could be costly, fix
477
		 *
478
		 * Also this is not what we want in the future.. then we want to squash identical shares.
479
		 */
480
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
481
		$existingShares = $provider->getSharesByPath($share->getNode());
482
		foreach($existingShares as $existingShare) {
483
			try {
484
				if ($existingShare->getFullId() === $share->getFullId()) {
485
					continue;
486
				}
487
			} catch (\UnexpectedValueException $e) {
488
				//It is a new share so just continue
489
			}
490
491
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
492
				throw new \Exception('Path is already shared with this group');
493
			}
494
		}
495
	}
496
497
	/**
498
	 * Check for pre share requirements for link shares
499
	 *
500
	 * @param \OCP\Share\IShare $share
501
	 * @throws \Exception
502
	 */
503
	protected function linkCreateChecks(\OCP\Share\IShare $share) {
504
		// Are link shares allowed?
505
		if (!$this->shareApiAllowLinks()) {
506
			throw new \Exception('Link sharing is not allowed');
507
		}
508
509
		// Link shares by definition can't have share permissions
510
		if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) {
511
			throw new \InvalidArgumentException('Link shares can’t have reshare permissions');
512
		}
513
514
		// Check if public upload is allowed
515
		if (!$this->shareApiLinkAllowPublicUpload() &&
516
			($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
517
			throw new \InvalidArgumentException('Public upload is not allowed');
518
		}
519
	}
520
521
	/**
522
	 * To make sure we don't get invisible link shares we set the parent
523
	 * of a link if it is a reshare. This is a quick word around
524
	 * until we can properly display multiple link shares in the UI
525
	 *
526
	 * See: https://github.com/owncloud/core/issues/22295
527
	 *
528
	 * FIXME: Remove once multiple link shares can be properly displayed
529
	 *
530
	 * @param \OCP\Share\IShare $share
531
	 */
532
	protected function setLinkParent(\OCP\Share\IShare $share) {
533
534
		// No sense in checking if the method is not there.
535
		if (method_exists($share, 'setParent')) {
536
			$storage = $share->getNode()->getStorage();
537
			if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
538
				/** @var \OCA\Files_Sharing\SharedStorage $storage */
539
				$share->setParent($storage->getShareId());
540
			}
541
		}
542
	}
543
544
	/**
545
	 * @param File|Folder $path
546
	 */
547
	protected function pathCreateChecks($path) {
548
		// Make sure that we do not share a path that contains a shared mountpoint
549
		if ($path instanceof \OCP\Files\Folder) {
550
			$mounts = $this->mountManager->findIn($path->getPath());
551
			foreach($mounts as $mount) {
552
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
553
					throw new \InvalidArgumentException('Path contains files shared with you');
554
				}
555
			}
556
		}
557
	}
558
559
	/**
560
	 * Check if the user that is sharing can actually share
561
	 *
562
	 * @param \OCP\Share\IShare $share
563
	 * @throws \Exception
564
	 */
565
	protected function canShare(\OCP\Share\IShare $share) {
566
		if (!$this->shareApiEnabled()) {
567
			throw new \Exception('Sharing is disabled');
568
		}
569
570
		if ($this->sharingDisabledForUser($share->getSharedBy())) {
571
			throw new \Exception('Sharing is disabled for you');
572
		}
573
	}
574
575
	/**
576
	 * Share a path
577
	 *
578
	 * @param \OCP\Share\IShare $share
579
	 * @return Share The share object
580
	 * @throws \Exception
581
	 *
582
	 * TODO: handle link share permissions or check them
583
	 */
584
	public function createShare(\OCP\Share\IShare $share) {
585
		$this->canShare($share);
586
587
		$this->generalCreateChecks($share);
588
589
		// Verify if there are any issues with the path
590
		$this->pathCreateChecks($share->getNode());
591
592
		/*
593
		 * On creation of a share the owner is always the owner of the path
594
		 * Except for mounted federated shares.
595
		 */
596
		$storage = $share->getNode()->getStorage();
597
		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
598
			$parent = $share->getNode()->getParent();
599
			while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
600
				$parent = $parent->getParent();
601
			}
602
			$share->setShareOwner($parent->getOwner()->getUID());
603
		} else {
604
			$share->setShareOwner($share->getNode()->getOwner()->getUID());
605
		}
606
607
		//Verify share type
608
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
609
			$this->userCreateChecks($share);
610
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
611
			$this->groupCreateChecks($share);
612
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
613
			$this->linkCreateChecks($share);
614
			$this->setLinkParent($share);
615
616
			/*
617
			 * For now ignore a set token.
618
			 */
619
			$share->setToken(
620
				$this->secureRandom->generate(
621
					\OC\Share\Constants::TOKEN_LENGTH,
622
					\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
623
				)
624
			);
625
626
			//Verify the expiration date
627
			$this->validateExpirationDate($share);
628
629
			//Verify the password
630
			$this->verifyPassword($share->getPassword());
631
632
			// If a password is set. Hash it!
633
			if ($share->getPassword() !== null) {
634
				$share->setPassword($this->hasher->hash($share->getPassword()));
635
			}
636
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
637
			$share->setToken(
638
				$this->secureRandom->generate(
639
					\OC\Share\Constants::TOKEN_LENGTH,
640
					\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
641
				)
642
			);
643
		}
644
645
		// Cannot share with the owner
646 View Code Duplication
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
647
			$share->getSharedWith() === $share->getShareOwner()) {
648
			throw new \InvalidArgumentException('Can’t share with the share owner');
649
		}
650
651
		// Generate the target
652
		$target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName();
653
		$target = \OC\Files\Filesystem::normalizePath($target);
654
		$share->setTarget($target);
655
656
		// Pre share event
657
		$event = new GenericEvent($share);
658
		$a = $this->eventDispatcher->dispatch('OCP\Share::preShare', $event);
0 ignored issues
show
Unused Code introduced by
$a is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
659
		if ($event->isPropagationStopped() && $event->hasArgument('error')) {
660
			throw new \Exception($event->getArgument('error'));
661
		}
662
663
		$oldShare = $share;
664
		$provider = $this->factory->getProviderForType($share->getShareType());
665
		$share = $provider->create($share);
666
		//reuse the node we already have
667
		$share->setNode($oldShare->getNode());
668
669
		// Post share event
670
		$event = new GenericEvent($share);
671
		$this->eventDispatcher->dispatch('OCP\Share::postShare', $event);
672
673
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
674
			$mailSend = $share->getMailSend();
675
			if($mailSend === true) {
676
				$user = $this->userManager->get($share->getSharedWith());
677
				if ($user !== null) {
678
					$emailAddress = $user->getEMailAddress();
679
					if ($emailAddress !== null && $emailAddress !== '') {
680
						$userLang = $this->config->getUserValue($share->getSharedWith(), 'core', 'lang', null);
681
						$l = $this->l10nFactory->get('lib', $userLang);
682
						$this->sendMailNotification(
683
							$l,
684
							$share->getNode()->getName(),
685
							$this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]),
686
							$share->getSharedBy(),
687
							$emailAddress,
688
							$share->getExpirationDate()
689
						);
690
						$this->logger->debug('Send share notification to ' . $emailAddress . ' for share with ID ' . $share->getId(), ['app' => 'share']);
691
					} else {
692
						$this->logger->debug('Share notification not send to ' . $share->getSharedWith() . ' because email address is not set.', ['app' => 'share']);
693
					}
694
				} else {
695
					$this->logger->debug('Share notification not send to ' . $share->getSharedWith() . ' because user could not be found.', ['app' => 'share']);
696
				}
697
			} else {
698
				$this->logger->debug('Share notification not send because mailsend is false.', ['app' => 'share']);
699
			}
700
		}
701
702
		return $share;
703
	}
704
705
	/**
706
	 * @param IL10N $l Language of the recipient
707
	 * @param string $filename file/folder name
708
	 * @param string $link link to the file/folder
709
	 * @param string $initiator user ID of share sender
710
	 * @param string $shareWith email address of share receiver
711
	 * @param \DateTime|null $expiration
712
	 * @throws \Exception If mail couldn't be sent
713
	 */
714
	protected function sendMailNotification(IL10N $l,
715
											$filename,
716
											$link,
717
											$initiator,
718
											$shareWith,
719
											\DateTime $expiration = null) {
720
		$initiatorUser = $this->userManager->get($initiator);
721
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
722
723
		$message = $this->mailer->createMessage();
724
725
		$emailTemplate = $this->mailer->createEMailTemplate('files_sharing.RecipientNotification', [
726
			'filename' => $filename,
727
			'link' => $link,
728
			'initiator' => $initiatorDisplayName,
729
			'expiration' => $expiration,
730
			'shareWith' => $shareWith,
731
		]);
732
733
		$emailTemplate->setSubject($l->t('%s shared »%s« with you', array($initiatorDisplayName, $filename)));
734
		$emailTemplate->addHeader();
735
		$emailTemplate->addHeading($l->t('%s shared »%s« with you', [$initiatorDisplayName, $filename]), false);
736
		$text = $l->t('%s shared »%s« with you.', [$initiatorDisplayName, $filename]);
737
738
		$emailTemplate->addBodyText(
739
			htmlspecialchars($text . ' ' . $l->t('Click the button below to open it.')),
740
			$text
741
		);
742
		$emailTemplate->addBodyButton(
743
			$l->t('Open »%s«', [$filename]),
744
			$link
745
		);
746
747
		$message->setTo([$shareWith]);
748
749
		// The "From" contains the sharers name
750
		$instanceName = $this->defaults->getName();
751
		$senderName = $l->t(
752
			'%s via %s',
753
			[
754
				$initiatorDisplayName,
755
				$instanceName
756
			]
757
		);
758
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
759
760
		// The "Reply-To" is set to the sharer if an mail address is configured
761
		// also the default footer contains a "Do not reply" which needs to be adjusted.
762
		$initiatorEmail = $initiatorUser->getEMailAddress();
763 View Code Duplication
		if($initiatorEmail !== null) {
764
			$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
765
			$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
766
		} else {
767
			$emailTemplate->addFooter();
768
		}
769
770
		$message->useTemplate($emailTemplate);
771
		$this->mailer->send($message);
772
	}
773
774
	/**
775
	 * Update a share
776
	 *
777
	 * @param \OCP\Share\IShare $share
778
	 * @return \OCP\Share\IShare The share object
779
	 * @throws \InvalidArgumentException
780
	 */
781
	public function updateShare(\OCP\Share\IShare $share) {
782
		$expirationDateUpdated = false;
783
784
		$this->canShare($share);
785
786
		try {
787
			$originalShare = $this->getShareById($share->getFullId());
788
		} catch (\UnexpectedValueException $e) {
789
			throw new \InvalidArgumentException('Share does not have a full id');
790
		}
791
792
		// We can't change the share type!
793
		if ($share->getShareType() !== $originalShare->getShareType()) {
794
			throw new \InvalidArgumentException('Can’t change share type');
795
		}
796
797
		// We can only change the recipient on user shares
798
		if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
799
		    $share->getShareType() !== \OCP\Share::SHARE_TYPE_USER) {
800
			throw new \InvalidArgumentException('Can only update recipient on user shares');
801
		}
802
803
		// Cannot share with the owner
804 View Code Duplication
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
805
			$share->getSharedWith() === $share->getShareOwner()) {
806
			throw new \InvalidArgumentException('Can’t share with the share owner');
807
		}
808
809
		$this->generalCreateChecks($share);
810
811
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
812
			$this->userCreateChecks($share);
813
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
814
			$this->groupCreateChecks($share);
815
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
816
			$this->linkCreateChecks($share);
817
818
			$this->updateSharePasswordIfNeeded($share, $originalShare);
819
820
			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
821
				//Verify the expiration date
822
				$this->validateExpirationDate($share);
823
				$expirationDateUpdated = true;
824
			}
825
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
826
			$plainTextPassword = $share->getPassword();
827
			if (!$this->updateSharePasswordIfNeeded($share, $originalShare)) {
828
				$plainTextPassword = null;
829
			}
830
		}
831
832
		$this->pathCreateChecks($share->getNode());
833
834
		// Now update the share!
835
		$provider = $this->factory->getProviderForType($share->getShareType());
836
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
837
			$share = $provider->update($share, $plainTextPassword);
0 ignored issues
show
Bug introduced by
The variable $plainTextPassword does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
838
		} else {
839
			$share = $provider->update($share);
840
		}
841
842
		if ($expirationDateUpdated === true) {
843
			\OC_Hook::emit(Share::class, 'post_set_expiration_date', [
844
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
845
				'itemSource' => $share->getNode()->getId(),
846
				'date' => $share->getExpirationDate(),
847
				'uidOwner' => $share->getSharedBy(),
848
			]);
849
		}
850
851
		if ($share->getPassword() !== $originalShare->getPassword()) {
852
			\OC_Hook::emit(Share::class, 'post_update_password', [
853
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
854
				'itemSource' => $share->getNode()->getId(),
855
				'uidOwner' => $share->getSharedBy(),
856
				'token' => $share->getToken(),
857
				'disabled' => is_null($share->getPassword()),
858
			]);
859
		}
860
861
		if ($share->getPermissions() !== $originalShare->getPermissions()) {
862
			if ($this->userManager->userExists($share->getShareOwner())) {
863
				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
864
			} else {
865
				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
866
			}
867
			\OC_Hook::emit(Share::class, 'post_update_permissions', array(
868
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
869
				'itemSource' => $share->getNode()->getId(),
870
				'shareType' => $share->getShareType(),
871
				'shareWith' => $share->getSharedWith(),
872
				'uidOwner' => $share->getSharedBy(),
873
				'permissions' => $share->getPermissions(),
874
				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
875
			));
876
		}
877
878
		return $share;
879
	}
880
881
	/**
882
	 * Updates the password of the given share if it is not the same as the
883
	 * password of the original share.
884
	 *
885
	 * @param \OCP\Share\IShare $share the share to update its password.
886
	 * @param \OCP\Share\IShare $originalShare the original share to compare its
887
	 *        password with.
888
	 * @return boolean whether the password was updated or not.
889
	 */
890
	private function updateSharePasswordIfNeeded(\OCP\Share\IShare $share, \OCP\Share\IShare $originalShare) {
891
		// Password updated.
892
		if ($share->getPassword() !== $originalShare->getPassword()) {
893
			//Verify the password
894
			$this->verifyPassword($share->getPassword());
895
896
			// If a password is set. Hash it!
897
			if ($share->getPassword() !== null) {
898
				$share->setPassword($this->hasher->hash($share->getPassword()));
899
900
				return true;
901
			}
902
		}
903
904
		return false;
905
	}
906
907
	/**
908
	 * Delete all the children of this share
909
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
910
	 *
911
	 * @param \OCP\Share\IShare $share
912
	 * @return \OCP\Share\IShare[] List of deleted shares
913
	 */
914
	protected function deleteChildren(\OCP\Share\IShare $share) {
915
		$deletedShares = [];
916
917
		$provider = $this->factory->getProviderForType($share->getShareType());
918
919
		foreach ($provider->getChildren($share) as $child) {
920
			$deletedChildren = $this->deleteChildren($child);
921
			$deletedShares = array_merge($deletedShares, $deletedChildren);
922
923
			$provider->delete($child);
924
			$deletedShares[] = $child;
925
		}
926
927
		return $deletedShares;
928
	}
929
930
	/**
931
	 * Delete a share
932
	 *
933
	 * @param \OCP\Share\IShare $share
934
	 * @throws ShareNotFound
935
	 * @throws \InvalidArgumentException
936
	 */
937
	public function deleteShare(\OCP\Share\IShare $share) {
938
939
		try {
940
			$share->getFullId();
941
		} catch (\UnexpectedValueException $e) {
942
			throw new \InvalidArgumentException('Share does not have a full id');
943
		}
944
945
		$event = new GenericEvent($share);
946
		$this->eventDispatcher->dispatch('OCP\Share::preUnshare', $event);
947
948
		// Get all children and delete them as well
949
		$deletedShares = $this->deleteChildren($share);
950
951
		// Do the actual delete
952
		$provider = $this->factory->getProviderForType($share->getShareType());
953
		$provider->delete($share);
954
955
		// All the deleted shares caused by this delete
956
		$deletedShares[] = $share;
957
958
		// Emit post hook
959
		$event->setArgument('deletedShares', $deletedShares);
960
		$this->eventDispatcher->dispatch('OCP\Share::postUnshare', $event);
961
	}
962
963
964
	/**
965
	 * Unshare a file as the recipient.
966
	 * This can be different from a regular delete for example when one of
967
	 * the users in a groups deletes that share. But the provider should
968
	 * handle this.
969
	 *
970
	 * @param \OCP\Share\IShare $share
971
	 * @param string $recipientId
972
	 */
973
	public function deleteFromSelf(\OCP\Share\IShare $share, $recipientId) {
974
		list($providerId, ) = $this->splitFullId($share->getFullId());
975
		$provider = $this->factory->getProvider($providerId);
976
977
		$provider->deleteFromSelf($share, $recipientId);
978
		$event = new GenericEvent($share);
979
		$this->eventDispatcher->dispatch('OCP\Share::postUnshareFromSelf', $event);
980
	}
981
982
	public function restoreShare(IShare $share, string $recipientId): IShare {
983
		list($providerId, ) = $this->splitFullId($share->getFullId());
984
		$provider = $this->factory->getProvider($providerId);
985
986
		return $provider->restore($share, $recipientId);
987
	}
988
989
	/**
990
	 * @inheritdoc
991
	 */
992
	public function moveShare(\OCP\Share\IShare $share, $recipientId) {
993
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
994
			throw new \InvalidArgumentException('Can’t change target of link share');
995
		}
996
997
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && $share->getSharedWith() !== $recipientId) {
998
			throw new \InvalidArgumentException('Invalid recipient');
999
		}
1000
1001
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
1002
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1003
			if (is_null($sharedWith)) {
1004
				throw new \InvalidArgumentException('Group "' . $share->getSharedWith() . '" does not exist');
1005
			}
1006
			$recipient = $this->userManager->get($recipientId);
1007
			if (!$sharedWith->inGroup($recipient)) {
0 ignored issues
show
Bug introduced by
It seems like $recipient defined by $this->userManager->get($recipientId) on line 1006 can be null; however, OCP\IGroup::inGroup() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1008
				throw new \InvalidArgumentException('Invalid recipient');
1009
			}
1010
		}
1011
1012
		list($providerId, ) = $this->splitFullId($share->getFullId());
1013
		$provider = $this->factory->getProvider($providerId);
1014
1015
		$provider->move($share, $recipientId);
1016
	}
1017
1018
	public function getSharesInFolder($userId, Folder $node, $reshares = false) {
1019
		$providers = $this->factory->getAllProviders();
1020
1021
		return array_reduce($providers, function($shares, IShareProvider $provider) use ($userId, $node, $reshares) {
1022
			$newShares = $provider->getSharesInFolder($userId, $node, $reshares);
1023
			foreach ($newShares as $fid => $data) {
1024
				if (!isset($shares[$fid])) {
1025
					$shares[$fid] = [];
1026
				}
1027
1028
				$shares[$fid] = array_merge($shares[$fid], $data);
1029
			}
1030
			return $shares;
1031
		}, []);
1032
	}
1033
1034
	/**
1035
	 * @inheritdoc
1036
	 */
1037
	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
1038
		if ($path !== null &&
1039
				!($path instanceof \OCP\Files\File) &&
1040
				!($path instanceof \OCP\Files\Folder)) {
1041
			throw new \InvalidArgumentException('invalid path');
1042
		}
1043
1044
		try {
1045
			$provider = $this->factory->getProviderForType($shareType);
1046
		} catch (ProviderException $e) {
1047
			return [];
1048
		}
1049
1050
		$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1051
1052
		/*
1053
		 * Work around so we don't return expired shares but still follow
1054
		 * proper pagination.
1055
		 */
1056
1057
		$shares2 = [];
1058
1059
		while(true) {
1060
			$added = 0;
1061
			foreach ($shares as $share) {
1062
1063
				try {
1064
					$this->checkExpireDate($share);
1065
				} catch (ShareNotFound $e) {
1066
					//Ignore since this basically means the share is deleted
1067
					continue;
1068
				}
1069
1070
				$added++;
1071
				$shares2[] = $share;
1072
1073
				if (count($shares2) === $limit) {
1074
					break;
1075
				}
1076
			}
1077
1078
			// If we did not fetch more shares than the limit then there are no more shares
1079
			if (count($shares) < $limit) {
1080
				break;
1081
			}
1082
1083
			if (count($shares2) === $limit) {
1084
				break;
1085
			}
1086
1087
			// If there was no limit on the select we are done
1088
			if ($limit === -1) {
1089
				break;
1090
			}
1091
1092
			$offset += $added;
1093
1094
			// Fetch again $limit shares
1095
			$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1096
1097
			// No more shares means we are done
1098
			if (empty($shares)) {
1099
				break;
1100
			}
1101
		}
1102
1103
		$shares = $shares2;
1104
1105
		return $shares;
1106
	}
1107
1108
	/**
1109
	 * @inheritdoc
1110
	 */
1111
	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1112
		try {
1113
			$provider = $this->factory->getProviderForType($shareType);
1114
		} catch (ProviderException $e) {
1115
			return [];
1116
		}
1117
1118
		$shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1119
1120
		// remove all shares which are already expired
1121
		foreach ($shares as $key => $share) {
1122
			try {
1123
				$this->checkExpireDate($share);
1124
			} catch (ShareNotFound $e) {
1125
				unset($shares[$key]);
1126
			}
1127
		}
1128
1129
		return $shares;
1130
	}
1131
1132
	/**
1133
	 * @inheritdoc
1134
	 */
1135
	public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1136
		$shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1137
1138
		// Only get deleted shares
1139
		$shares = array_filter($shares, function(IShare $share) {
1140
			return $share->getPermissions() === 0;
1141
		});
1142
1143
		// Only get shares where the owner still exists
1144
		$shares = array_filter($shares, function (IShare $share) {
1145
			return $this->userManager->userExists($share->getShareOwner());
1146
		});
1147
1148
		return $shares;
1149
	}
1150
1151
	/**
1152
	 * @inheritdoc
1153
	 */
1154
	public function getShareById($id, $recipient = null) {
1155
		if ($id === null) {
1156
			throw new ShareNotFound();
1157
		}
1158
1159
		list($providerId, $id) = $this->splitFullId($id);
1160
1161
		try {
1162
			$provider = $this->factory->getProvider($providerId);
1163
		} catch (ProviderException $e) {
1164
			throw new ShareNotFound();
1165
		}
1166
1167
		$share = $provider->getShareById($id, $recipient);
1168
1169
		$this->checkExpireDate($share);
1170
1171
		return $share;
1172
	}
1173
1174
	/**
1175
	 * Get all the shares for a given path
1176
	 *
1177
	 * @param \OCP\Files\Node $path
1178
	 * @param int $page
1179
	 * @param int $perPage
1180
	 *
1181
	 * @return Share[]
1182
	 */
1183
	public function getSharesByPath(\OCP\Files\Node $path, $page=0, $perPage=50) {
0 ignored issues
show
Unused Code introduced by
The parameter $path is not used and could be removed.

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

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

This check looks from 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.

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

Loading history...
1184
		return [];
1185
	}
1186
1187
	/**
1188
	 * Get the share by token possible with password
1189
	 *
1190
	 * @param string $token
1191
	 * @return Share
1192
	 *
1193
	 * @throws ShareNotFound
1194
	 */
1195
	public function getShareByToken($token) {
1196
		$share = null;
1197
		try {
1198
			if($this->shareApiAllowLinks()) {
1199
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_LINK);
1200
				$share = $provider->getShareByToken($token);
1201
			}
1202
		} catch (ProviderException $e) {
1203
		} catch (ShareNotFound $e) {
1204
		}
1205
1206
1207
		// If it is not a link share try to fetch a federated share by token
1208 View Code Duplication
		if ($share === null) {
1209
			try {
1210
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_REMOTE);
1211
				$share = $provider->getShareByToken($token);
1212
			} catch (ProviderException $e) {
1213
			} catch (ShareNotFound $e) {
1214
			}
1215
		}
1216
1217
		// If it is not a link share try to fetch a mail share by token
1218 View Code Duplication
		if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
1219
			try {
1220
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_EMAIL);
1221
				$share = $provider->getShareByToken($token);
1222
			} catch (ProviderException $e) {
1223
			} catch (ShareNotFound $e) {
1224
			}
1225
		}
1226
1227 View Code Duplication
		if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_CIRCLE)) {
1228
			try {
1229
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_CIRCLE);
1230
				$share = $provider->getShareByToken($token);
1231
			} catch (ProviderException $e) {
1232
			} catch (ShareNotFound $e) {
1233
			}
1234
		}
1235
1236
		if ($share === null) {
1237
			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1238
		}
1239
1240
		$this->checkExpireDate($share);
1241
1242
		/*
1243
		 * Reduce the permissions for link shares if public upload is not enabled
1244
		 */
1245
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1246
			!$this->shareApiLinkAllowPublicUpload()) {
1247
			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1248
		}
1249
1250
		return $share;
1251
	}
1252
1253
	protected function checkExpireDate($share) {
1254
		if ($share->getExpirationDate() !== null &&
1255
			$share->getExpirationDate() <= new \DateTime()) {
1256
			$this->deleteShare($share);
1257
			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1258
		}
1259
1260
	}
1261
1262
	/**
1263
	 * Verify the password of a public share
1264
	 *
1265
	 * @param \OCP\Share\IShare $share
1266
	 * @param string $password
1267
	 * @return bool
1268
	 */
1269
	public function checkPassword(\OCP\Share\IShare $share, $password) {
1270
		$passwordProtected = $share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK
1271
			|| $share->getShareType() !== \OCP\Share::SHARE_TYPE_EMAIL;
1272
		if (!$passwordProtected) {
1273
			//TODO maybe exception?
1274
			return false;
1275
		}
1276
1277
		if ($password === null || $share->getPassword() === null) {
1278
			return false;
1279
		}
1280
1281
		$newHash = '';
1282
		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1283
			return false;
1284
		}
1285
1286
		if (!empty($newHash)) {
1287
			$share->setPassword($newHash);
1288
			$provider = $this->factory->getProviderForType($share->getShareType());
1289
			$provider->update($share);
1290
		}
1291
1292
		return true;
1293
	}
1294
1295
	/**
1296
	 * @inheritdoc
1297
	 */
1298
	public function userDeleted($uid) {
1299
		$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];
1300
1301
		foreach ($types as $type) {
1302
			try {
1303
				$provider = $this->factory->getProviderForType($type);
1304
			} catch (ProviderException $e) {
1305
				continue;
1306
			}
1307
			$provider->userDeleted($uid, $type);
1308
		}
1309
	}
1310
1311
	/**
1312
	 * @inheritdoc
1313
	 */
1314
	public function groupDeleted($gid) {
1315
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1316
		$provider->groupDeleted($gid);
1317
	}
1318
1319
	/**
1320
	 * @inheritdoc
1321
	 */
1322
	public function userDeletedFromGroup($uid, $gid) {
1323
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1324
		$provider->userDeletedFromGroup($uid, $gid);
1325
	}
1326
1327
	/**
1328
	 * Get access list to a path. This means
1329
	 * all the users that can access a given path.
1330
	 *
1331
	 * Consider:
1332
	 * -root
1333
	 * |-folder1 (23)
1334
	 *  |-folder2 (32)
1335
	 *   |-fileA (42)
1336
	 *
1337
	 * fileA is shared with user1 and user1@server1
1338
	 * folder2 is shared with group2 (user4 is a member of group2)
1339
	 * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
1340
	 *
1341
	 * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
1342
	 * [
1343
	 *  users  => [
1344
	 *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
1345
	 *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
1346
	 *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
1347
	 *  ],
1348
	 *  remote => [
1349
	 *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
1350
	 *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
1351
	 *  ],
1352
	 *  public => bool
1353
	 *  mail => bool
1354
	 * ]
1355
	 *
1356
	 * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
1357
	 * [
1358
	 *  users  => ['user1', 'user2', 'user4'],
1359
	 *  remote => bool,
1360
	 *  public => bool
1361
	 *  mail => bool
1362
	 * ]
1363
	 *
1364
	 * This is required for encryption/activity
1365
	 *
1366
	 * @param \OCP\Files\Node $path
1367
	 * @param bool $recursive Should we check all parent folders as well
1368
	 * @param bool $currentAccess Ensure the recipient has access to the file (e.g. did not unshare it)
1369
	 * @return array
1370
	 */
1371
	public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1372
		$owner = $path->getOwner()->getUID();
1373
1374
		if ($currentAccess) {
1375
			$al = ['users' => [], 'remote' => [], 'public' => false];
1376
		} else {
1377
			$al = ['users' => [], 'remote' => false, 'public' => false];
1378
		}
1379
		if (!$this->userManager->userExists($owner)) {
1380
			return $al;
1381
		}
1382
1383
		//Get node for the owner
1384
		$userFolder = $this->rootFolder->getUserFolder($owner);
1385
		if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1386
			$path = $userFolder->getById($path->getId())[0];
1387
		}
1388
1389
		$providers = $this->factory->getAllProviders();
1390
1391
		/** @var Node[] $nodes */
1392
		$nodes = [];
1393
1394
1395
		if ($currentAccess) {
1396
			$ownerPath = $path->getPath();
1397
			$ownerPath = explode('/', $ownerPath, 4);
1398
			if (count($ownerPath) < 4) {
1399
				$ownerPath = '';
1400
			} else {
1401
				$ownerPath = $ownerPath[3];
1402
			}
1403
			$al['users'][$owner] = [
1404
				'node_id' => $path->getId(),
1405
				'node_path' => '/' . $ownerPath,
1406
			];
1407
		} else {
1408
			$al['users'][] = $owner;
1409
		}
1410
1411
		// Collect all the shares
1412
		while ($path->getPath() !== $userFolder->getPath()) {
1413
			$nodes[] = $path;
1414
			if (!$recursive) {
1415
				break;
1416
			}
1417
			$path = $path->getParent();
1418
		}
1419
1420
		foreach ($providers as $provider) {
1421
			$tmp = $provider->getAccessList($nodes, $currentAccess);
1422
1423
			foreach ($tmp as $k => $v) {
1424
				if (isset($al[$k])) {
1425
					if (is_array($al[$k])) {
1426
						if ($currentAccess) {
1427
							$al[$k] += $v;
1428
						} else {
1429
							$al[$k] = array_merge($al[$k], $v);
1430
							$al[$k] = array_unique($al[$k]);
1431
							$al[$k] = array_values($al[$k]);
1432
						}
1433
					} else {
1434
						$al[$k] = $al[$k] || $v;
1435
					}
1436
				} else {
1437
					$al[$k] = $v;
1438
				}
1439
			}
1440
		}
1441
1442
		return $al;
1443
	}
1444
1445
	/**
1446
	 * Create a new share
1447
	 * @return \OCP\Share\IShare
1448
	 */
1449
	public function newShare() {
1450
		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1451
	}
1452
1453
	/**
1454
	 * Is the share API enabled
1455
	 *
1456
	 * @return bool
1457
	 */
1458
	public function shareApiEnabled() {
1459
		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1460
	}
1461
1462
	/**
1463
	 * Is public link sharing enabled
1464
	 *
1465
	 * @return bool
1466
	 */
1467
	public function shareApiAllowLinks() {
1468
		return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
1469
	}
1470
1471
	/**
1472
	 * Is password on public link requires
1473
	 *
1474
	 * @return bool
1475
	 */
1476
	public function shareApiLinkEnforcePassword() {
1477
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1478
	}
1479
1480
	/**
1481
	 * Is default expire date enabled
1482
	 *
1483
	 * @return bool
1484
	 */
1485
	public function shareApiLinkDefaultExpireDate() {
1486
		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1487
	}
1488
1489
	/**
1490
	 * Is default expire date enforced
1491
	 *`
1492
	 * @return bool
1493
	 */
1494
	public function shareApiLinkDefaultExpireDateEnforced() {
1495
		return $this->shareApiLinkDefaultExpireDate() &&
1496
			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1497
	}
1498
1499
	/**
1500
	 * Number of default expire days
1501
	 *shareApiLinkAllowPublicUpload
1502
	 * @return int
1503
	 */
1504
	public function shareApiLinkDefaultExpireDays() {
1505
		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1506
	}
1507
1508
	/**
1509
	 * Allow public upload on link shares
1510
	 *
1511
	 * @return bool
1512
	 */
1513
	public function shareApiLinkAllowPublicUpload() {
1514
		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1515
	}
1516
1517
	/**
1518
	 * check if user can only share with group members
1519
	 * @return bool
1520
	 */
1521
	public function shareWithGroupMembersOnly() {
1522
		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1523
	}
1524
1525
	/**
1526
	 * Check if users can share with groups
1527
	 * @return bool
1528
	 */
1529
	public function allowGroupSharing() {
1530
		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1531
	}
1532
1533
	/**
1534
	 * Copied from \OC_Util::isSharingDisabledForUser
1535
	 *
1536
	 * TODO: Deprecate fuction from OC_Util
1537
	 *
1538
	 * @param string $userId
1539
	 * @return bool
1540
	 */
1541
	public function sharingDisabledForUser($userId) {
1542
		if ($userId === null) {
1543
			return false;
1544
		}
1545
1546
		if (isset($this->sharingDisabledForUsersCache[$userId])) {
1547
			return $this->sharingDisabledForUsersCache[$userId];
1548
		}
1549
1550
		if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
1551
			$groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1552
			$excludedGroups = json_decode($groupsList);
1553 View Code Duplication
			if (is_null($excludedGroups)) {
1554
				$excludedGroups = explode(',', $groupsList);
1555
				$newValue = json_encode($excludedGroups);
1556
				$this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
1557
			}
1558
			$user = $this->userManager->get($userId);
1559
			$usersGroups = $this->groupManager->getUserGroupIds($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($userId) on line 1558 can be null; however, OCP\IGroupManager::getUserGroupIds() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1560 View Code Duplication
			if (!empty($usersGroups)) {
1561
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
1562
				// if the user is only in groups which are disabled for sharing then
1563
				// sharing is also disabled for the user
1564
				if (empty($remainingGroups)) {
1565
					$this->sharingDisabledForUsersCache[$userId] = true;
1566
					return true;
1567
				}
1568
			}
1569
		}
1570
1571
		$this->sharingDisabledForUsersCache[$userId] = false;
1572
		return false;
1573
	}
1574
1575
	/**
1576
	 * @inheritdoc
1577
	 */
1578
	public function outgoingServer2ServerSharesAllowed() {
1579
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1580
	}
1581
1582
	/**
1583
	 * @inheritdoc
1584
	 */
1585
	public function shareProviderExists($shareType) {
1586
		try {
1587
			$this->factory->getProviderForType($shareType);
1588
		} catch (ProviderException $e) {
1589
			return false;
1590
		}
1591
1592
		return true;
1593
	}
1594
1595
}
1596