Completed
Push — master ( 0e6e62...37fbc3 )
by
unknown
29:06 queued 28:40
created

Manager::transferShare()   D

Complexity

Conditions 19
Paths 60

Size

Total Lines 82

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
nc 60
nop 5
dl 0
loc 82
rs 4.5166
c 0
b 0
f 0

How to fix   Long Method    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
 * @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) 2018, 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 OC\Files\View;
32
use OCP\Files\File;
33
use OCP\Files\Folder;
34
use OCP\Files\IRootFolder;
35
use OCP\Files\Mount\IMountManager;
36
use OCP\Files\NotFoundException;
37
use OCP\IConfig;
38
use OCP\IDBConnection;
39
use OCP\IGroupManager;
40
use OCP\IL10N;
41
use OCP\ILogger;
42
use OCP\IUserManager;
43
use OCP\Security\IHasher;
44
use OCP\Security\ISecureRandom;
45
use OCP\Share\Exceptions\GenericShareException;
46
use OCP\Share\Exceptions\ShareNotFound;
47
use OCP\Share\Exceptions\TransferSharesException;
48
use OCP\Share\IManager;
49
use OCP\Share\IProviderFactory;
50
use OCP\Share\IShare;
51
use Symfony\Component\EventDispatcher\EventDispatcher;
52
use Symfony\Component\EventDispatcher\GenericEvent;
53
54
/**
55
 * This class is the communication hub for all sharing related operations.
56
 */
57
class Manager implements IManager {
58
59
	/** @var IProviderFactory */
60
	private $factory;
61
	/** @var ILogger */
62
	private $logger;
63
	/** @var IConfig */
64
	private $config;
65
	/** @var ISecureRandom */
66
	private $secureRandom;
67
	/** @var IHasher */
68
	private $hasher;
69
	/** @var IMountManager */
70
	private $mountManager;
71
	/** @var IGroupManager */
72
	private $groupManager;
73
	/** @var IL10N */
74
	private $l;
75
	/** @var IUserManager */
76
	private $userManager;
77
	/** @var IRootFolder */
78
	private $rootFolder;
79
	/** @var CappedMemoryCache */
80
	private $sharingDisabledForUsersCache;
81
	/** @var EventDispatcher  */
82
	private $eventDispatcher;
83
84
	/** @var View */
85
	private $view;
86
	/** @var IDBConnection  */
87
	private $connection;
88
89
	/**
90
	 * Manager constructor.
91
	 *
92
	 * @param ILogger $logger
93
	 * @param IConfig $config
94
	 * @param ISecureRandom $secureRandom
95
	 * @param IHasher $hasher
96
	 * @param IMountManager $mountManager
97
	 * @param IGroupManager $groupManager
98
	 * @param IL10N $l
99
	 * @param IProviderFactory $factory
100
	 * @param IUserManager $userManager
101
	 * @param IRootFolder $rootFolder
102
	 */
103
	public function __construct(
104
			ILogger $logger,
105
			IConfig $config,
106
			ISecureRandom $secureRandom,
107
			IHasher $hasher,
108
			IMountManager $mountManager,
109
			IGroupManager $groupManager,
110
			IL10N $l,
111
			IProviderFactory $factory,
112
			IUserManager $userManager,
113
			IRootFolder $rootFolder,
114
			EventDispatcher $eventDispatcher,
115
			View $view,
116
			IDBConnection $connection
117
	) {
118
		$this->logger = $logger;
119
		$this->config = $config;
120
		$this->secureRandom = $secureRandom;
121
		$this->hasher = $hasher;
122
		$this->mountManager = $mountManager;
123
		$this->groupManager = $groupManager;
124
		$this->l = $l;
125
		$this->factory = $factory;
126
		$this->userManager = $userManager;
127
		$this->rootFolder = $rootFolder;
128
		$this->sharingDisabledForUsersCache = new CappedMemoryCache();
129
		$this->eventDispatcher = $eventDispatcher;
130
		$this->view = $view;
131
		$this->connection = $connection;
132
	}
133
134
	/**
135
	 * @param int[] $shareTypes - ref \OC\Share\Constants[]
136
	 * @return int[] $providerIdMap e.g. { "ocinternal" => { 0, 1 }[2] }[1]
137
	 */
138
	private function shareTypeToProviderMap($shareTypes) {
139
		$providerIdMap = [];
140
		foreach ($shareTypes as $shareType) {
141
			// Get provider and its ID, at this point provider is cached at IProviderFactory instance
142
			$provider = $this->factory->getProviderForType($shareType);
143
			$providerId = $provider->identifier();
144
145
			// Create a key -> multi value map
146
			$providerIdMap[$providerId][] = $shareType;
147
		}
148
		return $providerIdMap;
149
	}
150
151
	/**
152
	 * Convert from a full share id to a tuple (providerId, shareId)
153
	 *
154
	 * @param string $id
155
	 * @return string[]
156
	 */
157
	private function splitFullId($id) {
158
		return \explode(':', $id, 2);
159
	}
160
161
	/**
162
	 * Verify if a password meets all requirements
163
	 *
164
	 * @param string $password
165
	 * @throws \Exception
166
	 */
167
	protected function verifyPassword($password) {
168
		// Let others verify the password
169
		$accepted = true;
170
		$message = '';
171
		\OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
172
				'password' => $password,
173
				'accepted' => &$accepted,
174
				'message' => &$message
175
		]);
176
177
		if (!$accepted) {
178
			throw new \Exception($message);
179
		}
180
181
		$this->eventDispatcher->dispatch(
182
			'OCP\Share::validatePassword',
183
			new GenericEvent(null, ['password' => $password])
184
		);
185
	}
186
187
	/**
188
	 * Check if a password must be enforced if the shared has those permissions
189
	 * @param int $permissions \OCP\Constants::PERMISSION_* ("|" can be use for sets of permissions)
190
	 * @return bool true if the password must be enforced, false otherwise
191
	 */
192
	protected function passwordMustBeEnforced($permissions) {
193
		$roEnforcement = $permissions === \OCP\Constants::PERMISSION_READ && $this->shareApiLinkEnforcePasswordReadOnly();
194
		$woEnforcement = $permissions === \OCP\Constants::PERMISSION_CREATE && $this->shareApiLinkEnforcePasswordWriteOnly();
195
		// use read & write enforcement for the rest of the cases
196
		$rwEnforcement = ($permissions !== \OCP\Constants::PERMISSION_READ && $permissions !== \OCP\Constants::PERMISSION_CREATE) && $this->shareApiLinkEnforcePasswordReadWrite();
197
		if ($roEnforcement || $woEnforcement || $rwEnforcement) {
198
			return true;
199
		} else {
200
			return false;
201
		}
202
	}
203
204
	/**
205
	 * Check for generic requirements before creating a share
206
	 *
207
	 * @param \OCP\Share\IShare $share
208
	 * @throws \InvalidArgumentException
209
	 * @throws GenericShareException
210
	 */
211
	protected function generalCreateChecks(\OCP\Share\IShare $share) {
212
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
213
			// We expect a valid user as sharedWith for user shares
214
			if (!$this->userManager->userExists($share->getSharedWith())) {
215
				throw new \InvalidArgumentException('SharedWith is not a valid user');
216
			}
217
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
218
			// We expect a valid group as sharedWith for group shares
219
			if (!$this->groupManager->groupExists($share->getSharedWith())) {
220
				throw new \InvalidArgumentException('SharedWith is not a valid group');
221
			}
222
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
223
			if ($share->getSharedWith() !== null) {
224
				throw new \InvalidArgumentException('SharedWith should be empty');
225
			}
226
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
227
			if ($share->getSharedWith() === null) {
228
				throw new \InvalidArgumentException('SharedWith should not be empty');
229
			}
230
		} else {
231
			// We can't handle other types yet
232
			throw new \InvalidArgumentException('unkown share type');
233
		}
234
235
		// Verify the initiator of the share is set
236
		if ($share->getSharedBy() === null) {
237
			throw new \InvalidArgumentException('SharedBy should be set');
238
		}
239
240
		// Cannot share with yourself
241 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...
242
			$share->getSharedWith() === $share->getSharedBy()) {
243
			throw new \InvalidArgumentException('Can\'t share with yourself');
244
		}
245
246
		// The path should be set
247
		if ($share->getNode() === null) {
248
			throw new \InvalidArgumentException('Path should be set');
249
		}
250
251
		// And it should be a file or a folder
252
		if (!($share->getNode() instanceof \OCP\Files\File) &&
253
				!($share->getNode() instanceof \OCP\Files\Folder)) {
254
			throw new \InvalidArgumentException('Path should be either a file or a folder');
255
		}
256
257
		// And you can't share your rootfolder
258
		if ($this->userManager->userExists($share->getSharedBy())) {
259
			$sharedPath = $this->rootFolder->getUserFolder($share->getSharedBy())->getPath();
260
		} else {
261
			$sharedPath = $this->rootFolder->getUserFolder($share->getShareOwner())->getPath();
262
		}
263
		if ($sharedPath === $share->getNode()->getPath()) {
264
			throw new \InvalidArgumentException('You can\'t share your root folder');
265
		}
266
267
		// Check if we actually have share permissions
268 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...
269
			$message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getPath()]);
270
			throw new GenericShareException($message_t, $message_t, 404);
271
		}
272
273
		// Permissions should be set
274
		if ($share->getPermissions() === null) {
275
			throw new \InvalidArgumentException('A share requires permissions');
276
		}
277
278
		/*
279
		 * Quick fix for #23536
280
		 * Non moveable mount points do not have update and delete permissions
281
		 * while we 'most likely' do have that on the storage.
282
		 */
283
		$permissions = $share->getNode()->getPermissions();
284
		$mount = $share->getNode()->getMountPoint();
285
		if (!($mount instanceof MoveableMount)) {
286
			$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
287
		}
288
289
		// Check that we do not share with more permissions than we have
290 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...
291
			$message_t = $this->l->t('Cannot increase permissions of %s', [$share->getNode()->getPath()]);
292
			throw new GenericShareException($message_t, $message_t, 404);
293
		}
294
295
		if ($share->getNode() instanceof \OCP\Files\File) {
296 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...
297
				$message_t = $this->l->t('Files can\'t be shared with delete permissions');
298
				throw new GenericShareException($message_t);
299
			}
300 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...
301
				$message_t = $this->l->t('Files can\'t be shared with create permissions');
302
				throw new GenericShareException($message_t);
303
			}
304
		}
305
	}
306
307
	/**
308
	 * Validate if the expiration date fits the system settings
309
	 *
310
	 * @param \OCP\Share\IShare $share The share to validate the expiration date of
311
	 * @return \OCP\Share\IShare The modified share object
312
	 * @throws GenericShareException
313
	 * @throws \InvalidArgumentException
314
	 * @throws \Exception
315
	 */
316
	protected function validateExpirationDate(\OCP\Share\IShare $share) {
317
		$expirationDate = $share->getExpirationDate();
318
319
		if ($expirationDate !== null) {
320
			//Make sure the expiration date is a date
321
			$expirationDate->setTime(0, 0, 0);
322
323
			$date = new \DateTime();
324
			$date->setTime(0, 0, 0);
325 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...
326
				$message = $this->l->t('Expiration date is in the past');
327
				throw new GenericShareException($message, $message, 404);
328
			}
329
		}
330
331
		// If expiredate is empty set a default one if there is a default
332
		$fullId = null;
333
		try {
334
			$fullId = $share->getFullId();
335
		} catch (\UnexpectedValueException $e) {
336
			// This is a new share
337
		}
338
339
		if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
340
			$expirationDate = new \DateTime();
341
			$expirationDate->setTime(0, 0, 0);
342
			$expirationDate->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
343
		}
344
345
		// If we enforce the expiration date check that is does not exceed
346
		if ($this->shareApiLinkDefaultExpireDateEnforced()) {
347
			if ($expirationDate === null) {
348
				throw new \InvalidArgumentException('Expiration date is enforced');
349
			}
350
351
			$date = new \DateTime();
352
			$date->setTime(0, 0, 0);
353
			$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
354 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...
355
				$message = $this->l->t('Cannot set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
356
				throw new GenericShareException($message, $message, 404);
357
			}
358
		}
359
360
		$accepted = true;
361
		$message = '';
362
		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
363
			'expirationDate' => &$expirationDate,
364
			'accepted' => &$accepted,
365
			'message' => &$message,
366
			'passwordSet' => $share->getPassword() !== null,
367
		]);
368
369
		if (!$accepted) {
370
			throw new \Exception($message);
371
		}
372
373
		$share->setExpirationDate($expirationDate);
374
375
		return $share;
376
	}
377
378
	/**
379
	 * Check for pre share requirements for user shares
380
	 *
381
	 * @param \OCP\Share\IShare $share
382
	 * @throws \Exception
383
	 */
384
	protected function userCreateChecks(\OCP\Share\IShare $share) {
385
		// Check if we can share with group members only
386
		if ($this->shareWithGroupMembersOnly()) {
387
			$sharedBy = $this->userManager->get($share->getSharedBy());
388
			$sharedWith = $this->userManager->get($share->getSharedWith());
389
			// Verify we can share with this user
390
			$groups = \array_intersect(
391
					$this->groupManager->getUserGroupIds($sharedBy),
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 387 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...
392
					$this->groupManager->getUserGroupIds($sharedWith)
0 ignored issues
show
Bug introduced by
It seems like $sharedWith defined by $this->userManager->get($share->getSharedWith()) on line 388 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...
393
			);
394
			if (empty($groups)) {
395
				throw new \Exception('Only sharing with group members is allowed');
396
			}
397
		}
398
399
		/*
400
		 * TODO: Could be costly, fix
401
		 *
402
		 * Also this is not what we want in the future.. then we want to squash identical shares.
403
		 */
404
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_USER);
405
		$existingShares = $provider->getSharesByPath($share->getNode());
406
		foreach ($existingShares as $existingShare) {
407
			// Ignore if it is the same share
408
			try {
409
				if ($existingShare->getFullId() === $share->getFullId()) {
410
					continue;
411
				}
412
			} catch (\UnexpectedValueException $e) {
413
				//Shares are not identical
414
			}
415
416
			// Identical share already existst
417
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
418
				throw new \Exception('Path already shared with this user');
419
			}
420
421
			// The share is already shared with this user via a group share
422
			if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
423
				$group = $this->groupManager->get($existingShare->getSharedWith());
424
				if ($group !== null) {
425
					$user = $this->userManager->get($share->getSharedWith());
426
427
					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 425 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...
428
						throw new \Exception('Path already shared with this user');
429
					}
430
				}
431
			}
432
		}
433
	}
434
435
	/**
436
	 * Check for pre share requirements for group shares
437
	 *
438
	 * @param \OCP\Share\IShare $share
439
	 * @throws \Exception
440
	 */
441
	protected function groupCreateChecks(\OCP\Share\IShare $share) {
442
		// Verify group shares are allowed
443
		if (!$this->allowGroupSharing()) {
444
			throw new \Exception('Group sharing is now allowed');
445
		}
446
447
		// Verify if the user can share with this group
448
		if ($this->shareWithMembershipGroupOnly()) {
449
			$sharedBy = $this->userManager->get($share->getSharedBy());
450
			$sharedWith = $this->groupManager->get($share->getSharedWith());
451
			if ($sharedWith === null || !$sharedWith->inGroup($sharedBy)) {
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 449 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...
452
				throw new \Exception('Only sharing within your own groups is allowed');
453
			}
454
		}
455
456
		/*
457
		 * TODO: Could be costly, fix
458
		 *
459
		 * Also this is not what we want in the future.. then we want to squash identical shares.
460
		 */
461
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
462
		$existingShares = $provider->getSharesByPath($share->getNode());
463
		foreach ($existingShares as $existingShare) {
464
			try {
465
				if ($existingShare->getFullId() === $share->getFullId()) {
466
					continue;
467
				}
468
			} catch (\UnexpectedValueException $e) {
469
				//It is a new share so just continue
470
			}
471
472
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
473
				throw new \Exception('Path already shared with this group');
474
			}
475
		}
476
	}
477
478
	/**
479
	 * Check for pre share requirements for link shares
480
	 *
481
	 * @param \OCP\Share\IShare $share
482
	 * @throws \Exception
483
	 */
484
	protected function linkCreateChecks(\OCP\Share\IShare $share) {
485
		// Are link shares allowed?
486
		if (!$this->shareApiAllowLinks()) {
487
			throw new \Exception('Link sharing not allowed');
488
		}
489
490
		// Link shares by definition can't have share permissions
491
		if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) {
492
			throw new \InvalidArgumentException('Link shares can\'t have reshare permissions');
493
		}
494
495
		// Check if public upload is allowed
496
		if (!$this->shareApiLinkAllowPublicUpload() &&
497
			($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
498
			throw new \InvalidArgumentException('Public upload not allowed');
499
		}
500
	}
501
502
	/**
503
	 * To make sure we don't get invisible link shares we set the parent
504
	 * of a link if it is a reshare. This is a quick word around
505
	 * until we can properly display multiple link shares in the UI
506
	 *
507
	 * See: https://github.com/owncloud/core/issues/22295
508
	 *
509
	 * FIXME: Remove once multiple link shares can be properly displayed
510
	 *
511
	 * @param \OCP\Share\IShare $share
512
	 */
513
	protected function setLinkParent(\OCP\Share\IShare $share) {
514
515
		// No sense in checking if the method is not there.
516
		if (\method_exists($share, 'setParent')) {
517
			$storage = $share->getNode()->getStorage();
518
			if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
519
				$share->setParent($storage->getShareId());
520
			}
521
		};
522
	}
523
524
	/**
525
	 * @param File|Folder $path
526
	 */
527
	protected function pathCreateChecks($path) {
528
		// Make sure that we do not share a path that contains a shared mountpoint
529
		if ($path instanceof \OCP\Files\Folder) {
530
			$mounts = $this->mountManager->findIn($path->getPath());
531
			foreach ($mounts as $mount) {
532
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
533
					throw new \InvalidArgumentException('Path contains files shared with you');
534
				}
535
			}
536
		}
537
	}
538
539
	/**
540
	 * Check if the user that is sharing can actually share
541
	 *
542
	 * @param \OCP\Share\IShare $share
543
	 * @throws \Exception
544
	 */
545
	protected function canShare(\OCP\Share\IShare $share) {
546
		if (!$this->shareApiEnabled()) {
547
			throw new \Exception('The share API is disabled');
548
		}
549
550
		if ($this->sharingDisabledForUser($share->getSharedBy())) {
551
			throw new \Exception('You are not allowed to share');
552
		}
553
	}
554
555
	/**
556
	 * Share a path
557
	 *
558
	 * @param \OCP\Share\IShare $share
559
	 * @return Share The share object
560
	 * @throws \Exception
561
	 *
562
	 * TODO: handle link share permissions or check them
563
	 */
564
	public function createShare(\OCP\Share\IShare $share) {
565
		$this->canShare($share);
566
567
		$this->generalCreateChecks($share);
568
569
		// Verify if there are any issues with the path
570
		$this->pathCreateChecks($share->getNode());
571
572
		/*
573
		 * On creation of a share the owner is always the owner of the path
574
		 * Except for mounted federated shares.
575
		 */
576
		$storage = $share->getNode()->getStorage();
577
		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
578
			$parent = $share->getNode()->getParent();
579
			while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
580
				$parent = $parent->getParent();
581
			}
582
			$share->setShareOwner($parent->getOwner()->getUID());
583
		} else {
584
			$share->setShareOwner($share->getNode()->getOwner()->getUID());
585
		}
586
587
		//Verify share type
588
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
589
			$this->userCreateChecks($share);
590
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
591
			$this->groupCreateChecks($share);
592
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
593
			$this->linkCreateChecks($share);
594
			$this->setLinkParent($share);
595
596
			/*
597
			 * For now ignore a set token.
598
			 */
599
			$share->setToken(
600
				$this->secureRandom->generate(
601
					\OC\Share\Constants::TOKEN_LENGTH,
602
					\OCP\Security\ISecureRandom::CHAR_LOWER.
603
					\OCP\Security\ISecureRandom::CHAR_UPPER.
604
					\OCP\Security\ISecureRandom::CHAR_DIGITS
605
				)
606
			);
607
608
			//Verify the expiration date
609
			$this->validateExpirationDate($share);
610
611
			//Verify the password
612 View Code Duplication
			if ($this->passwordMustBeEnforced($share->getPermissions()) && $share->getPassword() === 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...
613
				throw new \InvalidArgumentException('Passwords are enforced for link shares');
614
			} else {
615
				$this->verifyPassword($share->getPassword());
616
			}
617
618
			// If a password is set. Hash it!
619
			if ($share->getPassword() !== null) {
620
				$share->setPassword($this->hasher->hash($share->getPassword()));
621
			}
622
		}
623
624
		// Cannot share with the owner
625 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...
626
			$share->getSharedWith() === $share->getShareOwner()) {
627
			throw new \InvalidArgumentException('Can\'t share with the share owner');
628
		}
629
630
		// Generate the target
631
		$target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName();
632
		$target = \OC\Files\Filesystem::normalizePath($target);
633
		$share->setTarget($target);
634
635
		// Pre share hook
636
		$run = true;
637
		$error = '';
638
		$preHookData = [
639
			'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
640
			'itemSource' => $share->getNode()->getId(),
641
			'shareType' => $share->getShareType(),
642
			'uidOwner' => $share->getSharedBy(),
643
			'permissions' => $share->getPermissions(),
644
			'fileSource' => $share->getNode()->getId(),
645
			'expiration' => $share->getExpirationDate(),
646
			'token' => $share->getToken(),
647
			'itemTarget' => $share->getTarget(),
648
			'shareWith' => $share->getSharedWith(),
649
			'run' => &$run,
650
			'error' => &$error,
651
		];
652
		\OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
653
654
		$beforeEvent = new GenericEvent(null, ['shareData' => $preHookData, 'shareObject' => $share]);
655
		$this->eventDispatcher->dispatch('share.beforeCreate', $beforeEvent);
656
657
		if ($run === false) {
658
			throw new \Exception($error);
659
		}
660
661
		$provider = $this->factory->getProviderForType($share->getShareType());
662
		$share = $provider->create($share);
663
664
		// Post share hook
665
		$postHookData = [
666
			'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
667
			'itemSource' => $share->getNode()->getId(),
668
			'shareType' => $share->getShareType(),
669
			'uidOwner' => $share->getSharedBy(),
670
			'permissions' => $share->getPermissions(),
671
			'fileSource' => $share->getNode()->getId(),
672
			'expiration' => $share->getExpirationDate(),
673
			'token' => $share->getToken(),
674
			'id' => $share->getId(),
675
			'shareWith' => $share->getSharedWith(),
676
			'itemTarget' => $share->getTarget(),
677
			'fileTarget' => $share->getTarget(),
678
			'passwordEnabled' => ($share->getPassword() !== null and ($share->getPassword() !== '')),
679
		];
680
681
		\OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
682
683
		$afterEvent = new GenericEvent(null, ['shareData' => $postHookData, 'shareObject' => $share]);
684
		$this->eventDispatcher->dispatch('share.afterCreate', $afterEvent);
685
686
		return $share;
687
	}
688
689
	/**
690
	 * Transfer shares from oldOwner to newOwner. Both old and new owners are uid
691
	 *
692
	 * finalTarget is of the form "user1/files/transferred from admin on 20180509"
693
	 *
694
	 * TransferShareException would be thrown when:
695
	 *  - oldOwner, newOwner does not exist.
696
	 *  - oldOwner and newOwner are same
697
	 * NotFoundException would be thrown when finalTarget does not exist in the file
698
	 * system
699
	 *
700
	 * @param IShare $share
701
	 * @param string $oldOwner
702
	 * @param string $newOwner
703
	 * @param string $finalTarget
704
	 * @param null|bool $isChild
705
	 * @throws TransferSharesException
706
	 * @throws NotFoundException
707
	 */
708
	public function transferShare(IShare $share, $oldOwner, $newOwner, $finalTarget, $isChild = null) {
709
		if ($this->userManager->get($oldOwner) === null) {
710
			throw new TransferSharesException("The current owner of the share $oldOwner doesn't exist");
711
		}
712
		if ($this->userManager->get($newOwner) === null) {
713
			throw new TransferSharesException("The future owner $newOwner, where the share has to be moved doesn't exist");
714
		}
715
716
		if ($oldOwner === $newOwner) {
717
			throw new TransferSharesException("The current owner of the share and the future owner of the share are same");
718
		}
719
720
		//If the destination location, i.e finalTarget is not present, then
721
		//throw an exception
722
		if (!$this->view->file_exists($finalTarget)) {
723
			throw new NotFoundException("The target location $finalTarget doesn't exist");
724
		}
725
726
		if ($isChild === true) {
727
			//Set the parent to null so that we don't lose the shares after transfer
728
			$builder = $this->connection->getQueryBuilder();
729
			$builder->update('share')
730
				->set('parent', 'null')
731
				->where($builder->expr()->eq('id', $builder->createNamedParameter($share->getId())))
732
				->execute();
733
		}
734
		/**
735
		 * If the share was already shared with new owner, then we can delete it
736
		 */
737
		if ($share->getSharedWith() === $newOwner) {
738
			// Unmount the shares before deleting, so we don't try to get the storage later on.
739
			$shareMountPoint = $this->mountManager->find('/' . $newOwner . '/files' . $share->getTarget());
740
			if ($shareMountPoint) {
741
				$this->mountManager->removeMount($shareMountPoint->getMountPoint());
742
			}
743
744
			$provider = $this->factory->getProviderForType($share->getShareType());
745
			//Try to get the children transferred and then delete the parent
746
			foreach ($provider->getChildren($share) as $child) {
747
				$this->transferShare($child, $oldOwner, $newOwner, $finalTarget, true);
748
			}
749
			$this->deleteShare($share);
750
		} else {
751
			$sharedWith = $share->getSharedWith();
752
753
			$targetFile = '/' . \rtrim(\basename($finalTarget), '/') . '/' . \ltrim(\basename($share->getTarget()), '/');
754
			/**
755
			 * Scenario where share is made by old owner to a user different
756
			 * from new owner
757
			 */
758
			if (($sharedWith !== null) && ($sharedWith !== $oldOwner) && ($sharedWith !== $newOwner)) {
759
				$sharedBy = $share->getSharedBy();
760
				$sharedOwner = $share->getShareOwner();
761
				//The origin of the share now has to be the destination user.
762
				if ($sharedBy === $oldOwner) {
763
					$share->setSharedBy($newOwner);
764
				}
765
				if ($sharedOwner === $oldOwner) {
766
					$share->setShareOwner($newOwner);
767
				}
768
				if (($sharedBy === $oldOwner) || ($sharedOwner === $oldOwner)) {
769
					$share->setTarget($targetFile);
770
				}
771
			} else {
772
				if ($share->getShareOwner() === $oldOwner) {
773
					$share->setShareOwner($newOwner);
774
				}
775
				if ($share->getSharedBy() === $oldOwner) {
776
					$share->setSharedBy($newOwner);
777
				}
778
			}
779
780
			/**
781
			 * Here we update the target when the share is link
782
			 */
783
			if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
784
				$share->setTarget($targetFile);
785
			}
786
787
			$this->updateShare($share);
788
		}
789
	}
790
791
	/**
792
	 * Update a share
793
	 *
794
	 * @param \OCP\Share\IShare $share
795
	 * @return \OCP\Share\IShare The share object
796
	 * @throws \InvalidArgumentException
797
	 */
798
	public function updateShare(\OCP\Share\IShare $share) {
799
		$expirationDateUpdated = false;
800
801
		$this->canShare($share);
802
803
		try {
804
			$originalShare = $this->getShareById($share->getFullId());
805
		} catch (\UnexpectedValueException $e) {
806
			throw new \InvalidArgumentException('Share does not have a full id');
807
		}
808
809
		// We can't change the share type!
810
		if ($share->getShareType() !== $originalShare->getShareType()) {
811
			throw new \InvalidArgumentException('Can\'t change share type');
812
		}
813
814
		// We can only change the recipient on user shares
815
		if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
816
			$share->getShareType() !== \OCP\Share::SHARE_TYPE_USER) {
817
			throw new \InvalidArgumentException('Can only update recipient on user shares');
818
		}
819
820
		// Cannot share with the owner
821 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...
822
			$share->getSharedWith() === $share->getShareOwner()) {
823
			throw new \InvalidArgumentException('Can\'t share with the share owner');
824
		}
825
826
		$this->generalCreateChecks($share);
827
828
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
829
			$this->userCreateChecks($share);
830
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
831
			$this->groupCreateChecks($share);
832
		} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
833
			$this->linkCreateChecks($share);
834
835
			// Password updated.
836
			if ($share->getPassword() !== $originalShare->getPassword() ||
837
					$share->getPermissions() !== $originalShare->getPermissions()) {
838
				//Verify the password. Permissions must be taken into account in case the password must be enforced
839 View Code Duplication
				if ($this->passwordMustBeEnforced($share->getPermissions()) && $share->getPassword() === 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...
840
					throw new \InvalidArgumentException('Passwords are enforced for link shares');
841
				} else {
842
					$this->verifyPassword($share->getPassword(), $share->getPermissions());
0 ignored issues
show
Unused Code introduced by
The call to Manager::verifyPassword() has too many arguments starting with $share->getPermissions().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
843
				}
844
845
				// If a password is set. Hash it! (only if the password has changed)
846
				if ($share->getPassword() !== null && $share->getPassword() !== $originalShare->getPassword()) {
847
					$share->setPassword($this->hasher->hash($share->getPassword()));
848
				}
849
			}
850
851
			//Verify the expiration date
852
			$this->validateExpirationDate($share);
853
854
			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
855
				$expirationDateUpdated = true;
856
			}
857
		}
858
859
		$this->pathCreateChecks($share->getNode());
860
861
		// Now update the share!
862
		$provider = $this->factory->getProviderForType($share->getShareType());
863
		$share = $provider->update($share);
864
865
		$shareAfterUpdateEvent = new GenericEvent(null);
866
		$shareAfterUpdateEvent->setArgument('shareobject', $share);
867
		$update = false;
868 View Code Duplication
		if ($expirationDateUpdated === true) {
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...
869
			\OC_Hook::emit('OCP\Share', 'post_set_expiration_date', [
870
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
871
				'itemSource' => $share->getNode()->getId(),
872
				'date' => $share->getExpirationDate(),
873
				'uidOwner' => $share->getSharedBy(),
874
			]);
875
			$shareAfterUpdateEvent->setArgument('expirationdateupdated', true);
876
			$shareAfterUpdateEvent->setArgument('oldexpirationdate', $originalShare->getExpirationDate());
877
			$update = true;
878
		}
879
880 View Code Duplication
		if ($share->getPassword() !== $originalShare->getPassword()) {
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...
881
			\OC_Hook::emit('OCP\Share', 'post_update_password', [
882
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
883
				'itemSource' => $share->getNode()->getId(),
884
				'uidOwner' => $share->getSharedBy(),
885
				'token' => $share->getToken(),
886
				'disabled' => $share->getPassword() === null,
887
			]);
888
			$shareAfterUpdateEvent->setArgument('passwordupdate', true);
889
			$update = true;
890
		}
891
892
		if ($share->getPermissions() !== $originalShare->getPermissions()) {
893
			if ($this->userManager->userExists($share->getShareOwner())) {
894
				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
895
			} else {
896
				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
897
			}
898
			\OC_Hook::emit('OCP\Share', 'post_update_permissions', [
899
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
900
				'itemSource' => $share->getNode()->getId(),
901
				'shareType' => $share->getShareType(),
902
				'shareWith' => $share->getSharedWith(),
903
				'uidOwner' => $share->getSharedBy(),
904
				'permissions' => $share->getPermissions(),
905
				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
906
			]);
907
			$shareAfterUpdateEvent->setArgument('permissionupdate', true);
908
			$shareAfterUpdateEvent->setArgument('oldpermissions', $originalShare->getPermissions());
909
			$shareAfterUpdateEvent->setArgument('path', $userFolder->getRelativePath($share->getNode()->getPath()));
910
			$update = true;
911
		}
912
913
		if ($share->getName() !== $originalShare->getName()) {
914
			$shareAfterUpdateEvent->setArgument('sharenameupdated', true);
915
			$shareAfterUpdateEvent->setArgument('oldname', $originalShare->getName());
916
			$update = true;
917
		}
918
919
		if ($update === true) {
920
			$this->eventDispatcher->dispatch('share.afterupdate', $shareAfterUpdateEvent);
921
		}
922
		return $share;
923
	}
924
925
	/**
926
	 * Delete all the children of this share
927
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
928
	 *
929
	 * @param \OCP\Share\IShare $share
930
	 * @return \OCP\Share\IShare[] List of deleted shares
931
	 */
932
	protected function deleteChildren(\OCP\Share\IShare $share) {
933
		$deletedShares = [];
934
935
		$provider = $this->factory->getProviderForType($share->getShareType());
936
937
		foreach ($provider->getChildren($share) as $child) {
938
			$deletedChildren = $this->deleteChildren($child);
939
			$deletedShares = \array_merge($deletedShares, $deletedChildren);
940
941
			$provider->delete($child);
942
			$deletedShares[] = $child;
943
		}
944
945
		return $deletedShares;
946
	}
947
948
	protected static function formatUnshareHookParams(\OCP\Share\IShare $share) {
949
		// Prepare hook
950
		$shareType = $share->getShareType();
951
		$sharedWith = '';
952
		if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
953
			$sharedWith = $share->getSharedWith();
954
		} elseif ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
955
			$sharedWith = $share->getSharedWith();
956
		} elseif ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) {
957
			$sharedWith = $share->getSharedWith();
958
		}
959
960
		$hookParams = [
961
			'id'         => $share->getId(),
962
			'itemType'   => $share->getNodeType(),
963
			'itemSource' => $share->getNodeId(),
964
			'shareType'  => $shareType,
965
			'shareWith'  => $sharedWith,
966
			'itemparent' => \method_exists($share, 'getParent') ? $share->getParent() : '',
967
			'uidOwner'   => $share->getSharedBy(),
968
			'fileSource' => $share->getNodeId(),
969
			'fileTarget' => $share->getTarget()
970
		];
971
		return $hookParams;
972
	}
973
974
	/**
975
	 * Delete a share
976
	 *
977
	 * @param \OCP\Share\IShare $share
978
	 * @throws ShareNotFound
979
	 * @throws \InvalidArgumentException
980
	 */
981
	public function deleteShare(\OCP\Share\IShare $share) {
982
		try {
983
			$share->getFullId();
984
		} catch (\UnexpectedValueException $e) {
985
			throw new \InvalidArgumentException('Share does not have a full id');
986
		}
987
988
		$hookParams = self::formatUnshareHookParams($share);
989
990
		// Emit pre-hook
991
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
992
993
		$beforeEvent = new GenericEvent(null, ['shareData' => $hookParams, 'shareObject' => $share]);
994
		$this->eventDispatcher->dispatch('share.beforeDelete', $beforeEvent);
995
		// Get all children and delete them as well
996
		$deletedShares = $this->deleteChildren($share);
997
998
		// Do the actual delete
999
		$provider = $this->factory->getProviderForType($share->getShareType());
1000
		$provider->delete($share);
1001
1002
		// All the deleted shares caused by this delete
1003
		$deletedShares[] = $share;
1004
1005
		//Format hook info
1006
		$formattedDeletedShares = \array_map('self::formatUnshareHookParams', $deletedShares);
1007
1008
		$hookParams['deletedShares'] = $formattedDeletedShares;
1009
1010
		// Emit post hook
1011
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
1012
		$afterEvent = new GenericEvent(null, ['shareData' => $hookParams['deletedShares'], 'shareObject' => $share]);
1013
		$this->eventDispatcher->dispatch('share.afterDelete', $afterEvent);
1014
	}
1015
1016
	/**
1017
	 * Unshare a file as the recipient.
1018
	 * This can be different from a regular delete for example when one of
1019
	 * the users in a groups deletes that share. But the provider should
1020
	 * handle this.
1021
	 *
1022
	 * @param \OCP\Share\IShare $share
1023
	 * @param string $recipientId
1024
	 */
1025
	public function deleteFromSelf(\OCP\Share\IShare $share, $recipientId) {
1026
		list($providerId, ) = $this->splitFullId($share->getFullId());
1027
		$provider = $this->factory->getProvider($providerId);
1028
1029
		$provider->deleteFromSelf($share, $recipientId);
1030
1031
		// Emit post hook. The parameter data structure is slightly different
1032
		// from the post_unshare hook to maintain backward compatibility with
1033
		// Share 1.0: the array contains all the key-value pairs from the old
1034
		// library plus some new ones.
1035
		$hookParams = self::formatUnshareHookParams($share);
1036
		$hookParams['itemTarget'] = $hookParams['fileTarget'];
1037
		$hookParams['unsharedItems'] = [$hookParams];
1038
		\OC_Hook::emit('OCP\Share', 'post_unshareFromSelf', $hookParams);
1039
		$event = new GenericEvent(null, [
1040
			'shareRecipient' => $recipientId,
1041
			'shareOwner' => $share->getSharedBy(),
1042
			'recipientPath' => $share->getTarget(),
1043
			'ownerPath' => $share->getNode()->getPath(),
1044
			'nodeType' => $share->getNodeType()]);
1045
		$this->eventDispatcher->dispatch('fromself.unshare', $event);
1046
	}
1047
1048
	/**
1049
	 * @inheritdoc
1050
	 */
1051
	public function moveShare(\OCP\Share\IShare $share, $recipientId) {
1052
		return $this->updateShareForRecipient($share, $recipientId);
1053
	}
1054
1055
	/**
1056
	 * @inheritdoc
1057
	 */
1058
	public function updateShareForRecipient(\OCP\Share\IShare $share, $recipientId) {
1059
		list($providerId, ) = $this->splitFullId($share->getFullId());
1060
		$provider = $this->factory->getProvider($providerId);
1061
1062
		return $provider->updateForRecipient($share, $recipientId);
1063
	}
1064
1065
	/**
1066
	 * @inheritdoc
1067
	 */
1068
	public function getAllSharesBy($userId, $shareTypes, $nodeIDs, $reshares = false) {
1069
		// This function requires at least 1 node (parent folder)
1070
		if (empty($nodeIDs)) {
1071
			throw new \InvalidArgumentException('Array of nodeIDs empty');
1072
		}
1073
		// This will ensure that if there are multiple share providers for the same share type, we will execute it in batches
1074
		$shares = [];
1075
1076
		$providerIdMap = $this->shareTypeToProviderMap($shareTypes);
1077
1078
		$today = new \DateTime();
1079
		foreach ($providerIdMap as $providerId => $shareTypeArray) {
1080
			// Get provider from cache
1081
			$provider = $this->factory->getProvider($providerId);
1082
1083
			$queriedShares = $provider->getAllSharesBy($userId, $shareTypeArray, $nodeIDs, $reshares);
0 ignored issues
show
Documentation introduced by
$shareTypeArray is of type integer, but the function expects a array<integer,integer>.

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...
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...
1084
			foreach ($queriedShares as $queriedShare) {
1085
				if ($queriedShare->getShareType() === \OCP\Share::SHARE_TYPE_LINK && $queriedShare->getExpirationDate() !== null &&
1086
					$queriedShare->getExpirationDate() <= $today
1087
				) {
1088
					try {
1089
						$this->deleteShare($queriedShare);
1090
					} catch (NotFoundException $e) {
1091
						//Ignore since this basically means the share is deleted
1092
					}
1093
					continue;
1094
				}
1095
				\array_push($shares, $queriedShare);
1096
			}
1097
		}
1098
1099
		return $shares;
1100
	}
1101
1102
	/**
1103
	 * @inheritdoc
1104
	 */
1105
	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
1106
		if ($path !== null &&
1107
				!($path instanceof \OCP\Files\File) &&
1108
				!($path instanceof \OCP\Files\Folder)) {
1109
			throw new \InvalidArgumentException('invalid path');
1110
		}
1111
1112
		$provider = $this->factory->getProviderForType($shareType);
1113
1114
		$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1115
1116
		/*
1117
		 * Work around so we don't return expired shares but still follow
1118
		 * proper pagination.
1119
		 */
1120
		if ($shareType === \OCP\Share::SHARE_TYPE_LINK) {
1121
			$shares2 = [];
1122
			$today = new \DateTime();
1123
1124
			while (true) {
1125
				$added = 0;
1126
				foreach ($shares as $share) {
1127
					// Check if the share is expired and if so delete it
1128
					if ($share->getExpirationDate() !== null &&
1129
						$share->getExpirationDate() <= $today
1130
					) {
1131
						try {
1132
							$this->deleteShare($share);
1133
						} catch (NotFoundException $e) {
1134
							//Ignore since this basically means the share is deleted
1135
						}
1136
						continue;
1137
					}
1138
					$added++;
1139
					$shares2[] = $share;
1140
1141
					if (\count($shares2) === $limit) {
1142
						break;
1143
					}
1144
				}
1145
1146
				if (\count($shares2) === $limit) {
1147
					break;
1148
				}
1149
1150
				// If there was no limit on the select we are done
1151
				if ($limit === -1) {
1152
					break;
1153
				}
1154
1155
				$offset += $added;
1156
1157
				// Fetch again $limit shares
1158
				$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1159
1160
				// No more shares means we are done
1161
				if (empty($shares)) {
1162
					break;
1163
				}
1164
			}
1165
1166
			$shares = $shares2;
1167
		}
1168
1169
		return $shares;
1170
	}
1171
1172
	/**
1173
	 * @inheritdoc
1174
	 */
1175
	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1176
		$provider = $this->factory->getProviderForType($shareType);
1177
1178
		return $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1179
	}
1180
1181
	/**
1182
	 * @inheritdoc
1183
	 */
1184
	public function getAllSharedWith($userId, $shareTypes, $node = null) {
1185
		$shares = [];
1186
		
1187
		// Aggregate all required $shareTypes by mapping provider to supported shareTypes
1188
		$providerIdMap = $this->shareTypeToProviderMap($shareTypes);
1189
		foreach ($providerIdMap as $providerId => $shareTypeArray) {
1190
			// Get provider from cache
1191
			$provider = $this->factory->getProvider($providerId);
1192
			
1193
			// Obtain all shares for all the supported provider types
1194
			$queriedShares = $provider->getAllSharedWith($userId, $node);
1195
			$shares = \array_merge($shares, $queriedShares);
1196
		}
1197
1198
		return $shares;
1199
	}
1200
	
1201
	/**
1202
	 * @inheritdoc
1203
	 */
1204
	public function getShareById($id, $recipient = null) {
1205
		if ($id === null) {
1206
			throw new ShareNotFound();
1207
		}
1208
1209
		list($providerId, $id) = $this->splitFullId($id);
1210
		$provider = $this->factory->getProvider($providerId);
1211
1212
		$share = $provider->getShareById($id, $recipient);
1213
1214
		// Validate link shares expiration date
1215
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1216
			$share->getExpirationDate() !== null &&
1217
			$share->getExpirationDate() <= new \DateTime()) {
1218
			$this->deleteShare($share);
1219
			throw new ShareNotFound();
1220
		}
1221
1222
		return $share;
1223
	}
1224
1225
	/**
1226
	 * Get all the shares for a given path
1227
	 *
1228
	 * @param \OCP\Files\Node $path
1229
	 * @param int $page
1230
	 * @param int $perPage
1231
	 *
1232
	 * @return Share[]
1233
	 */
1234
	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...
1235
		$types = [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP];
1236
		$providers = [];
1237
		$results = [];
1238
1239
		foreach ($types as $type) {
1240
			$provider = $this->factory->getProviderForType($type);
1241
			// store this way to deduplicate entries by id
1242
			$providers[$provider->identifier()] = $provider;
1243
		}
1244
1245
		foreach ($providers as $provider) {
1246
			$results = \array_merge($results, $provider->getSharesByPath($path));
1247
		}
1248
1249
		return $results;
1250
	}
1251
1252
	/**
1253
	 * Get the share by token possible with password
1254
	 *
1255
	 * @param string $token
1256
	 * @return Share
1257
	 *
1258
	 * @throws ShareNotFound
1259
	 */
1260
	public function getShareByToken($token) {
1261
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_LINK);
1262
1263
		try {
1264
			$share = $provider->getShareByToken($token);
1265
		} catch (ShareNotFound $e) {
1266
			$share = null;
1267
		}
1268
1269
		// If it is not a link share try to fetch a federated share by token
1270
		if ($share === null) {
1271
			$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_REMOTE);
1272
			$share = $provider->getShareByToken($token);
1273
		}
1274
1275
		if ($share->getExpirationDate() !== null &&
1276
			$share->getExpirationDate() <= new \DateTime()) {
1277
			$this->deleteShare($share);
1278
			throw new ShareNotFound();
1279
		}
1280
1281
		/*
1282
		 * Reduce the permissions for link shares if public upload is not enabled
1283
		 */
1284
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1285
			!$this->shareApiLinkAllowPublicUpload()) {
1286
			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1287
		}
1288
1289
		return $share;
1290
	}
1291
1292
	/**
1293
	 * Verify the password of a public share
1294
	 *
1295
	 * @param \OCP\Share\IShare $share
1296
	 * @param string $password
1297
	 * @return bool
1298
	 */
1299
	public function checkPassword(\OCP\Share\IShare $share, $password) {
1300
		if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK) {
1301
			//TODO maybe exception?
1302
			return false;
1303
		}
1304
1305
		if ($password === null || $share->getPassword() === null) {
1306
			return false;
1307
		}
1308
1309
		$newHash = '';
1310
		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1311
			return false;
1312
		}
1313
1314
		if (!empty($newHash)) {
1315
			$share->setPassword($newHash);
1316
			$provider = $this->factory->getProviderForType($share->getShareType());
1317
			$provider->update($share);
1318
		}
1319
1320
		return true;
1321
	}
1322
1323
	/**
1324
	 * @inheritdoc
1325
	 */
1326
	public function userDeleted($uid) {
1327
		$types = [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE];
1328
1329
		foreach ($types as $type) {
1330
			$provider = $this->factory->getProviderForType($type);
1331
			$provider->userDeleted($uid, $type);
1332
		}
1333
	}
1334
1335
	/**
1336
	 * @inheritdoc
1337
	 */
1338
	public function groupDeleted($gid) {
1339
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1340
		$provider->groupDeleted($gid);
1341
	}
1342
1343
	/**
1344
	 * @inheritdoc
1345
	 */
1346
	public function userDeletedFromGroup($uid, $gid) {
1347
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1348
		$provider->userDeletedFromGroup($uid, $gid);
1349
	}
1350
1351
	/**
1352
	 * Get access list to a path. This means
1353
	 * all the users and groups that can access a given path.
1354
	 *
1355
	 * Consider:
1356
	 * -root
1357
	 * |-folder1
1358
	 *  |-folder2
1359
	 *   |-fileA
1360
	 *
1361
	 * fileA is shared with user1
1362
	 * folder2 is shared with group2
1363
	 * folder1 is shared with user2
1364
	 *
1365
	 * Then the access list will to '/folder1/folder2/fileA' is:
1366
	 * [
1367
	 * 	'users' => ['user1', 'user2'],
1368
	 *  'groups' => ['group2']
1369
	 * ]
1370
	 *
1371
	 * This is required for encryption
1372
	 *
1373
	 * @param \OCP\Files\Node $path
1374
	 */
1375
	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...
1376
	}
1377
1378
	/**
1379
	 * Create a new share
1380
	 * @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...
1381
	 */
1382
	public function newShare() {
1383
		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1384
	}
1385
1386
	/**
1387
	 * Is the share API enabled
1388
	 *
1389
	 * @return bool
1390
	 */
1391
	public function shareApiEnabled() {
1392
		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1393
	}
1394
1395
	/**
1396
	 * Is public link sharing enabled
1397
	 *
1398
	 * @return bool
1399
	 */
1400
	public function shareApiAllowLinks() {
1401
		return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
1402
	}
1403
1404
	/**
1405
	 * Is password on public link requires (fallback to shareApiLinkEnforcePasswordReadOnly)
1406
	 *
1407
	 * @return bool
1408
	 */
1409
	public function shareApiLinkEnforcePassword() {
1410
		return $this->shareApiLinkEnforcePasswordReadOnly();
1411
	}
1412
1413
	/**
1414
	 * Is password enforced for read-only shares?
1415
	 *
1416
	 * @return bool
1417
	 */
1418
	public function shareApiLinkEnforcePasswordReadOnly() {
1419
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password_read_only', 'no') === 'yes';
1420
	}
1421
1422
	/**
1423
	 * Is password enforced for read & write shares?
1424
	 *
1425
	 * @return bool
1426
	 */
1427
	public function shareApiLinkEnforcePasswordReadWrite() {
1428
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password_read_write', 'no') === 'yes';
1429
	}
1430
1431
	/**
1432
	 * Is password enforced for write-only shares?
1433
	 *
1434
	 * @return bool
1435
	 */
1436
	public function shareApiLinkEnforcePasswordWriteOnly() {
1437
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password_write_only', 'no') === 'yes';
1438
	}
1439
1440
	/**
1441
	 * Is default expire date enabled
1442
	 *
1443
	 * @return bool
1444
	 */
1445
	public function shareApiLinkDefaultExpireDate() {
1446
		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1447
	}
1448
1449
	/**
1450
	 * Is default expire date enforced
1451
	 *`
1452
	 * @return bool
1453
	 */
1454
	public function shareApiLinkDefaultExpireDateEnforced() {
1455
		return $this->shareApiLinkDefaultExpireDate() &&
1456
			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1457
	}
1458
1459
	/**
1460
	 * Number of default expire days
1461
	 *shareApiLinkAllowPublicUpload
1462
	 * @return int
1463
	 */
1464
	public function shareApiLinkDefaultExpireDays() {
1465
		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1466
	}
1467
1468
	/**
1469
	 * Allow public upload on link shares
1470
	 *
1471
	 * @return bool
1472
	 */
1473
	public function shareApiLinkAllowPublicUpload() {
1474
		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1475
	}
1476
1477
	/**
1478
	 * check if user can only share with group members
1479
	 * @return bool
1480
	 */
1481
	public function shareWithGroupMembersOnly() {
1482
		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1483
	}
1484
1485
	/**
1486
	 * check if user can only share with groups he's member of
1487
	 * @return bool
1488
	 */
1489
	public function shareWithMembershipGroupOnly() {
1490
		return $this->config->getAppValue('core', 'shareapi_only_share_with_membership_groups', 'no') === 'yes';
1491
	}
1492
1493
	/**
1494
	 * Check if users can share with groups
1495
	 * @return bool
1496
	 */
1497
	public function allowGroupSharing() {
1498
		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1499
	}
1500
1501
	/**
1502
	 * Copied from \OC_Util::isSharingDisabledForUser
1503
	 *
1504
	 * @param string $userId
1505
	 * @return bool
1506
	 */
1507
	public function sharingDisabledForUser($userId) {
1508
		if ($userId === null) {
1509
			return false;
1510
		}
1511
1512
		if (isset($this->sharingDisabledForUsersCache[$userId])) {
1513
			return $this->sharingDisabledForUsersCache[$userId];
1514
		}
1515
1516
		if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
1517
			$groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1518
			$excludedGroups = \json_decode($groupsList);
1519
			if ($excludedGroups === null) {
1520
				$excludedGroups = \explode(',', $groupsList);
1521
				$newValue = \json_encode($excludedGroups);
1522
				$this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
1523
			}
1524
			$user = $this->userManager->get($userId);
1525
			$usersGroups = $this->groupManager->getUserGroupIds($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($userId) on line 1524 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...
1526
			$matchingGroups = \array_intersect($usersGroups, $excludedGroups);
1527
			if (!empty($matchingGroups)) {
1528
				// If the user is a member of any of the excluded groups they cannot use sharing
1529
				$this->sharingDisabledForUsersCache[$userId] = true;
1530
				return true;
1531
			}
1532
		}
1533
1534
		$this->sharingDisabledForUsersCache[$userId] = false;
1535
		return false;
1536
	}
1537
1538
	/**
1539
	 * @inheritdoc
1540
	 */
1541
	public function outgoingServer2ServerSharesAllowed() {
1542
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1543
	}
1544
}
1545