Completed
Push — master ( ea801d...4bfd4b )
by Lukas
24s
created

Manager::getShareByToken()   F

Complexity

Conditions 15
Paths 720

Size

Total Lines 50
Code Lines 29

Duplication

Lines 16
Ratio 32 %

Importance

Changes 0
Metric Value
cc 15
eloc 29
nc 720
nop 1
dl 16
loc 50
rs 2.9695
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Joas Schilling <[email protected]>
9
 * @author Roeland Jago Douma <[email protected]>
10
 * @author Vincent Petry <[email protected]>
11
 *
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License, version 3,
24
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
28
namespace OC\Share20;
29
30
use OC\Cache\CappedMemoryCache;
31
use OC\Files\Mount\MoveableMount;
32
use OC\HintException;
33
use OC\Share20\Exception\ProviderException;
34
use OCP\Files\File;
35
use OCP\Files\Folder;
36
use OCP\Files\IRootFolder;
37
use OCP\Files\Mount\IMountManager;
38
use OCP\Files\Node;
39
use OCP\Files\NotFoundException;
40
use OCP\IConfig;
41
use OCP\IGroupManager;
42
use OCP\IL10N;
43
use OCP\ILogger;
44
use OCP\IUserManager;
45
use OCP\Security\IHasher;
46
use OCP\Security\ISecureRandom;
47
use OCP\Share\Exceptions\GenericShareException;
48
use OCP\Share\Exceptions\ShareNotFound;
49
use OCP\Share\IManager;
50
use OCP\Share\IProviderFactory;
51
use Symfony\Component\EventDispatcher\EventDispatcher;
52
use Symfony\Component\EventDispatcher\GenericEvent;
53
use OCP\Share\IShareProvider;
54
55
/**
56
 * This class is the communication hub for all sharing related operations.
57
 */
58
class Manager implements IManager {
59
60
	/** @var IProviderFactory */
61
	private $factory;
62
	/** @var ILogger */
63
	private $logger;
64
	/** @var IConfig */
65
	private $config;
66
	/** @var ISecureRandom */
67
	private $secureRandom;
68
	/** @var IHasher */
69
	private $hasher;
70
	/** @var IMountManager */
71
	private $mountManager;
72
	/** @var IGroupManager */
73
	private $groupManager;
74
	/** @var IL10N */
75
	private $l;
76
	/** @var IUserManager */
77
	private $userManager;
78
	/** @var IRootFolder */
79
	private $rootFolder;
80
	/** @var CappedMemoryCache */
81
	private $sharingDisabledForUsersCache;
82
	/** @var EventDispatcher */
83
	private $eventDispatcher;
84
85
86
	/**
87
	 * Manager constructor.
88
	 *
89
	 * @param ILogger $logger
90
	 * @param IConfig $config
91
	 * @param ISecureRandom $secureRandom
92
	 * @param IHasher $hasher
93
	 * @param IMountManager $mountManager
94
	 * @param IGroupManager $groupManager
95
	 * @param IL10N $l
96
	 * @param IProviderFactory $factory
97
	 * @param IUserManager $userManager
98
	 * @param IRootFolder $rootFolder
99
	 * @param EventDispatcher $eventDispatcher
100
	 */
101
	public function __construct(
102
			ILogger $logger,
103
			IConfig $config,
104
			ISecureRandom $secureRandom,
105
			IHasher $hasher,
106
			IMountManager $mountManager,
107
			IGroupManager $groupManager,
108
			IL10N $l,
109
			IProviderFactory $factory,
110
			IUserManager $userManager,
111
			IRootFolder $rootFolder,
112
			EventDispatcher $eventDispatcher
113
	) {
114
		$this->logger = $logger;
115
		$this->config = $config;
116
		$this->secureRandom = $secureRandom;
117
		$this->hasher = $hasher;
118
		$this->mountManager = $mountManager;
119
		$this->groupManager = $groupManager;
120
		$this->l = $l;
121
		$this->factory = $factory;
122
		$this->userManager = $userManager;
123
		$this->rootFolder = $rootFolder;
124
		$this->eventDispatcher = $eventDispatcher;
125
		$this->sharingDisabledForUsersCache = new CappedMemoryCache();
126
	}
127
128
	/**
129
	 * Convert from a full share id to a tuple (providerId, shareId)
130
	 *
131
	 * @param string $id
132
	 * @return string[]
133
	 */
134
	private function splitFullId($id) {
135
		return explode(':', $id, 2);
136
	}
137
138
	/**
139
	 * Verify if a password meets all requirements
140
	 *
141
	 * @param string $password
142
	 * @throws \Exception
143
	 */
144
	protected function verifyPassword($password) {
145
		if ($password === null) {
146
			// No password is set, check if this is allowed.
147
			if ($this->shareApiLinkEnforcePassword()) {
148
				throw new \InvalidArgumentException('Passwords are enforced for link shares');
149
			}
150
151
			return;
152
		}
153
154
		// Let others verify the password
155
		try {
156
			$event = new GenericEvent($password);
157
			$this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
158
		} catch (HintException $e) {
159
			throw new \Exception($e->getHint());
160
		}
161
	}
162
163
	/**
164
	 * Check for generic requirements before creating a share
165
	 *
166
	 * @param \OCP\Share\IShare $share
167
	 * @throws \InvalidArgumentException
168
	 * @throws GenericShareException
169
	 */
170
	protected function generalCreateChecks(\OCP\Share\IShare $share) {
171
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
172
			// We expect a valid user as sharedWith for user shares
173
			if (!$this->userManager->userExists($share->getSharedWith())) {
174
				throw new \InvalidArgumentException('SharedWith is not a valid user');
175
			}
176
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
177
			// We expect a valid group as sharedWith for group shares
178
			if (!$this->groupManager->groupExists($share->getSharedWith())) {
179
				throw new \InvalidArgumentException('SharedWith is not a valid group');
180
			}
181
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
182
			if ($share->getSharedWith() !== null) {
183
				throw new \InvalidArgumentException('SharedWith should be empty');
184
			}
185
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
186
			if ($share->getSharedWith() === null) {
187
				throw new \InvalidArgumentException('SharedWith should not be empty');
188
			}
189
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
190
			if ($share->getSharedWith() === null) {
191
				throw new \InvalidArgumentException('SharedWith should not be empty');
192
			}
193
		} else {
194
			// We can't handle other types yet
195
			throw new \InvalidArgumentException('unkown share type');
196
		}
197
198
		// Verify the initiator of the share is set
199
		if ($share->getSharedBy() === null) {
200
			throw new \InvalidArgumentException('SharedBy should be set');
201
		}
202
203
		// Cannot share with yourself
204 View Code Duplication
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
205
			$share->getSharedWith() === $share->getSharedBy()) {
206
			throw new \InvalidArgumentException('Can\'t share with yourself');
207
		}
208
209
		// The path should be set
210
		if ($share->getNode() === null) {
211
			throw new \InvalidArgumentException('Path should be set');
212
		}
213
214
		// And it should be a file or a folder
215
		if (!($share->getNode() instanceof \OCP\Files\File) &&
216
				!($share->getNode() instanceof \OCP\Files\Folder)) {
217
			throw new \InvalidArgumentException('Path should be either a file or a folder');
218
		}
219
220
		// And you can't share your rootfolder
221
		if ($this->userManager->userExists($share->getSharedBy())) {
222
			$sharedPath = $this->rootFolder->getUserFolder($share->getSharedBy())->getPath();
223
		} else {
224
			$sharedPath = $this->rootFolder->getUserFolder($share->getShareOwner())->getPath();
225
		}
226
		if ($sharedPath === $share->getNode()->getPath()) {
227
			throw new \InvalidArgumentException('You can\'t share your root folder');
228
		}
229
230
		// Check if we actually have share permissions
231 View Code Duplication
		if (!$share->getNode()->isShareable()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
232
			$message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getPath()]);
233
			throw new GenericShareException($message_t, $message_t, 404);
234
		}
235
236
		// Permissions should be set
237
		if ($share->getPermissions() === null) {
238
			throw new \InvalidArgumentException('A share requires permissions');
239
		}
240
241
		/*
242
		 * Quick fix for #23536
243
		 * Non moveable mount points do not have update and delete permissions
244
		 * while we 'most likely' do have that on the storage.
245
		 */
246
		$permissions = $share->getNode()->getPermissions();
247
		$mount = $share->getNode()->getMountPoint();
248
		if (!($mount instanceof MoveableMount)) {
249
			$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
250
		}
251
252
		// Check that we do not share with more permissions than we have
253 View Code Duplication
		if ($share->getPermissions() & ~$permissions) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
254
			$message_t = $this->l->t('Cannot increase permissions of %s', [$share->getNode()->getPath()]);
255
			throw new GenericShareException($message_t, $message_t, 404);
256
		}
257
258
259
		// Check that read permissions are always set
260
		// Link shares are allowed to have no read permissions to allow upload to hidden folders
261
		if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK &&
262
			($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
263
			throw new \InvalidArgumentException('Shares need at least read permissions');
264
		}
265
266
		if ($share->getNode() instanceof \OCP\Files\File) {
267 View Code Duplication
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
268
				$message_t = $this->l->t('Files can\'t be shared with delete permissions');
269
				throw new GenericShareException($message_t);
270
			}
271 View Code Duplication
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
272
				$message_t = $this->l->t('Files can\'t be shared with create permissions');
273
				throw new GenericShareException($message_t);
274
			}
275
		}
276
	}
277
278
	/**
279
	 * Validate if the expiration date fits the system settings
280
	 *
281
	 * @param \OCP\Share\IShare $share The share to validate the expiration date of
282
	 * @return \OCP\Share\IShare The modified share object
283
	 * @throws GenericShareException
284
	 * @throws \InvalidArgumentException
285
	 * @throws \Exception
286
	 */
287
	protected function validateExpirationDate(\OCP\Share\IShare $share) {
288
289
		$expirationDate = $share->getExpirationDate();
290
291
		if ($expirationDate !== null) {
292
			//Make sure the expiration date is a date
293
			$expirationDate->setTime(0, 0, 0);
294
295
			$date = new \DateTime();
296
			$date->setTime(0, 0, 0);
297 View Code Duplication
			if ($date >= $expirationDate) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
				$message = $this->l->t('Expiration date is in the past');
299
				throw new GenericShareException($message, $message, 404);
300
			}
301
		}
302
303
		// If expiredate is empty set a default one if there is a default
304
		$fullId = null;
305
		try {
306
			$fullId = $share->getFullId();
307
		} catch (\UnexpectedValueException $e) {
308
			// This is a new share
309
		}
310
311
		if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
312
			$expirationDate = new \DateTime();
313
			$expirationDate->setTime(0,0,0);
314
			$expirationDate->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
315
		}
316
317
		// If we enforce the expiration date check that is does not exceed
318
		if ($this->shareApiLinkDefaultExpireDateEnforced()) {
319
			if ($expirationDate === null) {
320
				throw new \InvalidArgumentException('Expiration date is enforced');
321
			}
322
323
			$date = new \DateTime();
324
			$date->setTime(0, 0, 0);
325
			$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
326 View Code Duplication
			if ($date < $expirationDate) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
327
				$message = $this->l->t('Cannot set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
328
				throw new GenericShareException($message, $message, 404);
329
			}
330
		}
331
332
		$accepted = true;
333
		$message = '';
334
		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
335
			'expirationDate' => &$expirationDate,
336
			'accepted' => &$accepted,
337
			'message' => &$message,
338
			'passwordSet' => $share->getPassword() !== null,
339
		]);
340
341
		if (!$accepted) {
342
			throw new \Exception($message);
343
		}
344
345
		$share->setExpirationDate($expirationDate);
346
347
		return $share;
348
	}
349
350
	/**
351
	 * Check for pre share requirements for user shares
352
	 *
353
	 * @param \OCP\Share\IShare $share
354
	 * @throws \Exception
355
	 */
356
	protected function userCreateChecks(\OCP\Share\IShare $share) {
357
		// Check if we can share with group members only
358
		if ($this->shareWithGroupMembersOnly()) {
359
			$sharedBy = $this->userManager->get($share->getSharedBy());
360
			$sharedWith = $this->userManager->get($share->getSharedWith());
361
			// Verify we can share with this user
362
			$groups = array_intersect(
363
					$this->groupManager->getUserGroupIds($sharedBy),
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 359 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...
364
					$this->groupManager->getUserGroupIds($sharedWith)
0 ignored issues
show
Bug introduced by
It seems like $sharedWith defined by $this->userManager->get($share->getSharedWith()) on line 360 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...
365
			);
366
			if (empty($groups)) {
367
				throw new \Exception('Only sharing with group members is allowed');
368
			}
369
		}
370
371
		/*
372
		 * TODO: Could be costly, fix
373
		 *
374
		 * Also this is not what we want in the future.. then we want to squash identical shares.
375
		 */
376
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_USER);
377
		$existingShares = $provider->getSharesByPath($share->getNode());
378
		foreach($existingShares as $existingShare) {
379
			// Ignore if it is the same share
380
			try {
381
				if ($existingShare->getFullId() === $share->getFullId()) {
382
					continue;
383
				}
384
			} catch (\UnexpectedValueException $e) {
385
				//Shares are not identical
386
			}
387
388
			// Identical share already existst
389
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
390
				throw new \Exception('Path already shared with this user');
391
			}
392
393
			// The share is already shared with this user via a group share
394
			if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
395
				$group = $this->groupManager->get($existingShare->getSharedWith());
396
				$user = $this->userManager->get($share->getSharedWith());
397
398
				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 396 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...
399
					throw new \Exception('Path already shared with this user');
400
				}
401
			}
402
		}
403
	}
404
405
	/**
406
	 * Check for pre share requirements for group shares
407
	 *
408
	 * @param \OCP\Share\IShare $share
409
	 * @throws \Exception
410
	 */
411
	protected function groupCreateChecks(\OCP\Share\IShare $share) {
412
		// Verify group shares are allowed
413
		if (!$this->allowGroupSharing()) {
414
			throw new \Exception('Group sharing is now allowed');
415
		}
416
417
		// Verify if the user can share with this group
418
		if ($this->shareWithGroupMembersOnly()) {
419
			$sharedBy = $this->userManager->get($share->getSharedBy());
420
			$sharedWith = $this->groupManager->get($share->getSharedWith());
421
			if (!$sharedWith->inGroup($sharedBy)) {
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 419 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...
422
				throw new \Exception('Only sharing within your own groups is allowed');
423
			}
424
		}
425
426
		/*
427
		 * TODO: Could be costly, fix
428
		 *
429
		 * Also this is not what we want in the future.. then we want to squash identical shares.
430
		 */
431
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
432
		$existingShares = $provider->getSharesByPath($share->getNode());
433
		foreach($existingShares as $existingShare) {
434
			try {
435
				if ($existingShare->getFullId() === $share->getFullId()) {
436
					continue;
437
				}
438
			} catch (\UnexpectedValueException $e) {
439
				//It is a new share so just continue
440
			}
441
442
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
443
				throw new \Exception('Path already shared with this group');
444
			}
445
		}
446
	}
447
448
	/**
449
	 * Check for pre share requirements for link shares
450
	 *
451
	 * @param \OCP\Share\IShare $share
452
	 * @throws \Exception
453
	 */
454
	protected function linkCreateChecks(\OCP\Share\IShare $share) {
455
		// Are link shares allowed?
456
		if (!$this->shareApiAllowLinks()) {
457
			throw new \Exception('Link sharing not allowed');
458
		}
459
460
		// Link shares by definition can't have share permissions
461
		if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) {
462
			throw new \InvalidArgumentException('Link shares can\'t have reshare permissions');
463
		}
464
465
		// Check if public upload is allowed
466
		if (!$this->shareApiLinkAllowPublicUpload() &&
467
			($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
468
			throw new \InvalidArgumentException('Public upload not allowed');
469
		}
470
	}
471
472
	/**
473
	 * To make sure we don't get invisible link shares we set the parent
474
	 * of a link if it is a reshare. This is a quick word around
475
	 * until we can properly display multiple link shares in the UI
476
	 *
477
	 * See: https://github.com/owncloud/core/issues/22295
478
	 *
479
	 * FIXME: Remove once multiple link shares can be properly displayed
480
	 *
481
	 * @param \OCP\Share\IShare $share
482
	 */
483
	protected function setLinkParent(\OCP\Share\IShare $share) {
484
485
		// No sense in checking if the method is not there.
486
		if (method_exists($share, 'setParent')) {
487
			$storage = $share->getNode()->getStorage();
488
			if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
489
				/** @var \OCA\Files_Sharing\SharedStorage $storage */
490
				$share->setParent($storage->getShareId());
491
			}
492
		};
493
	}
494
495
	/**
496
	 * @param File|Folder $path
497
	 */
498
	protected function pathCreateChecks($path) {
499
		// Make sure that we do not share a path that contains a shared mountpoint
500
		if ($path instanceof \OCP\Files\Folder) {
501
			$mounts = $this->mountManager->findIn($path->getPath());
502
			foreach($mounts as $mount) {
503
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
504
					throw new \InvalidArgumentException('Path contains files shared with you');
505
				}
506
			}
507
		}
508
	}
509
510
	/**
511
	 * Check if the user that is sharing can actually share
512
	 *
513
	 * @param \OCP\Share\IShare $share
514
	 * @throws \Exception
515
	 */
516
	protected function canShare(\OCP\Share\IShare $share) {
517
		if (!$this->shareApiEnabled()) {
518
			throw new \Exception('The share API is disabled');
519
		}
520
521
		if ($this->sharingDisabledForUser($share->getSharedBy())) {
522
			throw new \Exception('You are not allowed to share');
523
		}
524
	}
525
526
	/**
527
	 * Share a path
528
	 *
529
	 * @param \OCP\Share\IShare $share
530
	 * @return Share The share object
531
	 * @throws \Exception
532
	 *
533
	 * TODO: handle link share permissions or check them
534
	 */
535
	public function createShare(\OCP\Share\IShare $share) {
536
		$this->canShare($share);
537
538
		$this->generalCreateChecks($share);
539
540
		// Verify if there are any issues with the path
541
		$this->pathCreateChecks($share->getNode());
542
543
		/*
544
		 * On creation of a share the owner is always the owner of the path
545
		 * Except for mounted federated shares.
546
		 */
547
		$storage = $share->getNode()->getStorage();
548
		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
549
			$parent = $share->getNode()->getParent();
550
			while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
551
				$parent = $parent->getParent();
552
			}
553
			$share->setShareOwner($parent->getOwner()->getUID());
554
		} else {
555
			$share->setShareOwner($share->getNode()->getOwner()->getUID());
556
		}
557
558
		//Verify share type
559
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
560
			$this->userCreateChecks($share);
561
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
562
			$this->groupCreateChecks($share);
563
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
564
			$this->linkCreateChecks($share);
565
			$this->setLinkParent($share);
566
567
			/*
568
			 * For now ignore a set token.
569
			 */
570
			$share->setToken(
571
				$this->secureRandom->generate(
572
					\OC\Share\Constants::TOKEN_LENGTH,
573
					\OCP\Security\ISecureRandom::CHAR_LOWER.
574
					\OCP\Security\ISecureRandom::CHAR_UPPER.
575
					\OCP\Security\ISecureRandom::CHAR_DIGITS
576
				)
577
			);
578
579
			//Verify the expiration date
580
			$this->validateExpirationDate($share);
581
582
			//Verify the password
583
			$this->verifyPassword($share->getPassword());
584
585
			// If a password is set. Hash it!
586
			if ($share->getPassword() !== null) {
587
				$share->setPassword($this->hasher->hash($share->getPassword()));
588
			}
589
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
590
			$this->linkCreateChecks($share);
591
			$share->setToken(
592
				$this->secureRandom->generate(
593
					\OC\Share\Constants::TOKEN_LENGTH,
594
					\OCP\Security\ISecureRandom::CHAR_LOWER.
595
					\OCP\Security\ISecureRandom::CHAR_UPPER.
596
					\OCP\Security\ISecureRandom::CHAR_DIGITS
597
				)
598
			);
599
		}
600
601
		// Cannot share with the owner
602 View Code Duplication
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
603
			$share->getSharedWith() === $share->getShareOwner()) {
604
			throw new \InvalidArgumentException('Can\'t share with the share owner');
605
		}
606
607
		// Generate the target
608
		$target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName();
609
		$target = \OC\Files\Filesystem::normalizePath($target);
610
		$share->setTarget($target);
611
612
		// Pre share hook
613
		$run = true;
614
		$error = '';
615
		$preHookData = [
616
			'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
617
			'itemSource' => $share->getNode()->getId(),
618
			'shareType' => $share->getShareType(),
619
			'uidOwner' => $share->getSharedBy(),
620
			'permissions' => $share->getPermissions(),
621
			'fileSource' => $share->getNode()->getId(),
622
			'expiration' => $share->getExpirationDate(),
623
			'token' => $share->getToken(),
624
			'itemTarget' => $share->getTarget(),
625
			'shareWith' => $share->getSharedWith(),
626
			'run' => &$run,
627
			'error' => &$error,
628
		];
629
		\OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
630
631
		if ($run === false) {
632
			throw new \Exception($error);
633
		}
634
635
		$oldShare = $share;
636
		$provider = $this->factory->getProviderForType($share->getShareType());
637
		$share = $provider->create($share);
638
		//reuse the node we already have
639
		$share->setNode($oldShare->getNode());
640
641
		// Post share hook
642
		$postHookData = [
643
			'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
644
			'itemSource' => $share->getNode()->getId(),
645
			'shareType' => $share->getShareType(),
646
			'uidOwner' => $share->getSharedBy(),
647
			'permissions' => $share->getPermissions(),
648
			'fileSource' => $share->getNode()->getId(),
649
			'expiration' => $share->getExpirationDate(),
650
			'token' => $share->getToken(),
651
			'id' => $share->getId(),
652
			'shareWith' => $share->getSharedWith(),
653
			'itemTarget' => $share->getTarget(),
654
			'fileTarget' => $share->getTarget(),
655
		];
656
657
		\OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
658
659
		return $share;
660
	}
661
662
	/**
663
	 * Update a share
664
	 *
665
	 * @param \OCP\Share\IShare $share
666
	 * @return \OCP\Share\IShare The share object
667
	 * @throws \InvalidArgumentException
668
	 */
669
	public function updateShare(\OCP\Share\IShare $share) {
670
		$expirationDateUpdated = false;
671
672
		$this->canShare($share);
673
674
		try {
675
			$originalShare = $this->getShareById($share->getFullId());
676
		} catch (\UnexpectedValueException $e) {
677
			throw new \InvalidArgumentException('Share does not have a full id');
678
		}
679
680
		// We can't change the share type!
681
		if ($share->getShareType() !== $originalShare->getShareType()) {
682
			throw new \InvalidArgumentException('Can\'t change share type');
683
		}
684
685
		// We can only change the recipient on user shares
686
		if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
687
		    $share->getShareType() !== \OCP\Share::SHARE_TYPE_USER) {
688
			throw new \InvalidArgumentException('Can only update recipient on user shares');
689
		}
690
691
		// Cannot share with the owner
692 View Code Duplication
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
693
			$share->getSharedWith() === $share->getShareOwner()) {
694
			throw new \InvalidArgumentException('Can\'t share with the share owner');
695
		}
696
697
		$this->generalCreateChecks($share);
698
699
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
700
			$this->userCreateChecks($share);
701
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
702
			$this->groupCreateChecks($share);
703
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
704
			$this->linkCreateChecks($share);
705
706
			// Password updated.
707
			if ($share->getPassword() !== $originalShare->getPassword()) {
708
				//Verify the password
709
				$this->verifyPassword($share->getPassword());
710
711
				// If a password is set. Hash it!
712
				if ($share->getPassword() !== null) {
713
					$share->setPassword($this->hasher->hash($share->getPassword()));
714
				}
715
			}
716
717
			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
718
				//Verify the expiration date
719
				$this->validateExpirationDate($share);
720
				$expirationDateUpdated = true;
721
			}
722
		}
723
724
		$this->pathCreateChecks($share->getNode());
725
726
		// Now update the share!
727
		$provider = $this->factory->getProviderForType($share->getShareType());
728
		$share = $provider->update($share);
729
730
		if ($expirationDateUpdated === true) {
731
			\OC_Hook::emit('OCP\Share', 'post_set_expiration_date', [
732
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
733
				'itemSource' => $share->getNode()->getId(),
734
				'date' => $share->getExpirationDate(),
735
				'uidOwner' => $share->getSharedBy(),
736
			]);
737
		}
738
739
		if ($share->getPassword() !== $originalShare->getPassword()) {
740
			\OC_Hook::emit('OCP\Share', 'post_update_password', [
741
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
742
				'itemSource' => $share->getNode()->getId(),
743
				'uidOwner' => $share->getSharedBy(),
744
				'token' => $share->getToken(),
745
				'disabled' => is_null($share->getPassword()),
746
			]);
747
		}
748
749
		if ($share->getPermissions() !== $originalShare->getPermissions()) {
750
			if ($this->userManager->userExists($share->getShareOwner())) {
751
				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
752
			} else {
753
				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
754
			}
755
			\OC_Hook::emit('OCP\Share', 'post_update_permissions', array(
756
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
757
				'itemSource' => $share->getNode()->getId(),
758
				'shareType' => $share->getShareType(),
759
				'shareWith' => $share->getSharedWith(),
760
				'uidOwner' => $share->getSharedBy(),
761
				'permissions' => $share->getPermissions(),
762
				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
763
			));
764
		}
765
766
		return $share;
767
	}
768
769
	/**
770
	 * Delete all the children of this share
771
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
772
	 *
773
	 * @param \OCP\Share\IShare $share
774
	 * @return \OCP\Share\IShare[] List of deleted shares
775
	 */
776
	protected function deleteChildren(\OCP\Share\IShare $share) {
777
		$deletedShares = [];
778
779
		$provider = $this->factory->getProviderForType($share->getShareType());
780
781
		foreach ($provider->getChildren($share) as $child) {
782
			$deletedChildren = $this->deleteChildren($child);
783
			$deletedShares = array_merge($deletedShares, $deletedChildren);
784
785
			$provider->delete($child);
786
			$deletedShares[] = $child;
787
		}
788
789
		return $deletedShares;
790
	}
791
792
	/**
793
	 * Delete a share
794
	 *
795
	 * @param \OCP\Share\IShare $share
796
	 * @throws ShareNotFound
797
	 * @throws \InvalidArgumentException
798
	 */
799
	public function deleteShare(\OCP\Share\IShare $share) {
800
801
		try {
802
			$share->getFullId();
803
		} catch (\UnexpectedValueException $e) {
804
			throw new \InvalidArgumentException('Share does not have a full id');
805
		}
806
807
		$formatHookParams = function(\OCP\Share\IShare $share) {
808
			// Prepare hook
809
			$shareType = $share->getShareType();
810
			$sharedWith = '';
811
			if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
812
				$sharedWith = $share->getSharedWith();
813
			} else if ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
814
				$sharedWith = $share->getSharedWith();
815
			} else if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) {
816
				$sharedWith = $share->getSharedWith();
817
			}
818
819
			$hookParams = [
820
				'id'         => $share->getId(),
821
				'itemType'   => $share->getNodeType(),
822
				'itemSource' => $share->getNodeId(),
823
				'shareType'  => $shareType,
824
				'shareWith'  => $sharedWith,
825
				'itemparent' => method_exists($share, 'getParent') ? $share->getParent() : '',
826
				'uidOwner'   => $share->getSharedBy(),
827
				'fileSource' => $share->getNodeId(),
828
				'fileTarget' => $share->getTarget()
829
			];
830
			return $hookParams;
831
		};
832
833
		$hookParams = $formatHookParams($share);
834
835
		// Emit pre-hook
836
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
837
838
		// Get all children and delete them as well
839
		$deletedShares = $this->deleteChildren($share);
840
841
		// Do the actual delete
842
		$provider = $this->factory->getProviderForType($share->getShareType());
843
		$provider->delete($share);
844
845
		// All the deleted shares caused by this delete
846
		$deletedShares[] = $share;
847
848
		//Format hook info
849
		$formattedDeletedShares = array_map(function($share) use ($formatHookParams) {
850
			return $formatHookParams($share);
851
		}, $deletedShares);
852
853
		$hookParams['deletedShares'] = $formattedDeletedShares;
854
855
		// Emit post hook
856
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
857
	}
858
859
860
	/**
861
	 * Unshare a file as the recipient.
862
	 * This can be different from a regular delete for example when one of
863
	 * the users in a groups deletes that share. But the provider should
864
	 * handle this.
865
	 *
866
	 * @param \OCP\Share\IShare $share
867
	 * @param string $recipientId
868
	 */
869
	public function deleteFromSelf(\OCP\Share\IShare $share, $recipientId) {
870
		list($providerId, ) = $this->splitFullId($share->getFullId());
871
		$provider = $this->factory->getProvider($providerId);
872
873
		$provider->deleteFromSelf($share, $recipientId);
874
	}
875
876
	/**
877
	 * @inheritdoc
878
	 */
879
	public function moveShare(\OCP\Share\IShare $share, $recipientId) {
880
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
881
			throw new \InvalidArgumentException('Can\'t change target of link share');
882
		}
883
884
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && $share->getSharedWith() !== $recipientId) {
885
			throw new \InvalidArgumentException('Invalid recipient');
886
		}
887
888
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
889
			$sharedWith = $this->groupManager->get($share->getSharedWith());
890
			$recipient = $this->userManager->get($recipientId);
891
			if (!$sharedWith->inGroup($recipient)) {
0 ignored issues
show
Bug introduced by
It seems like $recipient defined by $this->userManager->get($recipientId) on line 890 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...
892
				throw new \InvalidArgumentException('Invalid recipient');
893
			}
894
		}
895
896
		list($providerId, ) = $this->splitFullId($share->getFullId());
897
		$provider = $this->factory->getProvider($providerId);
898
899
		$provider->move($share, $recipientId);
900
	}
901
902
	public function getSharesInFolder($userId, Folder $node, $reshares = false) {
903
		$providers = $this->factory->getAllProviders();
904
905
		return array_reduce($providers, function($shares, IShareProvider $provider) use ($userId, $node, $reshares) {
906
			$newShares = $provider->getSharesInFolder($userId, $node, $reshares);
907
			foreach ($newShares as $fid => $data) {
908
				if (!isset($shares[$fid])) {
909
					$shares[$fid] = [];
910
				}
911
912
				$shares[$fid] = array_merge($shares[$fid], $data);
913
			}
914
			return $shares;
915
		}, []);
916
	}
917
918
	/**
919
	 * @inheritdoc
920
	 */
921
	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
922
		if ($path !== null &&
923
				!($path instanceof \OCP\Files\File) &&
924
				!($path instanceof \OCP\Files\Folder)) {
925
			throw new \InvalidArgumentException('invalid path');
926
		}
927
928
		try {
929
			$provider = $this->factory->getProviderForType($shareType);
930
		} catch (ProviderException $e) {
931
			return [];
932
		}
933
934
		$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
935
936
		/*
937
		 * Work around so we don't return expired shares but still follow
938
		 * proper pagination.
939
		 */
940
		if ($shareType === \OCP\Share::SHARE_TYPE_LINK) {
941
			$shares2 = [];
942
			$today = new \DateTime();
943
944
			while(true) {
945
				$added = 0;
946
				foreach ($shares as $share) {
947
					// Check if the share is expired and if so delete it
948
					if ($share->getExpirationDate() !== null &&
949
						$share->getExpirationDate() <= $today
950
					) {
951
						try {
952
							$this->deleteShare($share);
953
						} catch (NotFoundException $e) {
954
							//Ignore since this basically means the share is deleted
955
						}
956
						continue;
957
					}
958
					$added++;
959
					$shares2[] = $share;
960
961
					if (count($shares2) === $limit) {
962
						break;
963
					}
964
				}
965
966
				if (count($shares2) === $limit) {
967
					break;
968
				}
969
970
				// If there was no limit on the select we are done
971
				if ($limit === -1) {
972
					break;
973
				}
974
975
				$offset += $added;
976
977
				// Fetch again $limit shares
978
				$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
979
980
				// No more shares means we are done
981
				if (empty($shares)) {
982
					break;
983
				}
984
			}
985
986
			$shares = $shares2;
987
		}
988
989
		return $shares;
990
	}
991
992
	/**
993
	 * @inheritdoc
994
	 */
995
	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
996
		try {
997
			$provider = $this->factory->getProviderForType($shareType);
998
		} catch (ProviderException $e) {
999
			return [];
1000
		}
1001
1002
		return $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1003
	}
1004
1005
	/**
1006
	 * @inheritdoc
1007
	 */
1008
	public function getShareById($id, $recipient = null) {
1009
		if ($id === null) {
1010
			throw new ShareNotFound();
1011
		}
1012
1013
		list($providerId, $id) = $this->splitFullId($id);
1014
1015
		try {
1016
			$provider = $this->factory->getProvider($providerId);
1017
		} catch (ProviderException $e) {
1018
			throw new ShareNotFound();
1019
		}
1020
1021
		$share = $provider->getShareById($id, $recipient);
1022
1023
		// Validate link shares expiration date
1024
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1025
			$share->getExpirationDate() !== null &&
1026
			$share->getExpirationDate() <= new \DateTime()) {
1027
			$this->deleteShare($share);
1028
			throw new ShareNotFound();
1029
		}
1030
1031
		return $share;
1032
	}
1033
1034
	/**
1035
	 * Get all the shares for a given path
1036
	 *
1037
	 * @param \OCP\Files\Node $path
1038
	 * @param int $page
1039
	 * @param int $perPage
1040
	 *
1041
	 * @return Share[]
1042
	 */
1043
	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...
1044
		return [];
1045
	}
1046
1047
	/**
1048
	 * Get the share by token possible with password
1049
	 *
1050
	 * @param string $token
1051
	 * @return Share
1052
	 *
1053
	 * @throws ShareNotFound
1054
	 */
1055
	public function getShareByToken($token) {
1056
		$share = null;
1057
		try {
1058
			$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_LINK);
1059
			$share = $provider->getShareByToken($token);
1060
		} catch (ProviderException $e) {
1061
		} catch (ShareNotFound $e) {
1062
		}
1063
1064
1065
		// If it is not a link share try to fetch a federated share by token
1066 View Code Duplication
		if ($share === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1067
			try {
1068
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_REMOTE);
1069
				$share = $provider->getShareByToken($token);
1070
			} catch (ProviderException $e) {
1071
			} catch (ShareNotFound $e) {
1072
			}
1073
		}
1074
1075
		// If it is not a link share try to fetch a federated share by token
1076 View Code Duplication
		if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1077
			try {
1078
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_EMAIL);
1079
				$share = $provider->getShareByToken($token);
1080
			} catch (ProviderException $e) {
1081
			} catch (ShareNotFound $e) {
1082
			}
1083
		}
1084
1085
		if ($share === null) {
1086
			throw new ShareNotFound();
1087
		}
1088
1089
		if ($share->getExpirationDate() !== null &&
1090
			$share->getExpirationDate() <= new \DateTime()) {
1091
			$this->deleteShare($share);
1092
			throw new ShareNotFound();
1093
		}
1094
1095
		/*
1096
		 * Reduce the permissions for link shares if public upload is not enabled
1097
		 */
1098
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1099
			!$this->shareApiLinkAllowPublicUpload()) {
1100
			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1101
		}
1102
1103
		return $share;
1104
	}
1105
1106
	/**
1107
	 * Verify the password of a public share
1108
	 *
1109
	 * @param \OCP\Share\IShare $share
1110
	 * @param string $password
1111
	 * @return bool
1112
	 */
1113
	public function checkPassword(\OCP\Share\IShare $share, $password) {
1114
		if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK) {
1115
			//TODO maybe exception?
1116
			return false;
1117
		}
1118
1119
		if ($password === null || $share->getPassword() === null) {
1120
			return false;
1121
		}
1122
1123
		$newHash = '';
1124
		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1125
			return false;
1126
		}
1127
1128
		if (!empty($newHash)) {
1129
			$share->setPassword($newHash);
1130
			$provider = $this->factory->getProviderForType($share->getShareType());
1131
			$provider->update($share);
1132
		}
1133
1134
		return true;
1135
	}
1136
1137
	/**
1138
	 * @inheritdoc
1139
	 */
1140
	public function userDeleted($uid) {
1141
		$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];
1142
1143
		foreach ($types as $type) {
1144
			try {
1145
				$provider = $this->factory->getProviderForType($type);
1146
			} catch (ProviderException $e) {
1147
				continue;
1148
			}
1149
			$provider->userDeleted($uid, $type);
1150
		}
1151
	}
1152
1153
	/**
1154
	 * @inheritdoc
1155
	 */
1156
	public function groupDeleted($gid) {
1157
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1158
		$provider->groupDeleted($gid);
1159
	}
1160
1161
	/**
1162
	 * @inheritdoc
1163
	 */
1164
	public function userDeletedFromGroup($uid, $gid) {
1165
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1166
		$provider->userDeletedFromGroup($uid, $gid);
1167
	}
1168
1169
	/**
1170
	 * Get access list to a path. This means
1171
	 * all the users and groups that can access a given path.
1172
	 *
1173
	 * Consider:
1174
	 * -root
1175
	 * |-folder1
1176
	 *  |-folder2
1177
	 *   |-fileA
1178
	 *
1179
	 * fileA is shared with user1
1180
	 * folder2 is shared with group2
1181
	 * folder1 is shared with user2
1182
	 *
1183
	 * Then the access list will to '/folder1/folder2/fileA' is:
1184
	 * [
1185
	 * 	'users' => ['user1', 'user2'],
1186
	 *  'groups' => ['group2']
1187
	 * ]
1188
	 *
1189
	 * This is required for encryption
1190
	 *
1191
	 * @param \OCP\Files\Node $path
1192
	 */
1193
	public function getAccessList(\OCP\Files\Node $path) {
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...
1194
	}
1195
1196
	/**
1197
	 * Create a new share
1198
	 * @return \OCP\Share\IShare;
0 ignored issues
show
Documentation introduced by
The doc-type \OCP\Share\IShare; could not be parsed: Expected "|" or "end of type", but got ";" at position 17. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1199
	 */
1200
	public function newShare() {
1201
		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1202
	}
1203
1204
	/**
1205
	 * Is the share API enabled
1206
	 *
1207
	 * @return bool
1208
	 */
1209
	public function shareApiEnabled() {
1210
		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1211
	}
1212
1213
	/**
1214
	 * Is public link sharing enabled
1215
	 *
1216
	 * @return bool
1217
	 */
1218
	public function shareApiAllowLinks() {
1219
		return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
1220
	}
1221
1222
	/**
1223
	 * Is password on public link requires
1224
	 *
1225
	 * @return bool
1226
	 */
1227
	public function shareApiLinkEnforcePassword() {
1228
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1229
	}
1230
1231
	/**
1232
	 * Is default expire date enabled
1233
	 *
1234
	 * @return bool
1235
	 */
1236
	public function shareApiLinkDefaultExpireDate() {
1237
		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1238
	}
1239
1240
	/**
1241
	 * Is default expire date enforced
1242
	 *`
1243
	 * @return bool
1244
	 */
1245
	public function shareApiLinkDefaultExpireDateEnforced() {
1246
		return $this->shareApiLinkDefaultExpireDate() &&
1247
			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1248
	}
1249
1250
	/**
1251
	 * Number of default expire days
1252
	 *shareApiLinkAllowPublicUpload
1253
	 * @return int
1254
	 */
1255
	public function shareApiLinkDefaultExpireDays() {
1256
		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1257
	}
1258
1259
	/**
1260
	 * Allow public upload on link shares
1261
	 *
1262
	 * @return bool
1263
	 */
1264
	public function shareApiLinkAllowPublicUpload() {
1265
		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1266
	}
1267
1268
	/**
1269
	 * check if user can only share with group members
1270
	 * @return bool
1271
	 */
1272
	public function shareWithGroupMembersOnly() {
1273
		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1274
	}
1275
1276
	/**
1277
	 * Check if users can share with groups
1278
	 * @return bool
1279
	 */
1280
	public function allowGroupSharing() {
1281
		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1282
	}
1283
1284
	/**
1285
	 * Copied from \OC_Util::isSharingDisabledForUser
1286
	 *
1287
	 * TODO: Deprecate fuction from OC_Util
1288
	 *
1289
	 * @param string $userId
1290
	 * @return bool
1291
	 */
1292
	public function sharingDisabledForUser($userId) {
1293
		if ($userId === null) {
1294
			return false;
1295
		}
1296
1297
		if (isset($this->sharingDisabledForUsersCache[$userId])) {
1298
			return $this->sharingDisabledForUsersCache[$userId];
1299
		}
1300
1301
		if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
1302
			$groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1303
			$excludedGroups = json_decode($groupsList);
1304 View Code Duplication
			if (is_null($excludedGroups)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1305
				$excludedGroups = explode(',', $groupsList);
1306
				$newValue = json_encode($excludedGroups);
1307
				$this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
1308
			}
1309
			$user = $this->userManager->get($userId);
1310
			$usersGroups = $this->groupManager->getUserGroupIds($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($userId) on line 1309 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...
1311 View Code Duplication
			if (!empty($usersGroups)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1312
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
1313
				// if the user is only in groups which are disabled for sharing then
1314
				// sharing is also disabled for the user
1315
				if (empty($remainingGroups)) {
1316
					$this->sharingDisabledForUsersCache[$userId] = true;
1317
					return true;
1318
				}
1319
			}
1320
		}
1321
1322
		$this->sharingDisabledForUsersCache[$userId] = false;
1323
		return false;
1324
	}
1325
1326
	/**
1327
	 * @inheritdoc
1328
	 */
1329
	public function outgoingServer2ServerSharesAllowed() {
1330
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1331
	}
1332
1333
	/**
1334
	 * @inheritdoc
1335
	 */
1336
	public function shareProviderExists($shareType) {
1337
		try {
1338
			$this->factory->getProviderForType($shareType);
1339
		} catch (ProviderException $e) {
1340
			return false;
1341
		}
1342
1343
		return true;
1344
	}
1345
1346
}
1347