Completed
Push — master ( b4df57...e6895c )
by Lukas
26s
created

Manager   F

Complexity

Total Complexity 182

Size/Duplication

Total Lines 1210
Duplicated Lines 4.13 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 50
loc 1210
rs 0.6314
wmc 182
lcom 1
cbo 22

39 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 24 1
A splitFullId() 0 3 1
B verifyPassword() 0 23 4
D generalCreateChecks() 20 103 26
C validateExpirationDate() 8 62 11
C userCreateChecks() 0 48 10
C groupCreateChecks() 0 36 8
B linkCreateChecks() 0 22 6
A setLinkParent() 0 10 3
A pathCreateChecks() 0 11 4
A canShare() 0 9 3
C createShare() 4 113 12
D updateShare() 4 99 20
A deleteChildren() 0 15 2
B deleteShare() 0 59 6
A deleteFromSelf() 0 6 1
B moveShare() 0 22 6
C getSharesBy() 0 66 14
A getSharedWith() 0 5 1
B getShareById() 0 20 5
A getSharesByPath() 0 2 1
C getShareByToken() 0 31 7
B checkPassword() 0 23 6
A userDeleted() 0 8 2
A groupDeleted() 0 4 1
A userDeletedFromGroup() 0 4 1
A getAccessList() 0 2 1
A newShare() 0 3 1
A shareApiEnabled() 0 3 1
A shareApiAllowLinks() 0 3 1
A shareApiLinkEnforcePassword() 0 3 1
A shareApiLinkDefaultExpireDate() 0 3 1
A shareApiLinkDefaultExpireDateEnforced() 0 4 2
A shareApiLinkDefaultExpireDays() 0 3 1
A shareApiLinkAllowPublicUpload() 0 3 1
A shareWithGroupMembersOnly() 0 3 1
A allowGroupSharing() 0 3 1
C sharingDisabledForUser() 14 33 7
A outgoingServer2ServerSharesAllowed() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Manager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Manager, and based on these observations, apply Extract Interface, too.

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
 *
8
 * @copyright Copyright (c) 2016, ownCloud, Inc.
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OC\Share20;
26
27
use OC\Cache\CappedMemoryCache;
28
use OC\Files\Mount\MoveableMount;
29
use OCP\Files\File;
30
use OCP\Files\Folder;
31
use OCP\Files\IRootFolder;
32
use OCP\Files\Mount\IMountManager;
33
use OCP\Files\NotFoundException;
34
use OCP\IConfig;
35
use OCP\IGroupManager;
36
use OCP\IL10N;
37
use OCP\ILogger;
38
use OCP\IUserManager;
39
use OCP\Security\IHasher;
40
use OCP\Security\ISecureRandom;
41
use OCP\Share\Exceptions\GenericShareException;
42
use OCP\Share\Exceptions\ShareNotFound;
43
use OCP\Share\IManager;
44
use OCP\Share\IProviderFactory;
45
46
/**
47
 * This class is the communication hub for all sharing related operations.
48
 */
49
class Manager implements IManager {
50
51
	/** @var IProviderFactory */
52
	private $factory;
53
	/** @var ILogger */
54
	private $logger;
55
	/** @var IConfig */
56
	private $config;
57
	/** @var ISecureRandom */
58
	private $secureRandom;
59
	/** @var IHasher */
60
	private $hasher;
61
	/** @var IMountManager */
62
	private $mountManager;
63
	/** @var IGroupManager */
64
	private $groupManager;
65
	/** @var IL10N */
66
	private $l;
67
	/** @var IUserManager */
68
	private $userManager;
69
	/** @var IRootFolder */
70
	private $rootFolder;
71
	/** @var CappedMemoryCache */
72
	private $sharingDisabledForUsersCache;
73
74
75
	/**
76
	 * Manager constructor.
77
	 *
78
	 * @param ILogger $logger
79
	 * @param IConfig $config
80
	 * @param ISecureRandom $secureRandom
81
	 * @param IHasher $hasher
82
	 * @param IMountManager $mountManager
83
	 * @param IGroupManager $groupManager
84
	 * @param IL10N $l
85
	 * @param IProviderFactory $factory
86
	 * @param IUserManager $userManager
87
	 * @param IRootFolder $rootFolder
88
	 */
89
	public function __construct(
90
			ILogger $logger,
91
			IConfig $config,
92
			ISecureRandom $secureRandom,
93
			IHasher $hasher,
94
			IMountManager $mountManager,
95
			IGroupManager $groupManager,
96
			IL10N $l,
97
			IProviderFactory $factory,
98
			IUserManager $userManager,
99
			IRootFolder $rootFolder
100
	) {
101
		$this->logger = $logger;
102
		$this->config = $config;
103
		$this->secureRandom = $secureRandom;
104
		$this->hasher = $hasher;
105
		$this->mountManager = $mountManager;
106
		$this->groupManager = $groupManager;
107
		$this->l = $l;
108
		$this->factory = $factory;
109
		$this->userManager = $userManager;
110
		$this->rootFolder = $rootFolder;
111
		$this->sharingDisabledForUsersCache = new CappedMemoryCache();
112
	}
113
114
	/**
115
	 * Convert from a full share id to a tuple (providerId, shareId)
116
	 *
117
	 * @param string $id
118
	 * @return string[]
119
	 */
120
	private function splitFullId($id) {
121
		return explode(':', $id, 2);
122
	}
123
124
	/**
125
	 * Verify if a password meets all requirements
126
	 *
127
	 * @param string $password
128
	 * @throws \Exception
129
	 */
130
	protected function verifyPassword($password) {
131
		if ($password === null) {
132
			// No password is set, check if this is allowed.
133
			if ($this->shareApiLinkEnforcePassword()) {
134
				throw new \InvalidArgumentException('Passwords are enforced for link shares');
135
			}
136
137
			return;
138
		}
139
140
		// Let others verify the password
141
		$accepted = true;
142
		$message = '';
143
		\OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
144
				'password' => $password,
145
				'accepted' => &$accepted,
146
				'message' => &$message
147
		]);
148
149
		if (!$accepted) {
150
			throw new \Exception($message);
151
		}
152
	}
153
154
	/**
155
	 * Check for generic requirements before creating a share
156
	 *
157
	 * @param \OCP\Share\IShare $share
158
	 * @throws \InvalidArgumentException
159
	 * @throws GenericShareException
160
	 */
161
	protected function generalCreateChecks(\OCP\Share\IShare $share) {
162
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
163
			// We expect a valid user as sharedWith for user shares
164
			if (!$this->userManager->userExists($share->getSharedWith())) {
165
				throw new \InvalidArgumentException('SharedWith is not a valid user');
166
			}
167
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
168
			// We expect a valid group as sharedWith for group shares
169
			if (!$this->groupManager->groupExists($share->getSharedWith())) {
170
				throw new \InvalidArgumentException('SharedWith is not a valid group');
171
			}
172
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
173
			if ($share->getSharedWith() !== null) {
174
				throw new \InvalidArgumentException('SharedWith should be empty');
175
			}
176
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
177
			if ($share->getSharedWith() === null) {
178
				throw new \InvalidArgumentException('SharedWith should not be empty');
179
			}
180
		} else {
181
			// We can't handle other types yet
182
			throw new \InvalidArgumentException('unkown share type');
183
		}
184
185
		// Verify the initiator of the share is set
186
		if ($share->getSharedBy() === null) {
187
			throw new \InvalidArgumentException('SharedBy should be set');
188
		}
189
190
		// Cannot share with yourself
191 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...
192
			$share->getSharedWith() === $share->getSharedBy()) {
193
			throw new \InvalidArgumentException('Can\'t share with yourself');
194
		}
195
196
		// The path should be set
197
		if ($share->getNode() === null) {
198
			throw new \InvalidArgumentException('Path should be set');
199
		}
200
201
		// And it should be a file or a folder
202
		if (!($share->getNode() instanceof \OCP\Files\File) &&
203
				!($share->getNode() instanceof \OCP\Files\Folder)) {
204
			throw new \InvalidArgumentException('Path should be either a file or a folder');
205
		}
206
207
		// And you can't share your rootfolder
208
		if ($this->userManager->userExists($share->getSharedBy())) {
209
			$sharedPath = $this->rootFolder->getUserFolder($share->getSharedBy())->getPath();
210
		} else {
211
			$sharedPath = $this->rootFolder->getUserFolder($share->getShareOwner())->getPath();
212
		}
213
		if ($sharedPath === $share->getNode()->getPath()) {
214
			throw new \InvalidArgumentException('You can\'t share your root folder');
215
		}
216
217
		// Check if we actually have share permissions
218 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...
219
			$message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getPath()]);
220
			throw new GenericShareException($message_t, $message_t, 404);
221
		}
222
223
		// Permissions should be set
224
		if ($share->getPermissions() === null) {
225
			throw new \InvalidArgumentException('A share requires permissions');
226
		}
227
228
		/*
229
		 * Quick fix for #23536
230
		 * Non moveable mount points do not have update and delete permissions
231
		 * while we 'most likely' do have that on the storage.
232
		 */
233
		$permissions = $share->getNode()->getPermissions();
234
		$mount = $share->getNode()->getMountPoint();
235
		if (!($mount instanceof MoveableMount)) {
236
			$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
237
		}
238
239
		// Check that we do not share with more permissions than we have
240 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...
241
			$message_t = $this->l->t('Cannot increase permissions of %s', [$share->getNode()->getPath()]);
242
			throw new GenericShareException($message_t, $message_t, 404);
243
		}
244
245
246
		// Check that read permissions are always set
247
		// Link shares are allowed to have no read permissions to allow upload to hidden folders
248
		if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK &&
249
			($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
250
			throw new \InvalidArgumentException('Shares need at least read permissions');
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
				$user = $this->userManager->get($share->getSharedWith());
384
385
				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 383 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...
386
					throw new \Exception('Path already shared with this user');
387
				}
388
			}
389
		}
390
	}
391
392
	/**
393
	 * Check for pre share requirements for group shares
394
	 *
395
	 * @param \OCP\Share\IShare $share
396
	 * @throws \Exception
397
	 */
398
	protected function groupCreateChecks(\OCP\Share\IShare $share) {
399
		// Verify group shares are allowed
400
		if (!$this->allowGroupSharing()) {
401
			throw new \Exception('Group sharing is now allowed');
402
		}
403
404
		// Verify if the user can share with this group
405
		if ($this->shareWithGroupMembersOnly()) {
406
			$sharedBy = $this->userManager->get($share->getSharedBy());
407
			$sharedWith = $this->groupManager->get($share->getSharedWith());
408
			if (!$sharedWith->inGroup($sharedBy)) {
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 406 can be null; however, OCP\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...
409
				throw new \Exception('Only sharing within your own groups is allowed');
410
			}
411
		}
412
413
		/*
414
		 * TODO: Could be costly, fix
415
		 *
416
		 * Also this is not what we want in the future.. then we want to squash identical shares.
417
		 */
418
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
419
		$existingShares = $provider->getSharesByPath($share->getNode());
420
		foreach($existingShares as $existingShare) {
421
			try {
422
				if ($existingShare->getFullId() === $share->getFullId()) {
423
					continue;
424
				}
425
			} catch (\UnexpectedValueException $e) {
426
				//It is a new share so just continue
427
			}
428
429
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
430
				throw new \Exception('Path already shared with this group');
431
			}
432
		}
433
	}
434
435
	/**
436
	 * Check for pre share requirements for link shares
437
	 *
438
	 * @param \OCP\Share\IShare $share
439
	 * @throws \Exception
440
	 */
441
	protected function linkCreateChecks(\OCP\Share\IShare $share) {
442
		// Are link shares allowed?
443
		if (!$this->shareApiAllowLinks()) {
444
			throw new \Exception('Link sharing not allowed');
445
		}
446
447
		// Link shares by definition can't have share permissions
448
		if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) {
449
			throw new \InvalidArgumentException('Link shares can\'t have reshare permissions');
450
		}
451
452
		// We don't allow deletion on link shares
453
		if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
454
			throw new \InvalidArgumentException('Link shares can\'t have delete permissions');
455
		}
456
457
		// Check if public upload is allowed
458
		if (!$this->shareApiLinkAllowPublicUpload() &&
459
			($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE))) {
460
			throw new \InvalidArgumentException('Public upload not allowed');
461
		}
462
	}
463
464
	/**
465
	 * To make sure we don't get invisible link shares we set the parent
466
	 * of a link if it is a reshare. This is a quick word around
467
	 * until we can properly display multiple link shares in the UI
468
	 *
469
	 * See: https://github.com/owncloud/core/issues/22295
470
	 *
471
	 * FIXME: Remove once multiple link shares can be properly displayed
472
	 *
473
	 * @param \OCP\Share\IShare $share
474
	 */
475
	protected function setLinkParent(\OCP\Share\IShare $share) {
476
477
		// No sense in checking if the method is not there.
478
		if (method_exists($share, 'setParent')) {
479
			$storage = $share->getNode()->getStorage();
480
			if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
481
				$share->setParent($storage->getShareId());
482
			}
483
		};
484
	}
485
486
	/**
487
	 * @param File|Folder $path
488
	 */
489
	protected function pathCreateChecks($path) {
490
		// Make sure that we do not share a path that contains a shared mountpoint
491
		if ($path instanceof \OCP\Files\Folder) {
492
			$mounts = $this->mountManager->findIn($path->getPath());
493
			foreach($mounts as $mount) {
494
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
495
					throw new \InvalidArgumentException('Path contains files shared with you');
496
				}
497
			}
498
		}
499
	}
500
501
	/**
502
	 * Check if the user that is sharing can actually share
503
	 *
504
	 * @param \OCP\Share\IShare $share
505
	 * @throws \Exception
506
	 */
507
	protected function canShare(\OCP\Share\IShare $share) {
508
		if (!$this->shareApiEnabled()) {
509
			throw new \Exception('The share API is disabled');
510
		}
511
512
		if ($this->sharingDisabledForUser($share->getSharedBy())) {
513
			throw new \Exception('You are not allowed to share');
514
		}
515
	}
516
517
	/**
518
	 * Share a path
519
	 *
520
	 * @param \OCP\Share\IShare $share
521
	 * @return Share The share object
522
	 * @throws \Exception
523
	 *
524
	 * TODO: handle link share permissions or check them
525
	 */
526
	public function createShare(\OCP\Share\IShare $share) {
527
		$this->canShare($share);
528
529
		$this->generalCreateChecks($share);
530
531
		// Verify if there are any issues with the path
532
		$this->pathCreateChecks($share->getNode());
533
534
		/*
535
		 * On creation of a share the owner is always the owner of the path
536
		 * Except for mounted federated shares.
537
		 */
538
		$storage = $share->getNode()->getStorage();
539
		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
540
			$parent = $share->getNode()->getParent();
541
			while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
542
				$parent = $parent->getParent();
543
			}
544
			$share->setShareOwner($parent->getOwner()->getUID());
545
		} else {
546
			$share->setShareOwner($share->getNode()->getOwner()->getUID());
547
		}
548
549
		//Verify share type
550
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
551
			$this->userCreateChecks($share);
552
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
553
			$this->groupCreateChecks($share);
554
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
555
			$this->linkCreateChecks($share);
556
			$this->setLinkParent($share);
557
558
			/*
559
			 * For now ignore a set token.
560
			 */
561
			$share->setToken(
562
				$this->secureRandom->generate(
563
					\OC\Share\Constants::TOKEN_LENGTH,
564
					\OCP\Security\ISecureRandom::CHAR_LOWER.
565
					\OCP\Security\ISecureRandom::CHAR_UPPER.
566
					\OCP\Security\ISecureRandom::CHAR_DIGITS
567
				)
568
			);
569
570
			//Verify the expiration date
571
			$this->validateExpirationDate($share);
572
573
			//Verify the password
574
			$this->verifyPassword($share->getPassword());
575
576
			// If a password is set. Hash it!
577
			if ($share->getPassword() !== null) {
578
				$share->setPassword($this->hasher->hash($share->getPassword()));
579
			}
580
		}
581
582
		// Cannot share with the owner
583 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...
584
			$share->getSharedWith() === $share->getShareOwner()) {
585
			throw new \InvalidArgumentException('Can\'t share with the share owner');
586
		}
587
588
		// Generate the target
589
		$target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName();
590
		$target = \OC\Files\Filesystem::normalizePath($target);
591
		$share->setTarget($target);
592
593
		// Pre share hook
594
		$run = true;
595
		$error = '';
596
		$preHookData = [
597
			'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
598
			'itemSource' => $share->getNode()->getId(),
599
			'shareType' => $share->getShareType(),
600
			'uidOwner' => $share->getSharedBy(),
601
			'permissions' => $share->getPermissions(),
602
			'fileSource' => $share->getNode()->getId(),
603
			'expiration' => $share->getExpirationDate(),
604
			'token' => $share->getToken(),
605
			'itemTarget' => $share->getTarget(),
606
			'shareWith' => $share->getSharedWith(),
607
			'run' => &$run,
608
			'error' => &$error,
609
		];
610
		\OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
611
612
		if ($run === false) {
613
			throw new \Exception($error);
614
		}
615
616
		$provider = $this->factory->getProviderForType($share->getShareType());
617
		$share = $provider->create($share);
618
619
		// Post share hook
620
		$postHookData = [
621
			'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
622
			'itemSource' => $share->getNode()->getId(),
623
			'shareType' => $share->getShareType(),
624
			'uidOwner' => $share->getSharedBy(),
625
			'permissions' => $share->getPermissions(),
626
			'fileSource' => $share->getNode()->getId(),
627
			'expiration' => $share->getExpirationDate(),
628
			'token' => $share->getToken(),
629
			'id' => $share->getId(),
630
			'shareWith' => $share->getSharedWith(),
631
			'itemTarget' => $share->getTarget(),
632
			'fileTarget' => $share->getTarget(),
633
		];
634
635
		\OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
636
637
		return $share;
638
	}
639
640
	/**
641
	 * Update a share
642
	 *
643
	 * @param \OCP\Share\IShare $share
644
	 * @return \OCP\Share\IShare The share object
645
	 * @throws \InvalidArgumentException
646
	 */
647
	public function updateShare(\OCP\Share\IShare $share) {
648
		$expirationDateUpdated = false;
649
650
		$this->canShare($share);
651
652
		try {
653
			$originalShare = $this->getShareById($share->getFullId());
654
		} catch (\UnexpectedValueException $e) {
655
			throw new \InvalidArgumentException('Share does not have a full id');
656
		}
657
658
		// We can't change the share type!
659
		if ($share->getShareType() !== $originalShare->getShareType()) {
660
			throw new \InvalidArgumentException('Can\'t change share type');
661
		}
662
663
		// We can only change the recipient on user shares
664
		if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
665
		    $share->getShareType() !== \OCP\Share::SHARE_TYPE_USER) {
666
			throw new \InvalidArgumentException('Can only update recipient on user shares');
667
		}
668
669
		// Cannot share with the owner
670 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...
671
			$share->getSharedWith() === $share->getShareOwner()) {
672
			throw new \InvalidArgumentException('Can\'t share with the share owner');
673
		}
674
675
		$this->generalCreateChecks($share);
676
677
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
678
			$this->userCreateChecks($share);
679
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
680
			$this->groupCreateChecks($share);
681
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
682
			$this->linkCreateChecks($share);
683
684
			// Password updated.
685
			if ($share->getPassword() !== $originalShare->getPassword()) {
686
				//Verify the password
687
				$this->verifyPassword($share->getPassword());
688
689
				// If a password is set. Hash it!
690
				if ($share->getPassword() !== null) {
691
					$share->setPassword($this->hasher->hash($share->getPassword()));
692
				}
693
			}
694
695
			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
696
				//Verify the expiration date
697
				$this->validateExpirationDate($share);
698
				$expirationDateUpdated = true;
699
			}
700
		}
701
702
		$this->pathCreateChecks($share->getNode());
703
704
		// Now update the share!
705
		$provider = $this->factory->getProviderForType($share->getShareType());
706
		$share = $provider->update($share);
707
708
		if ($expirationDateUpdated === true) {
709
			\OC_Hook::emit('OCP\Share', 'post_set_expiration_date', [
710
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
711
				'itemSource' => $share->getNode()->getId(),
712
				'date' => $share->getExpirationDate(),
713
				'uidOwner' => $share->getSharedBy(),
714
			]);
715
		}
716
717
		if ($share->getPassword() !== $originalShare->getPassword()) {
718
			\OC_Hook::emit('OCP\Share', 'post_update_password', [
719
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
720
				'itemSource' => $share->getNode()->getId(),
721
				'uidOwner' => $share->getSharedBy(),
722
				'token' => $share->getToken(),
723
				'disabled' => is_null($share->getPassword()),
724
			]);
725
		}
726
727
		if ($share->getPermissions() !== $originalShare->getPermissions()) {
728
			if ($this->userManager->userExists($share->getShareOwner())) {
729
				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
730
			} else {
731
				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
732
			}
733
			\OC_Hook::emit('OCP\Share', 'post_update_permissions', array(
734
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
735
				'itemSource' => $share->getNode()->getId(),
736
				'shareType' => $share->getShareType(),
737
				'shareWith' => $share->getSharedWith(),
738
				'uidOwner' => $share->getSharedBy(),
739
				'permissions' => $share->getPermissions(),
740
				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
741
			));
742
		}
743
744
		return $share;
745
	}
746
747
	/**
748
	 * Delete all the children of this share
749
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
750
	 *
751
	 * @param \OCP\Share\IShare $share
752
	 * @return \OCP\Share\IShare[] List of deleted shares
753
	 */
754
	protected function deleteChildren(\OCP\Share\IShare $share) {
755
		$deletedShares = [];
756
757
		$provider = $this->factory->getProviderForType($share->getShareType());
758
759
		foreach ($provider->getChildren($share) as $child) {
760
			$deletedChildren = $this->deleteChildren($child);
761
			$deletedShares = array_merge($deletedShares, $deletedChildren);
762
763
			$provider->delete($child);
764
			$deletedShares[] = $child;
765
		}
766
767
		return $deletedShares;
768
	}
769
770
	/**
771
	 * Delete a share
772
	 *
773
	 * @param \OCP\Share\IShare $share
774
	 * @throws ShareNotFound
775
	 * @throws \InvalidArgumentException
776
	 */
777
	public function deleteShare(\OCP\Share\IShare $share) {
778
779
		try {
780
			$share->getFullId();
781
		} catch (\UnexpectedValueException $e) {
782
			throw new \InvalidArgumentException('Share does not have a full id');
783
		}
784
785
		$formatHookParams = function(\OCP\Share\IShare $share) {
786
			// Prepare hook
787
			$shareType = $share->getShareType();
788
			$sharedWith = '';
789
			if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
790
				$sharedWith = $share->getSharedWith();
791
			} else if ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
792
				$sharedWith = $share->getSharedWith();
793
			} else if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) {
794
				$sharedWith = $share->getSharedWith();
795
			}
796
797
			$hookParams = [
798
				'id'         => $share->getId(),
799
				'itemType'   => $share->getNodeType(),
800
				'itemSource' => $share->getNodeId(),
801
				'shareType'  => $shareType,
802
				'shareWith'  => $sharedWith,
803
				'itemparent' => method_exists($share, 'getParent') ? $share->getParent() : '',
804
				'uidOwner'   => $share->getSharedBy(),
805
				'fileSource' => $share->getNodeId(),
806
				'fileTarget' => $share->getTarget()
807
			];
808
			return $hookParams;
809
		};
810
811
		$hookParams = $formatHookParams($share);
812
813
		// Emit pre-hook
814
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
815
816
		// Get all children and delete them as well
817
		$deletedShares = $this->deleteChildren($share);
818
819
		// Do the actual delete
820
		$provider = $this->factory->getProviderForType($share->getShareType());
821
		$provider->delete($share);
822
823
		// All the deleted shares caused by this delete
824
		$deletedShares[] = $share;
825
826
		//Format hook info
827
		$formattedDeletedShares = array_map(function($share) use ($formatHookParams) {
828
			return $formatHookParams($share);
829
		}, $deletedShares);
830
831
		$hookParams['deletedShares'] = $formattedDeletedShares;
832
833
		// Emit post hook
834
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
835
	}
836
837
838
	/**
839
	 * Unshare a file as the recipient.
840
	 * This can be different from a regular delete for example when one of
841
	 * the users in a groups deletes that share. But the provider should
842
	 * handle this.
843
	 *
844
	 * @param \OCP\Share\IShare $share
845
	 * @param string $recipientId
846
	 */
847
	public function deleteFromSelf(\OCP\Share\IShare $share, $recipientId) {
848
		list($providerId, ) = $this->splitFullId($share->getFullId());
849
		$provider = $this->factory->getProvider($providerId);
850
851
		$provider->deleteFromSelf($share, $recipientId);
852
	}
853
854
	/**
855
	 * @inheritdoc
856
	 */
857
	public function moveShare(\OCP\Share\IShare $share, $recipientId) {
858
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
859
			throw new \InvalidArgumentException('Can\'t change target of link share');
860
		}
861
862
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && $share->getSharedWith() !== $recipientId) {
863
			throw new \InvalidArgumentException('Invalid recipient');
864
		}
865
866
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
867
			$sharedWith = $this->groupManager->get($share->getSharedWith());
868
			$recipient = $this->userManager->get($recipientId);
869
			if (!$sharedWith->inGroup($recipient)) {
0 ignored issues
show
Bug introduced by
It seems like $recipient defined by $this->userManager->get($recipientId) on line 868 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...
870
				throw new \InvalidArgumentException('Invalid recipient');
871
			}
872
		}
873
874
		list($providerId, ) = $this->splitFullId($share->getFullId());
875
		$provider = $this->factory->getProvider($providerId);
876
877
		$provider->move($share, $recipientId);
878
	}
879
880
	/**
881
	 * @inheritdoc
882
	 */
883
	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
884
		if ($path !== null &&
885
				!($path instanceof \OCP\Files\File) &&
886
				!($path instanceof \OCP\Files\Folder)) {
887
			throw new \InvalidArgumentException('invalid path');
888
		}
889
890
		$provider = $this->factory->getProviderForType($shareType);
891
892
		$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
893
894
		/*
895
		 * Work around so we don't return expired shares but still follow
896
		 * proper pagination.
897
		 */
898
		if ($shareType === \OCP\Share::SHARE_TYPE_LINK) {
899
			$shares2 = [];
900
			$today = new \DateTime();
901
902
			while(true) {
903
				$added = 0;
904
				foreach ($shares as $share) {
905
					// Check if the share is expired and if so delete it
906
					if ($share->getExpirationDate() !== null &&
907
						$share->getExpirationDate() <= $today
908
					) {
909
						try {
910
							$this->deleteShare($share);
911
						} catch (NotFoundException $e) {
912
							//Ignore since this basically means the share is deleted
913
						}
914
						continue;
915
					}
916
					$added++;
917
					$shares2[] = $share;
918
919
					if (count($shares2) === $limit) {
920
						break;
921
					}
922
				}
923
924
				if (count($shares2) === $limit) {
925
					break;
926
				}
927
928
				// If there was no limit on the select we are done
929
				if ($limit === -1) {
930
					break;
931
				}
932
933
				$offset += $added;
934
935
				// Fetch again $limit shares
936
				$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
937
938
				// No more shares means we are done
939
				if (empty($shares)) {
940
					break;
941
				}
942
			}
943
944
			$shares = $shares2;
945
		}
946
947
		return $shares;
948
	}
949
950
	/**
951
	 * @inheritdoc
952
	 */
953
	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
954
		$provider = $this->factory->getProviderForType($shareType);
955
956
		return $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
957
	}
958
959
	/**
960
	 * @inheritdoc
961
	 */
962
	public function getShareById($id, $recipient = null) {
963
		if ($id === null) {
964
			throw new ShareNotFound();
965
		}
966
967
		list($providerId, $id) = $this->splitFullId($id);
968
		$provider = $this->factory->getProvider($providerId);
969
970
		$share = $provider->getShareById($id, $recipient);
971
972
		// Validate link shares expiration date
973
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
974
			$share->getExpirationDate() !== null &&
975
			$share->getExpirationDate() <= new \DateTime()) {
976
			$this->deleteShare($share);
977
			throw new ShareNotFound();
978
		}
979
980
		return $share;
981
	}
982
983
	/**
984
	 * Get all the shares for a given path
985
	 *
986
	 * @param \OCP\Files\Node $path
987
	 * @param int $page
988
	 * @param int $perPage
989
	 *
990
	 * @return Share[]
991
	 */
992
	public function getSharesByPath(\OCP\Files\Node $path, $page=0, $perPage=50) {
0 ignored issues
show
Unused Code introduced by
The parameter $path is not used and could be removed.

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

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

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

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

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

Loading history...
993
	}
994
995
	/**
996
	 * Get the share by token possible with password
997
	 *
998
	 * @param string $token
999
	 * @return Share
1000
	 *
1001
	 * @throws ShareNotFound
1002
	 */
1003
	public function getShareByToken($token) {
1004
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_LINK);
1005
1006
		try {
1007
			$share = $provider->getShareByToken($token);
1008
		} catch (ShareNotFound $e) {
1009
			$share = null;
1010
		}
1011
1012
		// If it is not a link share try to fetch a federated share by token
1013
		if ($share === null) {
1014
			$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_REMOTE);
1015
			$share = $provider->getShareByToken($token);
1016
		}
1017
1018
		if ($share->getExpirationDate() !== null &&
1019
			$share->getExpirationDate() <= new \DateTime()) {
1020
			$this->deleteShare($share);
1021
			throw new ShareNotFound();
1022
		}
1023
1024
		/*
1025
		 * Reduce the permissions for link shares if public upload is not enabled
1026
		 */
1027
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1028
			!$this->shareApiLinkAllowPublicUpload()) {
1029
			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1030
		}
1031
1032
		return $share;
1033
	}
1034
1035
	/**
1036
	 * Verify the password of a public share
1037
	 *
1038
	 * @param \OCP\Share\IShare $share
1039
	 * @param string $password
1040
	 * @return bool
1041
	 */
1042
	public function checkPassword(\OCP\Share\IShare $share, $password) {
1043
		if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK) {
1044
			//TODO maybe exception?
1045
			return false;
1046
		}
1047
1048
		if ($password === null || $share->getPassword() === null) {
1049
			return false;
1050
		}
1051
1052
		$newHash = '';
1053
		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1054
			return false;
1055
		}
1056
1057
		if (!empty($newHash)) {
1058
			$share->setPassword($newHash);
1059
			$provider = $this->factory->getProviderForType($share->getShareType());
1060
			$provider->update($share);
1061
		}
1062
1063
		return true;
1064
	}
1065
1066
	/**
1067
	 * @inheritdoc
1068
	 */
1069
	public function userDeleted($uid) {
1070
		$types = [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE];
1071
1072
		foreach ($types as $type) {
1073
			$provider = $this->factory->getProviderForType($type);
1074
			$provider->userDeleted($uid, $type);
1075
		}
1076
	}
1077
1078
	/**
1079
	 * @inheritdoc
1080
	 */
1081
	public function groupDeleted($gid) {
1082
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1083
		$provider->groupDeleted($gid);
1084
	}
1085
1086
	/**
1087
	 * @inheritdoc
1088
	 */
1089
	public function userDeletedFromGroup($uid, $gid) {
1090
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1091
		$provider->userDeletedFromGroup($uid, $gid);
1092
	}
1093
1094
	/**
1095
	 * Get access list to a path. This means
1096
	 * all the users and groups that can access a given path.
1097
	 *
1098
	 * Consider:
1099
	 * -root
1100
	 * |-folder1
1101
	 *  |-folder2
1102
	 *   |-fileA
1103
	 *
1104
	 * fileA is shared with user1
1105
	 * folder2 is shared with group2
1106
	 * folder1 is shared with user2
1107
	 *
1108
	 * Then the access list will to '/folder1/folder2/fileA' is:
1109
	 * [
1110
	 * 	'users' => ['user1', 'user2'],
1111
	 *  'groups' => ['group2']
1112
	 * ]
1113
	 *
1114
	 * This is required for encryption
1115
	 *
1116
	 * @param \OCP\Files\Node $path
1117
	 */
1118
	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...
1119
	}
1120
1121
	/**
1122
	 * Create a new share
1123
	 * @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...
1124
	 */
1125
	public function newShare() {
1126
		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1127
	}
1128
1129
	/**
1130
	 * Is the share API enabled
1131
	 *
1132
	 * @return bool
1133
	 */
1134
	public function shareApiEnabled() {
1135
		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1136
	}
1137
1138
	/**
1139
	 * Is public link sharing enabled
1140
	 *
1141
	 * @return bool
1142
	 */
1143
	public function shareApiAllowLinks() {
1144
		return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
1145
	}
1146
1147
	/**
1148
	 * Is password on public link requires
1149
	 *
1150
	 * @return bool
1151
	 */
1152
	public function shareApiLinkEnforcePassword() {
1153
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1154
	}
1155
1156
	/**
1157
	 * Is default expire date enabled
1158
	 *
1159
	 * @return bool
1160
	 */
1161
	public function shareApiLinkDefaultExpireDate() {
1162
		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1163
	}
1164
1165
	/**
1166
	 * Is default expire date enforced
1167
	 *`
1168
	 * @return bool
1169
	 */
1170
	public function shareApiLinkDefaultExpireDateEnforced() {
1171
		return $this->shareApiLinkDefaultExpireDate() &&
1172
			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1173
	}
1174
1175
	/**
1176
	 * Number of default expire days
1177
	 *shareApiLinkAllowPublicUpload
1178
	 * @return int
1179
	 */
1180
	public function shareApiLinkDefaultExpireDays() {
1181
		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1182
	}
1183
1184
	/**
1185
	 * Allow public upload on link shares
1186
	 *
1187
	 * @return bool
1188
	 */
1189
	public function shareApiLinkAllowPublicUpload() {
1190
		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1191
	}
1192
1193
	/**
1194
	 * check if user can only share with group members
1195
	 * @return bool
1196
	 */
1197
	public function shareWithGroupMembersOnly() {
1198
		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1199
	}
1200
1201
	/**
1202
	 * Check if users can share with groups
1203
	 * @return bool
1204
	 */
1205
	public function allowGroupSharing() {
1206
		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1207
	}
1208
1209
	/**
1210
	 * Copied from \OC_Util::isSharingDisabledForUser
1211
	 *
1212
	 * TODO: Deprecate fuction from OC_Util
1213
	 *
1214
	 * @param string $userId
1215
	 * @return bool
1216
	 */
1217
	public function sharingDisabledForUser($userId) {
1218
		if ($userId === null) {
1219
			return false;
1220
		}
1221
1222
		if (isset($this->sharingDisabledForUsersCache[$userId])) {
1223
			return $this->sharingDisabledForUsersCache[$userId];
1224
		}
1225
1226
		if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
1227
			$groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1228
			$excludedGroups = json_decode($groupsList);
1229 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...
1230
				$excludedGroups = explode(',', $groupsList);
1231
				$newValue = json_encode($excludedGroups);
1232
				$this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
1233
			}
1234
			$user = $this->userManager->get($userId);
1235
			$usersGroups = $this->groupManager->getUserGroupIds($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($userId) on line 1234 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...
1236 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...
1237
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
1238
				// if the user is only in groups which are disabled for sharing then
1239
				// sharing is also disabled for the user
1240
				if (empty($remainingGroups)) {
1241
					$this->sharingDisabledForUsersCache[$userId] = true;
1242
					return true;
1243
				}
1244
			}
1245
		}
1246
1247
		$this->sharingDisabledForUsersCache[$userId] = false;
1248
		return false;
1249
	}
1250
1251
	/**
1252
	 * @inheritdoc
1253
	 */
1254
	public function outgoingServer2ServerSharesAllowed() {
1255
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1256
	}
1257
1258
}
1259