Completed
Pull Request — master (#28401)
by Pauli
12:29
created

Manager::formatUnshareHookParams()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 20
nc 8
nop 1
dl 0
loc 25
rs 8.439
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Björn Schießle <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Roeland Jago Douma <[email protected]>
7
 * @author Thomas Müller <[email protected]>
8
 * @author Vincent Petry <[email protected]>
9
 *
10
 * @copyright Copyright (c) 2017, ownCloud GmbH
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
27
namespace OC\Share20;
28
29
use OC\Cache\CappedMemoryCache;
30
use OC\Files\Mount\MoveableMount;
31
use OCP\Files\File;
32
use OCP\Files\Folder;
33
use OCP\Files\IRootFolder;
34
use OCP\Files\Mount\IMountManager;
35
use OCP\Files\NotFoundException;
36
use OCP\IConfig;
37
use OCP\IGroupManager;
38
use OCP\IL10N;
39
use OCP\ILogger;
40
use OCP\IUserManager;
41
use OCP\Security\IHasher;
42
use OCP\Security\ISecureRandom;
43
use OCP\Share\Exceptions\GenericShareException;
44
use OCP\Share\Exceptions\ShareNotFound;
45
use OCP\Share\IManager;
46
use OCP\Share\IProviderFactory;
47
use Symfony\Component\EventDispatcher\GenericEvent;
48
49
/**
50
 * This class is the communication hub for all sharing related operations.
51
 */
52
class Manager implements IManager {
53
54
	/** @var IProviderFactory */
55
	private $factory;
56
	/** @var ILogger */
57
	private $logger;
58
	/** @var IConfig */
59
	private $config;
60
	/** @var ISecureRandom */
61
	private $secureRandom;
62
	/** @var IHasher */
63
	private $hasher;
64
	/** @var IMountManager */
65
	private $mountManager;
66
	/** @var IGroupManager */
67
	private $groupManager;
68
	/** @var IL10N */
69
	private $l;
70
	/** @var IUserManager */
71
	private $userManager;
72
	/** @var IRootFolder */
73
	private $rootFolder;
74
	/** @var CappedMemoryCache */
75
	private $sharingDisabledForUsersCache;
76
77
78
	/**
79
	 * Manager constructor.
80
	 *
81
	 * @param ILogger $logger
82
	 * @param IConfig $config
83
	 * @param ISecureRandom $secureRandom
84
	 * @param IHasher $hasher
85
	 * @param IMountManager $mountManager
86
	 * @param IGroupManager $groupManager
87
	 * @param IL10N $l
88
	 * @param IProviderFactory $factory
89
	 * @param IUserManager $userManager
90
	 * @param IRootFolder $rootFolder
91
	 */
92
	public function __construct(
93
			ILogger $logger,
94
			IConfig $config,
95
			ISecureRandom $secureRandom,
96
			IHasher $hasher,
97
			IMountManager $mountManager,
98
			IGroupManager $groupManager,
99
			IL10N $l,
100
			IProviderFactory $factory,
101
			IUserManager $userManager,
102
			IRootFolder $rootFolder
103
	) {
104
		$this->logger = $logger;
105
		$this->config = $config;
106
		$this->secureRandom = $secureRandom;
107
		$this->hasher = $hasher;
108
		$this->mountManager = $mountManager;
109
		$this->groupManager = $groupManager;
110
		$this->l = $l;
111
		$this->factory = $factory;
112
		$this->userManager = $userManager;
113
		$this->rootFolder = $rootFolder;
114
		$this->sharingDisabledForUsersCache = new CappedMemoryCache();
115
	}
116
117
	/**
118
	 * Convert from a full share id to a tuple (providerId, shareId)
119
	 *
120
	 * @param string $id
121
	 * @return string[]
122
	 */
123
	private function splitFullId($id) {
124
		return explode(':', $id, 2);
125
	}
126
127
	/**
128
	 * Verify if a password meets all requirements
129
	 *
130
	 * @param string $password
131
	 * @throws \Exception
132
	 */
133
	protected function verifyPassword($password) {
134
		if ($password === null) {
135
			// No password is set, check if this is allowed.
136
			if ($this->shareApiLinkEnforcePassword()) {
137
				throw new \InvalidArgumentException('Passwords are enforced for link shares');
138
			}
139
140
			return;
141
		}
142
143
		// Let others verify the password
144
		$accepted = true;
145
		$message = '';
146
		\OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
147
				'password' => $password,
148
				'accepted' => &$accepted,
149
				'message' => &$message
150
		]);
151
152
		if (!$accepted) {
153
			throw new \Exception($message);
154
		}
155
156
		\OC::$server->getEventDispatcher()->dispatch(
157
			'OCP\Share::validatePassword',
158
			new GenericEvent(null, ['password' => $password])
159
		);
160
	}
161
162
	/**
163
	 * Check for generic requirements before creating a share
164
	 *
165
	 * @param \OCP\Share\IShare $share
166
	 * @throws \InvalidArgumentException
167
	 * @throws GenericShareException
168
	 */
169
	protected function generalCreateChecks(\OCP\Share\IShare $share) {
170
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
171
			// We expect a valid user as sharedWith for user shares
172
			if (!$this->userManager->userExists($share->getSharedWith())) {
173
				throw new \InvalidArgumentException('SharedWith is not a valid user');
174
			}
175
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
176
			// We expect a valid group as sharedWith for group shares
177
			if (!$this->groupManager->groupExists($share->getSharedWith())) {
178
				throw new \InvalidArgumentException('SharedWith is not a valid group');
179
			}
180
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
181
			if ($share->getSharedWith() !== null) {
182
				throw new \InvalidArgumentException('SharedWith should be empty');
183
			}
184
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
185
			if ($share->getSharedWith() === null) {
186
				throw new \InvalidArgumentException('SharedWith should not be empty');
187
			}
188
		} else {
189
			// We can't handle other types yet
190
			throw new \InvalidArgumentException('unkown share type');
191
		}
192
193
		// Verify the initiator of the share is set
194
		if ($share->getSharedBy() === null) {
195
			throw new \InvalidArgumentException('SharedBy should be set');
196
		}
197
198
		// Cannot share with yourself
199 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...
200
			$share->getSharedWith() === $share->getSharedBy()) {
201
			throw new \InvalidArgumentException('Can\'t share with yourself');
202
		}
203
204
		// The path should be set
205
		if ($share->getNode() === null) {
206
			throw new \InvalidArgumentException('Path should be set');
207
		}
208
209
		// And it should be a file or a folder
210
		if (!($share->getNode() instanceof \OCP\Files\File) &&
211
				!($share->getNode() instanceof \OCP\Files\Folder)) {
212
			throw new \InvalidArgumentException('Path should be either a file or a folder');
213
		}
214
215
		// And you can't share your rootfolder
216
		if ($this->userManager->userExists($share->getSharedBy())) {
217
			$sharedPath = $this->rootFolder->getUserFolder($share->getSharedBy())->getPath();
218
		} else {
219
			$sharedPath = $this->rootFolder->getUserFolder($share->getShareOwner())->getPath();
220
		}
221
		if ($sharedPath === $share->getNode()->getPath()) {
222
			throw new \InvalidArgumentException('You can\'t share your root folder');
223
		}
224
225
		// Check if we actually have share permissions
226 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...
227
			$message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getPath()]);
228
			throw new GenericShareException($message_t, $message_t, 404);
229
		}
230
231
		// Permissions should be set
232
		if ($share->getPermissions() === null) {
233
			throw new \InvalidArgumentException('A share requires permissions');
234
		}
235
236
		/*
237
		 * Quick fix for #23536
238
		 * Non moveable mount points do not have update and delete permissions
239
		 * while we 'most likely' do have that on the storage.
240
		 */
241
		$permissions = $share->getNode()->getPermissions();
242
		$mount = $share->getNode()->getMountPoint();
243
		if (!($mount instanceof MoveableMount)) {
244
			$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
245
		}
246
247
		// Check that we do not share with more permissions than we have
248 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...
249
			$message_t = $this->l->t('Cannot increase permissions of %s', [$share->getNode()->getPath()]);
250
			throw new GenericShareException($message_t, $message_t, 404);
251
		}
252
253
		if ($share->getNode() instanceof \OCP\Files\File) {
254 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...
255
				$message_t = $this->l->t('Files can\'t be shared with delete permissions');
256
				throw new GenericShareException($message_t);
257
			}
258 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...
259
				$message_t = $this->l->t('Files can\'t be shared with create permissions');
260
				throw new GenericShareException($message_t);
261
			}
262
		}
263
	}
264
265
	/**
266
	 * Validate if the expiration date fits the system settings
267
	 *
268
	 * @param \OCP\Share\IShare $share The share to validate the expiration date of
269
	 * @return \OCP\Share\IShare The modified share object
270
	 * @throws GenericShareException
271
	 * @throws \InvalidArgumentException
272
	 * @throws \Exception
273
	 */
274
	protected function validateExpirationDate(\OCP\Share\IShare $share) {
275
276
		$expirationDate = $share->getExpirationDate();
277
278
		if ($expirationDate !== null) {
279
			//Make sure the expiration date is a date
280
			$expirationDate->setTime(0, 0, 0);
281
282
			$date = new \DateTime();
283
			$date->setTime(0, 0, 0);
284 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...
285
				$message = $this->l->t('Expiration date is in the past');
286
				throw new GenericShareException($message, $message, 404);
287
			}
288
		}
289
290
		// If expiredate is empty set a default one if there is a default
291
		$fullId = null;
292
		try {
293
			$fullId = $share->getFullId();
294
		} catch (\UnexpectedValueException $e) {
295
			// This is a new share
296
		}
297
298
		if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
299
			$expirationDate = new \DateTime();
300
			$expirationDate->setTime(0,0,0);
301
			$expirationDate->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
302
		}
303
304
		// If we enforce the expiration date check that is does not exceed
305
		if ($this->shareApiLinkDefaultExpireDateEnforced()) {
306
			if ($expirationDate === null) {
307
				throw new \InvalidArgumentException('Expiration date is enforced');
308
			}
309
310
			$date = new \DateTime();
311
			$date->setTime(0, 0, 0);
312
			$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
313 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...
314
				$message = $this->l->t('Cannot set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
315
				throw new GenericShareException($message, $message, 404);
316
			}
317
		}
318
319
		$accepted = true;
320
		$message = '';
321
		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
322
			'expirationDate' => &$expirationDate,
323
			'accepted' => &$accepted,
324
			'message' => &$message,
325
			'passwordSet' => $share->getPassword() !== null,
326
		]);
327
328
		if (!$accepted) {
329
			throw new \Exception($message);
330
		}
331
332
		$share->setExpirationDate($expirationDate);
333
334
		return $share;
335
	}
336
337
	/**
338
	 * Check for pre share requirements for user shares
339
	 *
340
	 * @param \OCP\Share\IShare $share
341
	 * @throws \Exception
342
	 */
343
	protected function userCreateChecks(\OCP\Share\IShare $share) {
344
		// Check if we can share with group members only
345
		if ($this->shareWithGroupMembersOnly()) {
346
			$sharedBy = $this->userManager->get($share->getSharedBy());
347
			$sharedWith = $this->userManager->get($share->getSharedWith());
348
			// Verify we can share with this user
349
			$groups = array_intersect(
350
					$this->groupManager->getUserGroupIds($sharedBy),
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 346 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...
351
					$this->groupManager->getUserGroupIds($sharedWith)
0 ignored issues
show
Bug introduced by
It seems like $sharedWith defined by $this->userManager->get($share->getSharedWith()) on line 347 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...
352
			);
353
			if (empty($groups)) {
354
				throw new \Exception('Only sharing with group members is allowed');
355
			}
356
		}
357
358
		/*
359
		 * TODO: Could be costly, fix
360
		 *
361
		 * Also this is not what we want in the future.. then we want to squash identical shares.
362
		 */
363
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_USER);
364
		$existingShares = $provider->getSharesByPath($share->getNode());
365
		foreach($existingShares as $existingShare) {
366
			// Ignore if it is the same share
367
			try {
368
				if ($existingShare->getFullId() === $share->getFullId()) {
369
					continue;
370
				}
371
			} catch (\UnexpectedValueException $e) {
372
				//Shares are not identical
373
			}
374
375
			// Identical share already existst
376
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
377
				throw new \Exception('Path already shared with this user');
378
			}
379
380
			// The share is already shared with this user via a group share
381
			if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
382
				$group = $this->groupManager->get($existingShare->getSharedWith());
383
				if (!is_null($group)) {
384
					$user = $this->userManager->get($share->getSharedWith());
385
386
					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 384 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...
387
						throw new \Exception('Path already shared with this user');
388
					}
389
				}
390
			}
391
		}
392
	}
393
394
	/**
395
	 * Check for pre share requirements for group shares
396
	 *
397
	 * @param \OCP\Share\IShare $share
398
	 * @throws \Exception
399
	 */
400
	protected function groupCreateChecks(\OCP\Share\IShare $share) {
401
		// Verify group shares are allowed
402
		if (!$this->allowGroupSharing()) {
403
			throw new \Exception('Group sharing is now allowed');
404
		}
405
406
		// Verify if the user can share with this group
407
		if ($this->shareWithGroupMembersOnly()) {
408
			$sharedBy = $this->userManager->get($share->getSharedBy());
409
			$sharedWith = $this->groupManager->get($share->getSharedWith());
410
			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 408 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...
411
				throw new \Exception('Only sharing within your own groups is allowed');
412
			}
413
		}
414
415
		/*
416
		 * TODO: Could be costly, fix
417
		 *
418
		 * Also this is not what we want in the future.. then we want to squash identical shares.
419
		 */
420
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
421
		$existingShares = $provider->getSharesByPath($share->getNode());
422
		foreach($existingShares as $existingShare) {
423
			try {
424
				if ($existingShare->getFullId() === $share->getFullId()) {
425
					continue;
426
				}
427
			} catch (\UnexpectedValueException $e) {
428
				//It is a new share so just continue
429
			}
430
431
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
432
				throw new \Exception('Path already shared with this group');
433
			}
434
		}
435
	}
436
437
	/**
438
	 * Check for pre share requirements for link shares
439
	 *
440
	 * @param \OCP\Share\IShare $share
441
	 * @throws \Exception
442
	 */
443
	protected function linkCreateChecks(\OCP\Share\IShare $share) {
444
		// Are link shares allowed?
445
		if (!$this->shareApiAllowLinks()) {
446
			throw new \Exception('Link sharing not allowed');
447
		}
448
449
		// Link shares by definition can't have share permissions
450
		if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) {
451
			throw new \InvalidArgumentException('Link shares can\'t have reshare permissions');
452
		}
453
454
		// Check if public upload is allowed
455
		if (!$this->shareApiLinkAllowPublicUpload() &&
456
			($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
457
			throw new \InvalidArgumentException('Public upload not allowed');
458
		}
459
	}
460
461
	/**
462
	 * To make sure we don't get invisible link shares we set the parent
463
	 * of a link if it is a reshare. This is a quick word around
464
	 * until we can properly display multiple link shares in the UI
465
	 *
466
	 * See: https://github.com/owncloud/core/issues/22295
467
	 *
468
	 * FIXME: Remove once multiple link shares can be properly displayed
469
	 *
470
	 * @param \OCP\Share\IShare $share
471
	 */
472
	protected function setLinkParent(\OCP\Share\IShare $share) {
473
474
		// No sense in checking if the method is not there.
475
		if (method_exists($share, 'setParent')) {
476
			$storage = $share->getNode()->getStorage();
477
			if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
478
				$share->setParent($storage->getShareId());
479
			}
480
		};
481
	}
482
483
	/**
484
	 * @param File|Folder $path
485
	 */
486
	protected function pathCreateChecks($path) {
487
		// Make sure that we do not share a path that contains a shared mountpoint
488
		if ($path instanceof \OCP\Files\Folder) {
489
			$mounts = $this->mountManager->findIn($path->getPath());
490
			foreach($mounts as $mount) {
491
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
492
					throw new \InvalidArgumentException('Path contains files shared with you');
493
				}
494
			}
495
		}
496
	}
497
498
	/**
499
	 * Check if the user that is sharing can actually share
500
	 *
501
	 * @param \OCP\Share\IShare $share
502
	 * @throws \Exception
503
	 */
504
	protected function canShare(\OCP\Share\IShare $share) {
505
		if (!$this->shareApiEnabled()) {
506
			throw new \Exception('The share API is disabled');
507
		}
508
509
		if ($this->sharingDisabledForUser($share->getSharedBy())) {
510
			throw new \Exception('You are not allowed to share');
511
		}
512
	}
513
514
	/**
515
	 * Share a path
516
	 *
517
	 * @param \OCP\Share\IShare $share
518
	 * @return Share The share object
519
	 * @throws \Exception
520
	 *
521
	 * TODO: handle link share permissions or check them
522
	 */
523
	public function createShare(\OCP\Share\IShare $share) {
524
		$this->canShare($share);
525
526
		$this->generalCreateChecks($share);
527
528
		// Verify if there are any issues with the path
529
		$this->pathCreateChecks($share->getNode());
530
531
		/*
532
		 * On creation of a share the owner is always the owner of the path
533
		 * Except for mounted federated shares.
534
		 */
535
		$storage = $share->getNode()->getStorage();
536
		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
537
			$parent = $share->getNode()->getParent();
538
			while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
539
				$parent = $parent->getParent();
540
			}
541
			$share->setShareOwner($parent->getOwner()->getUID());
542
		} else {
543
			$share->setShareOwner($share->getNode()->getOwner()->getUID());
544
		}
545
546
		//Verify share type
547
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
548
			$this->userCreateChecks($share);
549
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
550
			$this->groupCreateChecks($share);
551
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
552
			$this->linkCreateChecks($share);
553
			$this->setLinkParent($share);
554
555
			/*
556
			 * For now ignore a set token.
557
			 */
558
			$share->setToken(
559
				$this->secureRandom->generate(
560
					\OC\Share\Constants::TOKEN_LENGTH,
561
					\OCP\Security\ISecureRandom::CHAR_LOWER.
562
					\OCP\Security\ISecureRandom::CHAR_UPPER.
563
					\OCP\Security\ISecureRandom::CHAR_DIGITS
564
				)
565
			);
566
567
			//Verify the expiration date
568
			$this->validateExpirationDate($share);
569
570
			//Verify the password
571
			$this->verifyPassword($share->getPassword());
572
573
			// If a password is set. Hash it!
574
			if ($share->getPassword() !== null) {
575
				$share->setPassword($this->hasher->hash($share->getPassword()));
576
			}
577
		}
578
579
		// Cannot share with the owner
580 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...
581
			$share->getSharedWith() === $share->getShareOwner()) {
582
			throw new \InvalidArgumentException('Can\'t share with the share owner');
583
		}
584
585
		// Generate the target
586
		$target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName();
587
		$target = \OC\Files\Filesystem::normalizePath($target);
588
		$share->setTarget($target);
589
590
		// Pre share hook
591
		$run = true;
592
		$error = '';
593
		$preHookData = [
594
			'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
595
			'itemSource' => $share->getNode()->getId(),
596
			'shareType' => $share->getShareType(),
597
			'uidOwner' => $share->getSharedBy(),
598
			'permissions' => $share->getPermissions(),
599
			'fileSource' => $share->getNode()->getId(),
600
			'expiration' => $share->getExpirationDate(),
601
			'token' => $share->getToken(),
602
			'itemTarget' => $share->getTarget(),
603
			'shareWith' => $share->getSharedWith(),
604
			'run' => &$run,
605
			'error' => &$error,
606
		];
607
		\OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
608
609
		if ($run === false) {
610
			throw new \Exception($error);
611
		}
612
613
		$provider = $this->factory->getProviderForType($share->getShareType());
614
		$share = $provider->create($share);
615
616
		// Post share hook
617
		$postHookData = [
618
			'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
619
			'itemSource' => $share->getNode()->getId(),
620
			'shareType' => $share->getShareType(),
621
			'uidOwner' => $share->getSharedBy(),
622
			'permissions' => $share->getPermissions(),
623
			'fileSource' => $share->getNode()->getId(),
624
			'expiration' => $share->getExpirationDate(),
625
			'token' => $share->getToken(),
626
			'id' => $share->getId(),
627
			'shareWith' => $share->getSharedWith(),
628
			'itemTarget' => $share->getTarget(),
629
			'fileTarget' => $share->getTarget(),
630
		];
631
632
		\OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
633
634
		return $share;
635
	}
636
637
	/**
638
	 * Update a share
639
	 *
640
	 * @param \OCP\Share\IShare $share
641
	 * @return \OCP\Share\IShare The share object
642
	 * @throws \InvalidArgumentException
643
	 */
644
	public function updateShare(\OCP\Share\IShare $share) {
645
		$expirationDateUpdated = false;
646
647
		$this->canShare($share);
648
649
		try {
650
			$originalShare = $this->getShareById($share->getFullId());
651
		} catch (\UnexpectedValueException $e) {
652
			throw new \InvalidArgumentException('Share does not have a full id');
653
		}
654
655
		// We can't change the share type!
656
		if ($share->getShareType() !== $originalShare->getShareType()) {
657
			throw new \InvalidArgumentException('Can\'t change share type');
658
		}
659
660
		// We can only change the recipient on user shares
661
		if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
662
		    $share->getShareType() !== \OCP\Share::SHARE_TYPE_USER) {
663
			throw new \InvalidArgumentException('Can only update recipient on user shares');
664
		}
665
666
		// Cannot share with the owner
667 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...
668
			$share->getSharedWith() === $share->getShareOwner()) {
669
			throw new \InvalidArgumentException('Can\'t share with the share owner');
670
		}
671
672
		$this->generalCreateChecks($share);
673
674
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
675
			$this->userCreateChecks($share);
676
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
677
			$this->groupCreateChecks($share);
678
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
679
			$this->linkCreateChecks($share);
680
681
			// Password updated.
682
			if ($share->getPassword() !== $originalShare->getPassword()) {
683
				//Verify the password
684
				$this->verifyPassword($share->getPassword());
685
686
				// If a password is set. Hash it!
687
				if ($share->getPassword() !== null) {
688
					$share->setPassword($this->hasher->hash($share->getPassword()));
689
				}
690
			}
691
692
			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
693
				//Verify the expiration date
694
				$this->validateExpirationDate($share);
695
				$expirationDateUpdated = true;
696
			}
697
		}
698
699
		$this->pathCreateChecks($share->getNode());
700
701
		// Now update the share!
702
		$provider = $this->factory->getProviderForType($share->getShareType());
703
		$share = $provider->update($share);
704
705
		if ($expirationDateUpdated === true) {
706
			\OC_Hook::emit('OCP\Share', 'post_set_expiration_date', [
707
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
708
				'itemSource' => $share->getNode()->getId(),
709
				'date' => $share->getExpirationDate(),
710
				'uidOwner' => $share->getSharedBy(),
711
			]);
712
		}
713
714
		if ($share->getPassword() !== $originalShare->getPassword()) {
715
			\OC_Hook::emit('OCP\Share', 'post_update_password', [
716
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
717
				'itemSource' => $share->getNode()->getId(),
718
				'uidOwner' => $share->getSharedBy(),
719
				'token' => $share->getToken(),
720
				'disabled' => is_null($share->getPassword()),
721
			]);
722
		}
723
724
		if ($share->getPermissions() !== $originalShare->getPermissions()) {
725
			if ($this->userManager->userExists($share->getShareOwner())) {
726
				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
727
			} else {
728
				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
729
			}
730
			\OC_Hook::emit('OCP\Share', 'post_update_permissions', [
731
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
732
				'itemSource' => $share->getNode()->getId(),
733
				'shareType' => $share->getShareType(),
734
				'shareWith' => $share->getSharedWith(),
735
				'uidOwner' => $share->getSharedBy(),
736
				'permissions' => $share->getPermissions(),
737
				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
738
			]);
739
		}
740
741
		return $share;
742
	}
743
744
	/**
745
	 * Delete all the children of this share
746
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
747
	 *
748
	 * @param \OCP\Share\IShare $share
749
	 * @return \OCP\Share\IShare[] List of deleted shares
750
	 */
751
	protected function deleteChildren(\OCP\Share\IShare $share) {
752
		$deletedShares = [];
753
754
		$provider = $this->factory->getProviderForType($share->getShareType());
755
756
		foreach ($provider->getChildren($share) as $child) {
757
			$deletedChildren = $this->deleteChildren($child);
758
			$deletedShares = array_merge($deletedShares, $deletedChildren);
759
760
			$provider->delete($child);
761
			$deletedShares[] = $child;
762
		}
763
764
		return $deletedShares;
765
	}
766
767
	protected static function formatUnshareHookParams(\OCP\Share\IShare $share) {
768
		// Prepare hook
769
		$shareType = $share->getShareType();
770
		$sharedWith = '';
771
		if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
772
			$sharedWith = $share->getSharedWith();
773
		} else if ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
774
			$sharedWith = $share->getSharedWith();
775
		} else if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) {
776
			$sharedWith = $share->getSharedWith();
777
		}
778
779
		$hookParams = [
780
			'id'         => $share->getId(),
781
			'itemType'   => $share->getNodeType(),
782
			'itemSource' => $share->getNodeId(),
783
			'shareType'  => $shareType,
784
			'shareWith'  => $sharedWith,
785
			'itemparent' => method_exists($share, 'getParent') ? $share->getParent() : '',
786
			'uidOwner'   => $share->getSharedBy(),
787
			'fileSource' => $share->getNodeId(),
788
			'fileTarget' => $share->getTarget()
789
		];
790
		return $hookParams;
791
	}
792
793
	/**
794
	 * Delete a share
795
	 *
796
	 * @param \OCP\Share\IShare $share
797
	 * @throws ShareNotFound
798
	 * @throws \InvalidArgumentException
799
	 */
800
	public function deleteShare(\OCP\Share\IShare $share) {
801
802
		try {
803
			$share->getFullId();
804
		} catch (\UnexpectedValueException $e) {
805
			throw new \InvalidArgumentException('Share does not have a full id');
806
		}
807
808
		$hookParams = self::formatUnshareHookParams($share);
809
810
		// Emit pre-hook
811
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
812
813
		// Get all children and delete them as well
814
		$deletedShares = $this->deleteChildren($share);
815
816
		// Do the actual delete
817
		$provider = $this->factory->getProviderForType($share->getShareType());
818
		$provider->delete($share);
819
820
		// All the deleted shares caused by this delete
821
		$deletedShares[] = $share;
822
823
		//Format hook info
824
		$formattedDeletedShares = array_map('self::formatUnshareHookParams', $deletedShares);
825
826
		$hookParams['deletedShares'] = $formattedDeletedShares;
827
828
		// Emit post hook
829
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
830
	}
831
832
833
	/**
834
	 * Unshare a file as the recipient.
835
	 * This can be different from a regular delete for example when one of
836
	 * the users in a groups deletes that share. But the provider should
837
	 * handle this.
838
	 *
839
	 * @param \OCP\Share\IShare $share
840
	 * @param string $recipientId
841
	 */
842
	public function deleteFromSelf(\OCP\Share\IShare $share, $recipientId) {
843
		list($providerId, ) = $this->splitFullId($share->getFullId());
844
		$provider = $this->factory->getProvider($providerId);
845
846
		$provider->deleteFromSelf($share, $recipientId);
847
848
		// Emit post hook. The parameter data structure is slightly different
849
		// from the post_unshare hook to maintain backward compatibility with
850
		// Share 1.0: the array contains all the key-value pairs from the old
851
		// library plus some new ones.
852
		$hookParams = self::formatUnshareHookParams($share);
853
		$hookParams['itemTarget'] = $hookParams['fileTarget'];
854
		$hookParams['unsharedItems'] = [$hookParams];
855
		\OC_Hook::emit('OCP\Share', 'post_unshareFromSelf', $hookParams);
856
	}
857
858
	/**
859
	 * @inheritdoc
860
	 */
861
	public function moveShare(\OCP\Share\IShare $share, $recipientId) {
862
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
863
			throw new \InvalidArgumentException('Can\'t change target of link share');
864
		}
865
866
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && $share->getSharedWith() !== $recipientId) {
867
			throw new \InvalidArgumentException('Invalid recipient');
868
		}
869
870
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
871
			$sharedWith = $this->groupManager->get($share->getSharedWith());
872
			if (is_null($sharedWith)) {
873
				throw new \InvalidArgumentException('Group "' . $share->getSharedWith() . '" does not exist');
874
			}
875
			$recipient = $this->userManager->get($recipientId);
876
			if (!$sharedWith->inGroup($recipient)) {
0 ignored issues
show
Bug introduced by
It seems like $recipient defined by $this->userManager->get($recipientId) on line 875 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...
877
				throw new \InvalidArgumentException('Invalid recipient');
878
			}
879
		}
880
881
		list($providerId, ) = $this->splitFullId($share->getFullId());
882
		$provider = $this->factory->getProvider($providerId);
883
884
		$provider->move($share, $recipientId);
885
	}
886
887
888
	/**
889
	 * @inheritdoc
890
	 */
891
	public function getAllSharesBy($userId, $shareTypes, $nodeIDs, $reshares = false) {
892
		// This function requires at least 1 node (parent folder)
893
		if (empty($nodeIDs)) {
894
			throw new \InvalidArgumentException('Array of nodeIDs empty');
895
		}
896
		// This will ensure that if there are multiple share providers for the same share type, we will execute it in batches
897
		$shares = array();
898
		$providerIdMap = array();
899
		foreach ($shareTypes as $shareType) {
900
			// Get provider and its ID, at this point provider is cached at IProviderFactory instance
901
			$provider = $this->factory->getProviderForType($shareType);
902
			$providerId = $provider->identifier();
903
904
			// Create a key -> multi value map
905
			if (!isset($providerIdMap[$providerId])) {
906
				$providerIdMap[$providerId] = array();
907
			}
908
			array_push($providerIdMap[$providerId], $shareType);
909
		}
910
911
		$today = new \DateTime();
912
		foreach ($providerIdMap as $providerId => $shareTypeArray) {
913
			// Get provider from cache
914
			$provider = $this->factory->getProvider($providerId);
915
916
			$queriedShares = $provider->getAllSharesBy($userId, $shareTypeArray, $nodeIDs, $reshares);
0 ignored issues
show
Documentation introduced by
$nodeIDs is of type array<integer,integer>, but the function expects a array<integer,object<OCP\Files\Node>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
917
			foreach ($queriedShares as $queriedShare){
918
				if ($queriedShare->getShareType() === \OCP\Share::SHARE_TYPE_LINK && $queriedShare->getExpirationDate() !== null &&
919
					$queriedShare->getExpirationDate() <= $today
920
				) {
921
					try {
922
						$this->deleteShare($queriedShare);
923
					} catch (NotFoundException $e) {
924
						//Ignore since this basically means the share is deleted
925
					}
926
					continue;
927
				}
928
				array_push($shares, $queriedShare);
929
			}
930
		}
931
932
		return $shares;
933
	}
934
935
	/**
936
	 * @inheritdoc
937
	 */
938
	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
939
		if ($path !== null &&
940
				!($path instanceof \OCP\Files\File) &&
941
				!($path instanceof \OCP\Files\Folder)) {
942
			throw new \InvalidArgumentException('invalid path');
943
		}
944
945
		$provider = $this->factory->getProviderForType($shareType);
946
947
		$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
948
949
		/*
950
		 * Work around so we don't return expired shares but still follow
951
		 * proper pagination.
952
		 */
953
		if ($shareType === \OCP\Share::SHARE_TYPE_LINK) {
954
			$shares2 = [];
955
			$today = new \DateTime();
956
957
			while(true) {
958
				$added = 0;
959
				foreach ($shares as $share) {
960
					// Check if the share is expired and if so delete it
961
					if ($share->getExpirationDate() !== null &&
962
						$share->getExpirationDate() <= $today
963
					) {
964
						try {
965
							$this->deleteShare($share);
966
						} catch (NotFoundException $e) {
967
							//Ignore since this basically means the share is deleted
968
						}
969
						continue;
970
					}
971
					$added++;
972
					$shares2[] = $share;
973
974
					if (count($shares2) === $limit) {
975
						break;
976
					}
977
				}
978
979
				if (count($shares2) === $limit) {
980
					break;
981
				}
982
983
				// If there was no limit on the select we are done
984
				if ($limit === -1) {
985
					break;
986
				}
987
988
				$offset += $added;
989
990
				// Fetch again $limit shares
991
				$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
992
993
				// No more shares means we are done
994
				if (empty($shares)) {
995
					break;
996
				}
997
			}
998
999
			$shares = $shares2;
1000
		}
1001
1002
		return $shares;
1003
	}
1004
1005
	/**
1006
	 * @inheritdoc
1007
	 */
1008
	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1009
		$provider = $this->factory->getProviderForType($shareType);
1010
1011
		return $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1012
	}
1013
1014
	/**
1015
	 * @inheritdoc
1016
	 */
1017
	public function getShareById($id, $recipient = null) {
1018
		if ($id === null) {
1019
			throw new ShareNotFound();
1020
		}
1021
1022
		list($providerId, $id) = $this->splitFullId($id);
1023
		$provider = $this->factory->getProvider($providerId);
1024
1025
		$share = $provider->getShareById($id, $recipient);
1026
1027
		// Validate link shares expiration date
1028
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1029
			$share->getExpirationDate() !== null &&
1030
			$share->getExpirationDate() <= new \DateTime()) {
1031
			$this->deleteShare($share);
1032
			throw new ShareNotFound();
1033
		}
1034
1035
		return $share;
1036
	}
1037
1038
	/**
1039
	 * Get all the shares for a given path
1040
	 *
1041
	 * @param \OCP\Files\Node $path
1042
	 * @param int $page
1043
	 * @param int $perPage
1044
	 *
1045
	 * @return Share[]
1046
	 */
1047
	public function getSharesByPath(\OCP\Files\Node $path, $page=0, $perPage=50) {
0 ignored issues
show
Unused Code introduced by
The parameter $page is not used and could be removed.

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...
1048
		$types = [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP];
1049
		$providers = [];
1050
		$results = [];
1051
1052
		foreach ($types as $type) {
1053
			$provider = $this->factory->getProviderForType($type);
1054
			// store this way to deduplicate entries by id
1055
			$providers[$provider->identifier()] = $provider;
1056
		}
1057
1058
		foreach ($providers as $provider) {
1059
			$results = array_merge($results, $provider->getSharesByPath($path));
1060
		}
1061
1062
		return $results;
1063
	}
1064
1065
	/**
1066
	 * Get the share by token possible with password
1067
	 *
1068
	 * @param string $token
1069
	 * @return Share
1070
	 *
1071
	 * @throws ShareNotFound
1072
	 */
1073
	public function getShareByToken($token) {
1074
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_LINK);
1075
1076
		try {
1077
			$share = $provider->getShareByToken($token);
1078
		} catch (ShareNotFound $e) {
1079
			$share = null;
1080
		}
1081
1082
		// If it is not a link share try to fetch a federated share by token
1083
		if ($share === null) {
1084
			$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_REMOTE);
1085
			$share = $provider->getShareByToken($token);
1086
		}
1087
1088
		if ($share->getExpirationDate() !== null &&
1089
			$share->getExpirationDate() <= new \DateTime()) {
1090
			$this->deleteShare($share);
1091
			throw new ShareNotFound();
1092
		}
1093
1094
		/*
1095
		 * Reduce the permissions for link shares if public upload is not enabled
1096
		 */
1097
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1098
			!$this->shareApiLinkAllowPublicUpload()) {
1099
			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1100
		}
1101
1102
		return $share;
1103
	}
1104
1105
	/**
1106
	 * Verify the password of a public share
1107
	 *
1108
	 * @param \OCP\Share\IShare $share
1109
	 * @param string $password
1110
	 * @return bool
1111
	 */
1112
	public function checkPassword(\OCP\Share\IShare $share, $password) {
1113
		if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK) {
1114
			//TODO maybe exception?
1115
			return false;
1116
		}
1117
1118
		if ($password === null || $share->getPassword() === null) {
1119
			return false;
1120
		}
1121
1122
		$newHash = '';
1123
		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1124
			return false;
1125
		}
1126
1127
		if (!empty($newHash)) {
1128
			$share->setPassword($newHash);
1129
			$provider = $this->factory->getProviderForType($share->getShareType());
1130
			$provider->update($share);
1131
		}
1132
1133
		return true;
1134
	}
1135
1136
	/**
1137
	 * @inheritdoc
1138
	 */
1139
	public function userDeleted($uid) {
1140
		$types = [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE];
1141
1142
		foreach ($types as $type) {
1143
			$provider = $this->factory->getProviderForType($type);
1144
			$provider->userDeleted($uid, $type);
1145
		}
1146
	}
1147
1148
	/**
1149
	 * @inheritdoc
1150
	 */
1151
	public function groupDeleted($gid) {
1152
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1153
		$provider->groupDeleted($gid);
1154
	}
1155
1156
	/**
1157
	 * @inheritdoc
1158
	 */
1159
	public function userDeletedFromGroup($uid, $gid) {
1160
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1161
		$provider->userDeletedFromGroup($uid, $gid);
1162
	}
1163
1164
	/**
1165
	 * Get access list to a path. This means
1166
	 * all the users and groups that can access a given path.
1167
	 *
1168
	 * Consider:
1169
	 * -root
1170
	 * |-folder1
1171
	 *  |-folder2
1172
	 *   |-fileA
1173
	 *
1174
	 * fileA is shared with user1
1175
	 * folder2 is shared with group2
1176
	 * folder1 is shared with user2
1177
	 *
1178
	 * Then the access list will to '/folder1/folder2/fileA' is:
1179
	 * [
1180
	 * 	'users' => ['user1', 'user2'],
1181
	 *  'groups' => ['group2']
1182
	 * ]
1183
	 *
1184
	 * This is required for encryption
1185
	 *
1186
	 * @param \OCP\Files\Node $path
1187
	 */
1188
	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...
1189
	}
1190
1191
	/**
1192
	 * Create a new share
1193
	 * @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...
1194
	 */
1195
	public function newShare() {
1196
		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1197
	}
1198
1199
	/**
1200
	 * Is the share API enabled
1201
	 *
1202
	 * @return bool
1203
	 */
1204
	public function shareApiEnabled() {
1205
		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1206
	}
1207
1208
	/**
1209
	 * Is public link sharing enabled
1210
	 *
1211
	 * @return bool
1212
	 */
1213
	public function shareApiAllowLinks() {
1214
		return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
1215
	}
1216
1217
	/**
1218
	 * Is password on public link requires
1219
	 *
1220
	 * @return bool
1221
	 */
1222
	public function shareApiLinkEnforcePassword() {
1223
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1224
	}
1225
1226
	/**
1227
	 * Is default expire date enabled
1228
	 *
1229
	 * @return bool
1230
	 */
1231
	public function shareApiLinkDefaultExpireDate() {
1232
		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1233
	}
1234
1235
	/**
1236
	 * Is default expire date enforced
1237
	 *`
1238
	 * @return bool
1239
	 */
1240
	public function shareApiLinkDefaultExpireDateEnforced() {
1241
		return $this->shareApiLinkDefaultExpireDate() &&
1242
			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1243
	}
1244
1245
	/**
1246
	 * Number of default expire days
1247
	 *shareApiLinkAllowPublicUpload
1248
	 * @return int
1249
	 */
1250
	public function shareApiLinkDefaultExpireDays() {
1251
		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1252
	}
1253
1254
	/**
1255
	 * Allow public upload on link shares
1256
	 *
1257
	 * @return bool
1258
	 */
1259
	public function shareApiLinkAllowPublicUpload() {
1260
		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1261
	}
1262
1263
	/**
1264
	 * check if user can only share with group members
1265
	 * @return bool
1266
	 */
1267
	public function shareWithGroupMembersOnly() {
1268
		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1269
	}
1270
1271
	/**
1272
	 * Check if users can share with groups
1273
	 * @return bool
1274
	 */
1275
	public function allowGroupSharing() {
1276
		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1277
	}
1278
1279
	/**
1280
	 * Copied from \OC_Util::isSharingDisabledForUser
1281
	 *
1282
	 * TODO: Deprecate fuction from OC_Util
1283
	 *
1284
	 * @param string $userId
1285
	 * @return bool
1286
	 */
1287
	public function sharingDisabledForUser($userId) {
1288
		if ($userId === null) {
1289
			return false;
1290
		}
1291
1292
		if (isset($this->sharingDisabledForUsersCache[$userId])) {
1293
			return $this->sharingDisabledForUsersCache[$userId];
1294
		}
1295
1296
		if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
1297
			$groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1298
			$excludedGroups = json_decode($groupsList);
1299 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...
1300
				$excludedGroups = explode(',', $groupsList);
1301
				$newValue = json_encode($excludedGroups);
1302
				$this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
1303
			}
1304
			$user = $this->userManager->get($userId);
1305
			$usersGroups = $this->groupManager->getUserGroupIds($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($userId) on line 1304 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...
1306 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...
1307
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
1308
				// if the user is only in groups which are disabled for sharing then
1309
				// sharing is also disabled for the user
1310
				if (empty($remainingGroups)) {
1311
					$this->sharingDisabledForUsersCache[$userId] = true;
1312
					return true;
1313
				}
1314
			}
1315
		}
1316
1317
		$this->sharingDisabledForUsersCache[$userId] = false;
1318
		return false;
1319
	}
1320
1321
	/**
1322
	 * @inheritdoc
1323
	 */
1324
	public function outgoingServer2ServerSharesAllowed() {
1325
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1326
	}
1327
1328
}
1329