Completed
Push — master ( a89f9a...3036b1 )
by Morris
29:19 queued 12:13
created

Manager::outgoingServer2ServerGroupSharesAllowed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Daniel Calviño Sánchez <[email protected]>
9
 * @author Jan-Christoph Borchardt <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Julius Härtl <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Maxence Lange <[email protected]>
14
 * @author Maxence Lange <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Pauli Järvinen <[email protected]>
17
 * @author Robin Appelman <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Stephan Müller <[email protected]>
20
 * @author Vincent Petry <[email protected]>
21
 *
22
 * @license AGPL-3.0
23
 *
24
 * This code is free software: you can redistribute it and/or modify
25
 * it under the terms of the GNU Affero General Public License, version 3,
26
 * as published by the Free Software Foundation.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License, version 3,
34
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
35
 *
36
 */
37
38
namespace OC\Share20;
39
40
use OC\Cache\CappedMemoryCache;
41
use OC\Files\Mount\MoveableMount;
42
use OC\HintException;
43
use OC\Share20\Exception\ProviderException;
44
use OCP\Files\File;
45
use OCP\Files\Folder;
46
use OCP\Files\IRootFolder;
47
use OCP\Files\Mount\IMountManager;
48
use OCP\Files\Node;
49
use OCP\IConfig;
50
use OCP\IGroupManager;
51
use OCP\IL10N;
52
use OCP\ILogger;
53
use OCP\IURLGenerator;
54
use OCP\IUser;
55
use OCP\IUserManager;
56
use OCP\L10N\IFactory;
57
use OCP\Mail\IMailer;
58
use OCP\Security\IHasher;
59
use OCP\Security\ISecureRandom;
60
use OCP\Share\Exceptions\GenericShareException;
61
use OCP\Share\Exceptions\ShareNotFound;
62
use OCP\Share\IManager;
63
use OCP\Share\IProviderFactory;
64
use OCP\Share\IShare;
65
use Symfony\Component\EventDispatcher\EventDispatcher;
66
use Symfony\Component\EventDispatcher\GenericEvent;
67
use OCP\Share\IShareProvider;
68
use OCP\Share;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, OC\Share20\Share.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
69
70
/**
71
 * This class is the communication hub for all sharing related operations.
72
 */
73
class Manager implements IManager {
74
75
	/** @var IProviderFactory */
76
	private $factory;
77
	/** @var ILogger */
78
	private $logger;
79
	/** @var IConfig */
80
	private $config;
81
	/** @var ISecureRandom */
82
	private $secureRandom;
83
	/** @var IHasher */
84
	private $hasher;
85
	/** @var IMountManager */
86
	private $mountManager;
87
	/** @var IGroupManager */
88
	private $groupManager;
89
	/** @var IL10N */
90
	private $l;
91
	/** @var IFactory */
92
	private $l10nFactory;
93
	/** @var IUserManager */
94
	private $userManager;
95
	/** @var IRootFolder */
96
	private $rootFolder;
97
	/** @var CappedMemoryCache */
98
	private $sharingDisabledForUsersCache;
99
	/** @var EventDispatcher */
100
	private $eventDispatcher;
101
	/** @var LegacyHooks */
102
	private $legacyHooks;
103
	/** @var IMailer */
104
	private $mailer;
105
	/** @var IURLGenerator */
106
	private $urlGenerator;
107
	/** @var \OC_Defaults */
108
	private $defaults;
109
110
111
	/**
112
	 * Manager constructor.
113
	 *
114
	 * @param ILogger $logger
115
	 * @param IConfig $config
116
	 * @param ISecureRandom $secureRandom
117
	 * @param IHasher $hasher
118
	 * @param IMountManager $mountManager
119
	 * @param IGroupManager $groupManager
120
	 * @param IL10N $l
121
	 * @param IFactory $l10nFactory
122
	 * @param IProviderFactory $factory
123
	 * @param IUserManager $userManager
124
	 * @param IRootFolder $rootFolder
125
	 * @param EventDispatcher $eventDispatcher
126
	 * @param IMailer $mailer
127
	 * @param IURLGenerator $urlGenerator
128
	 * @param \OC_Defaults $defaults
129
	 */
130
	public function __construct(
131
			ILogger $logger,
132
			IConfig $config,
133
			ISecureRandom $secureRandom,
134
			IHasher $hasher,
135
			IMountManager $mountManager,
136
			IGroupManager $groupManager,
137
			IL10N $l,
138
			IFactory $l10nFactory,
139
			IProviderFactory $factory,
140
			IUserManager $userManager,
141
			IRootFolder $rootFolder,
142
			EventDispatcher $eventDispatcher,
143
			IMailer $mailer,
144
			IURLGenerator $urlGenerator,
145
			\OC_Defaults $defaults
146
	) {
147
		$this->logger = $logger;
148
		$this->config = $config;
149
		$this->secureRandom = $secureRandom;
150
		$this->hasher = $hasher;
151
		$this->mountManager = $mountManager;
152
		$this->groupManager = $groupManager;
153
		$this->l = $l;
154
		$this->l10nFactory = $l10nFactory;
155
		$this->factory = $factory;
156
		$this->userManager = $userManager;
157
		$this->rootFolder = $rootFolder;
158
		$this->eventDispatcher = $eventDispatcher;
159
		$this->sharingDisabledForUsersCache = new CappedMemoryCache();
160
		$this->legacyHooks = new LegacyHooks($this->eventDispatcher);
161
		$this->mailer = $mailer;
162
		$this->urlGenerator = $urlGenerator;
163
		$this->defaults = $defaults;
164
	}
165
166
	/**
167
	 * Convert from a full share id to a tuple (providerId, shareId)
168
	 *
169
	 * @param string $id
170
	 * @return string[]
171
	 */
172
	private function splitFullId($id) {
173
		return explode(':', $id, 2);
174
	}
175
176
	/**
177
	 * Verify if a password meets all requirements
178
	 *
179
	 * @param string $password
180
	 * @throws \Exception
181
	 */
182
	protected function verifyPassword($password) {
183
		if ($password === null) {
184
			// No password is set, check if this is allowed.
185
			if ($this->shareApiLinkEnforcePassword()) {
186
				throw new \InvalidArgumentException('Passwords are enforced for link shares');
187
			}
188
189
			return;
190
		}
191
192
		// Let others verify the password
193
		try {
194
			$event = new GenericEvent($password);
195
			$this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
196
		} catch (HintException $e) {
197
			throw new \Exception($e->getHint());
198
		}
199
	}
200
201
	/**
202
	 * Check for generic requirements before creating a share
203
	 *
204
	 * @param \OCP\Share\IShare $share
205
	 * @throws \InvalidArgumentException
206
	 * @throws GenericShareException
207
	 *
208
	 * @suppress PhanUndeclaredClassMethod
209
	 */
210
	protected function generalCreateChecks(\OCP\Share\IShare $share) {
211
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
212
			// We expect a valid user as sharedWith for user shares
213
			if (!$this->userManager->userExists($share->getSharedWith())) {
214
				throw new \InvalidArgumentException('SharedWith is not a valid user');
215
			}
216
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
217
			// We expect a valid group as sharedWith for group shares
218
			if (!$this->groupManager->groupExists($share->getSharedWith())) {
219
				throw new \InvalidArgumentException('SharedWith is not a valid group');
220
			}
221
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
222
			if ($share->getSharedWith() !== null) {
223
				throw new \InvalidArgumentException('SharedWith should be empty');
224
			}
225
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
226
			if ($share->getSharedWith() === null) {
227
				throw new \InvalidArgumentException('SharedWith should not be empty');
228
			}
229
		}  else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE_GROUP) {
230
			if ($share->getSharedWith() === null) {
231
				throw new \InvalidArgumentException('SharedWith should not be empty');
232
			}
233
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
234
			if ($share->getSharedWith() === null) {
235
				throw new \InvalidArgumentException('SharedWith should not be empty');
236
			}
237
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_CIRCLE) {
238
			$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith());
239
			if ($circle === null) {
240
				throw new \InvalidArgumentException('SharedWith is not a valid circle');
241
			}
242
		} else {
243
			// We can't handle other types yet
244
			throw new \InvalidArgumentException('unknown share type');
245
		}
246
247
		// Verify the initiator of the share is set
248
		if ($share->getSharedBy() === null) {
249
			throw new \InvalidArgumentException('SharedBy should be set');
250
		}
251
252
		// Cannot share with yourself
253 View Code Duplication
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
254
			$share->getSharedWith() === $share->getSharedBy()) {
255
			throw new \InvalidArgumentException('Can’t share with yourself');
256
		}
257
258
		// The path should be set
259
		if ($share->getNode() === null) {
260
			throw new \InvalidArgumentException('Path should be set');
261
		}
262
263
		// And it should be a file or a folder
264
		if (!($share->getNode() instanceof \OCP\Files\File) &&
265
				!($share->getNode() instanceof \OCP\Files\Folder)) {
266
			throw new \InvalidArgumentException('Path should be either a file or a folder');
267
		}
268
269
		// And you can't share your rootfolder
270
		if ($this->userManager->userExists($share->getSharedBy())) {
271
			$sharedPath = $this->rootFolder->getUserFolder($share->getSharedBy())->getPath();
272
		} else {
273
			$sharedPath = $this->rootFolder->getUserFolder($share->getShareOwner())->getPath();
274
		}
275
		if ($sharedPath === $share->getNode()->getPath()) {
276
			throw new \InvalidArgumentException('You can’t share your root folder');
277
		}
278
279
		// Check if we actually have share permissions
280 View Code Duplication
		if (!$share->getNode()->isShareable()) {
281
			$message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getPath()]);
282
			throw new GenericShareException($message_t, $message_t, 404);
283
		}
284
285
		// Permissions should be set
286
		if ($share->getPermissions() === null) {
287
			throw new \InvalidArgumentException('A share requires permissions');
288
		}
289
290
		/*
291
		 * Quick fix for #23536
292
		 * Non moveable mount points do not have update and delete permissions
293
		 * while we 'most likely' do have that on the storage.
294
		 */
295
		$permissions = $share->getNode()->getPermissions();
296
		$mount = $share->getNode()->getMountPoint();
297
		if (!($mount instanceof MoveableMount)) {
298
			$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
299
		}
300
301
		// Check that we do not share with more permissions than we have
302 View Code Duplication
		if ($share->getPermissions() & ~$permissions) {
303
			$message_t = $this->l->t('Can’t increase permissions of %s', [$share->getNode()->getPath()]);
304
			throw new GenericShareException($message_t, $message_t, 404);
305
		}
306
307
308
		// Check that read permissions are always set
309
		// Link shares are allowed to have no read permissions to allow upload to hidden folders
310
		$noReadPermissionRequired = $share->getShareType() === \OCP\Share::SHARE_TYPE_LINK
311
			|| $share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL;
312
		if (!$noReadPermissionRequired &&
313
			($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
314
			throw new \InvalidArgumentException('Shares need at least read permissions');
315
		}
316
317
		if ($share->getNode() instanceof \OCP\Files\File) {
318 View Code Duplication
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
319
				$message_t = $this->l->t('Files can’t be shared with delete permissions');
320
				throw new GenericShareException($message_t);
321
			}
322 View Code Duplication
			if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
323
				$message_t = $this->l->t('Files can’t be shared with create permissions');
324
				throw new GenericShareException($message_t);
325
			}
326
		}
327
	}
328
329
	/**
330
	 * Validate if the expiration date fits the system settings
331
	 *
332
	 * @param \OCP\Share\IShare $share The share to validate the expiration date of
333
	 * @return \OCP\Share\IShare The modified share object
334
	 * @throws GenericShareException
335
	 * @throws \InvalidArgumentException
336
	 * @throws \Exception
337
	 */
338
	protected function validateExpirationDate(\OCP\Share\IShare $share) {
339
340
		$expirationDate = $share->getExpirationDate();
341
342
		if ($expirationDate !== null) {
343
			//Make sure the expiration date is a date
344
			$expirationDate->setTime(0, 0, 0);
345
346
			$date = new \DateTime();
347
			$date->setTime(0, 0, 0);
348 View Code Duplication
			if ($date >= $expirationDate) {
349
				$message = $this->l->t('Expiration date is in the past');
350
				throw new GenericShareException($message, $message, 404);
351
			}
352
		}
353
354
		// If expiredate is empty set a default one if there is a default
355
		$fullId = null;
356
		try {
357
			$fullId = $share->getFullId();
358
		} catch (\UnexpectedValueException $e) {
359
			// This is a new share
360
		}
361
362
		if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
363
			$expirationDate = new \DateTime();
364
			$expirationDate->setTime(0,0,0);
365
			$expirationDate->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
366
		}
367
368
		// If we enforce the expiration date check that is does not exceed
369
		if ($this->shareApiLinkDefaultExpireDateEnforced()) {
370
			if ($expirationDate === null) {
371
				throw new \InvalidArgumentException('Expiration date is enforced');
372
			}
373
374
			$date = new \DateTime();
375
			$date->setTime(0, 0, 0);
376
			$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
377 View Code Duplication
			if ($date < $expirationDate) {
378
				$message = $this->l->t('Can’t set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
379
				throw new GenericShareException($message, $message, 404);
380
			}
381
		}
382
383
		$accepted = true;
384
		$message = '';
385
		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
386
			'expirationDate' => &$expirationDate,
387
			'accepted' => &$accepted,
388
			'message' => &$message,
389
			'passwordSet' => $share->getPassword() !== null,
390
		]);
391
392
		if (!$accepted) {
393
			throw new \Exception($message);
394
		}
395
396
		$share->setExpirationDate($expirationDate);
397
398
		return $share;
399
	}
400
401
	/**
402
	 * Check for pre share requirements for user shares
403
	 *
404
	 * @param \OCP\Share\IShare $share
405
	 * @throws \Exception
406
	 */
407
	protected function userCreateChecks(\OCP\Share\IShare $share) {
408
		// Check if we can share with group members only
409
		if ($this->shareWithGroupMembersOnly()) {
410
			$sharedBy = $this->userManager->get($share->getSharedBy());
411
			$sharedWith = $this->userManager->get($share->getSharedWith());
412
			// Verify we can share with this user
413
			$groups = array_intersect(
414
					$this->groupManager->getUserGroupIds($sharedBy),
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 410 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...
415
					$this->groupManager->getUserGroupIds($sharedWith)
0 ignored issues
show
Bug introduced by
It seems like $sharedWith defined by $this->userManager->get($share->getSharedWith()) on line 411 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...
416
			);
417
			if (empty($groups)) {
418
				throw new \Exception('Sharing is only allowed with group members');
419
			}
420
		}
421
422
		/*
423
		 * TODO: Could be costly, fix
424
		 *
425
		 * Also this is not what we want in the future.. then we want to squash identical shares.
426
		 */
427
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_USER);
428
		$existingShares = $provider->getSharesByPath($share->getNode());
429
		foreach($existingShares as $existingShare) {
430
			// Ignore if it is the same share
431
			try {
432
				if ($existingShare->getFullId() === $share->getFullId()) {
433
					continue;
434
				}
435
			} catch (\UnexpectedValueException $e) {
436
				//Shares are not identical
437
			}
438
439
			// Identical share already existst
440
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
441
				throw new \Exception('Path is already shared with this user');
442
			}
443
444
			// The share is already shared with this user via a group share
445
			if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
446
				$group = $this->groupManager->get($existingShare->getSharedWith());
447
				if (!is_null($group)) {
448
					$user = $this->userManager->get($share->getSharedWith());
449
450
					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 448 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...
451
						throw new \Exception('Path is already shared with this user');
452
					}
453
				}
454
			}
455
		}
456
	}
457
458
	/**
459
	 * Check for pre share requirements for group shares
460
	 *
461
	 * @param \OCP\Share\IShare $share
462
	 * @throws \Exception
463
	 */
464
	protected function groupCreateChecks(\OCP\Share\IShare $share) {
465
		// Verify group shares are allowed
466
		if (!$this->allowGroupSharing()) {
467
			throw new \Exception('Group sharing is now allowed');
468
		}
469
470
		// Verify if the user can share with this group
471
		if ($this->shareWithGroupMembersOnly()) {
472
			$sharedBy = $this->userManager->get($share->getSharedBy());
473
			$sharedWith = $this->groupManager->get($share->getSharedWith());
474
			if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) {
0 ignored issues
show
Bug introduced by
It seems like $sharedBy defined by $this->userManager->get($share->getSharedBy()) on line 472 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...
475
				throw new \Exception('Sharing is only allowed within your own groups');
476
			}
477
		}
478
479
		/*
480
		 * TODO: Could be costly, fix
481
		 *
482
		 * Also this is not what we want in the future.. then we want to squash identical shares.
483
		 */
484
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
485
		$existingShares = $provider->getSharesByPath($share->getNode());
486
		foreach($existingShares as $existingShare) {
487
			try {
488
				if ($existingShare->getFullId() === $share->getFullId()) {
489
					continue;
490
				}
491
			} catch (\UnexpectedValueException $e) {
492
				//It is a new share so just continue
493
			}
494
495
			if ($existingShare->getSharedWith() === $share->getSharedWith()) {
496
				throw new \Exception('Path is already shared with this group');
497
			}
498
		}
499
	}
500
501
	/**
502
	 * Check for pre share requirements for link shares
503
	 *
504
	 * @param \OCP\Share\IShare $share
505
	 * @throws \Exception
506
	 */
507
	protected function linkCreateChecks(\OCP\Share\IShare $share) {
508
		// Are link shares allowed?
509
		if (!$this->shareApiAllowLinks()) {
510
			throw new \Exception('Link sharing is not allowed');
511
		}
512
513
		// Link shares by definition can't have share permissions
514
		if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) {
515
			throw new \InvalidArgumentException('Link shares can’t have reshare permissions');
516
		}
517
518
		// Check if public upload is allowed
519
		if (!$this->shareApiLinkAllowPublicUpload() &&
520
			($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
521
			throw new \InvalidArgumentException('Public upload is not allowed');
522
		}
523
	}
524
525
	/**
526
	 * To make sure we don't get invisible link shares we set the parent
527
	 * of a link if it is a reshare. This is a quick word around
528
	 * until we can properly display multiple link shares in the UI
529
	 *
530
	 * See: https://github.com/owncloud/core/issues/22295
531
	 *
532
	 * FIXME: Remove once multiple link shares can be properly displayed
533
	 *
534
	 * @param \OCP\Share\IShare $share
535
	 */
536
	protected function setLinkParent(\OCP\Share\IShare $share) {
537
538
		// No sense in checking if the method is not there.
539
		if (method_exists($share, 'setParent')) {
540
			$storage = $share->getNode()->getStorage();
541
			if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
542
				/** @var \OCA\Files_Sharing\SharedStorage $storage */
543
				$share->setParent($storage->getShareId());
544
			}
545
		}
546
	}
547
548
	/**
549
	 * @param File|Folder $path
550
	 */
551
	protected function pathCreateChecks($path) {
552
		// Make sure that we do not share a path that contains a shared mountpoint
553
		if ($path instanceof \OCP\Files\Folder) {
554
			$mounts = $this->mountManager->findIn($path->getPath());
555
			foreach($mounts as $mount) {
556
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
557
					throw new \InvalidArgumentException('Path contains files shared with you');
558
				}
559
			}
560
		}
561
	}
562
563
	/**
564
	 * Check if the user that is sharing can actually share
565
	 *
566
	 * @param \OCP\Share\IShare $share
567
	 * @throws \Exception
568
	 */
569
	protected function canShare(\OCP\Share\IShare $share) {
570
		if (!$this->shareApiEnabled()) {
571
			throw new \Exception('Sharing is disabled');
572
		}
573
574
		if ($this->sharingDisabledForUser($share->getSharedBy())) {
575
			throw new \Exception('Sharing is disabled for you');
576
		}
577
	}
578
579
	/**
580
	 * Share a path
581
	 *
582
	 * @param \OCP\Share\IShare $share
583
	 * @return Share The share object
584
	 * @throws \Exception
585
	 *
586
	 * TODO: handle link share permissions or check them
587
	 */
588
	public function createShare(\OCP\Share\IShare $share) {
589
		$this->canShare($share);
590
591
		$this->generalCreateChecks($share);
592
593
		// Verify if there are any issues with the path
594
		$this->pathCreateChecks($share->getNode());
595
596
		/*
597
		 * On creation of a share the owner is always the owner of the path
598
		 * Except for mounted federated shares.
599
		 */
600
		$storage = $share->getNode()->getStorage();
601
		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
602
			$parent = $share->getNode()->getParent();
603
			while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
604
				$parent = $parent->getParent();
605
			}
606
			$share->setShareOwner($parent->getOwner()->getUID());
607
		} else {
608
			$share->setShareOwner($share->getNode()->getOwner()->getUID());
609
		}
610
611
		//Verify share type
612
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
613
			$this->userCreateChecks($share);
614
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
615
			$this->groupCreateChecks($share);
616
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
617
			$this->linkCreateChecks($share);
618
			$this->setLinkParent($share);
619
620
			/*
621
			 * For now ignore a set token.
622
			 */
623
			$share->setToken(
624
				$this->secureRandom->generate(
625
					\OC\Share\Constants::TOKEN_LENGTH,
626
					\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
627
				)
628
			);
629
630
			//Verify the expiration date
631
			$this->validateExpirationDate($share);
632
633
			//Verify the password
634
			$this->verifyPassword($share->getPassword());
635
636
			// If a password is set. Hash it!
637
			if ($share->getPassword() !== null) {
638
				$share->setPassword($this->hasher->hash($share->getPassword()));
639
			}
640
		} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
641
			$share->setToken(
642
				$this->secureRandom->generate(
643
					\OC\Share\Constants::TOKEN_LENGTH,
644
					\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
645
				)
646
			);
647
		}
648
649
		// Cannot share with the owner
650 View Code Duplication
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
651
			$share->getSharedWith() === $share->getShareOwner()) {
652
			throw new \InvalidArgumentException('Can’t share with the share owner');
653
		}
654
655
		// Generate the target
656
		$target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName();
657
		$target = \OC\Files\Filesystem::normalizePath($target);
658
		$share->setTarget($target);
659
660
		// Pre share event
661
		$event = new GenericEvent($share);
662
		$a = $this->eventDispatcher->dispatch('OCP\Share::preShare', $event);
0 ignored issues
show
Unused Code introduced by
$a is not used, you could remove the assignment.

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

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

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
842
		} else {
843
			$share = $provider->update($share);
844
		}
845
846
		if ($expirationDateUpdated === true) {
847
			\OC_Hook::emit(Share::class, 'post_set_expiration_date', [
848
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
849
				'itemSource' => $share->getNode()->getId(),
850
				'date' => $share->getExpirationDate(),
851
				'uidOwner' => $share->getSharedBy(),
852
			]);
853
		}
854
855
		if ($share->getPassword() !== $originalShare->getPassword()) {
856
			\OC_Hook::emit(Share::class, 'post_update_password', [
857
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
858
				'itemSource' => $share->getNode()->getId(),
859
				'uidOwner' => $share->getSharedBy(),
860
				'token' => $share->getToken(),
861
				'disabled' => is_null($share->getPassword()),
862
			]);
863
		}
864
865
		if ($share->getPermissions() !== $originalShare->getPermissions()) {
866
			if ($this->userManager->userExists($share->getShareOwner())) {
867
				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
868
			} else {
869
				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
870
			}
871
			\OC_Hook::emit(Share::class, 'post_update_permissions', array(
872
				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
873
				'itemSource' => $share->getNode()->getId(),
874
				'shareType' => $share->getShareType(),
875
				'shareWith' => $share->getSharedWith(),
876
				'uidOwner' => $share->getSharedBy(),
877
				'permissions' => $share->getPermissions(),
878
				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
879
			));
880
		}
881
882
		return $share;
883
	}
884
885
	/**
886
	 * Updates the password of the given share if it is not the same as the
887
	 * password of the original share.
888
	 *
889
	 * @param \OCP\Share\IShare $share the share to update its password.
890
	 * @param \OCP\Share\IShare $originalShare the original share to compare its
891
	 *        password with.
892
	 * @return boolean whether the password was updated or not.
893
	 */
894
	private function updateSharePasswordIfNeeded(\OCP\Share\IShare $share, \OCP\Share\IShare $originalShare) {
895
		// Password updated.
896
		if ($share->getPassword() !== $originalShare->getPassword()) {
897
			//Verify the password
898
			$this->verifyPassword($share->getPassword());
899
900
			// If a password is set. Hash it!
901
			if ($share->getPassword() !== null) {
902
				$share->setPassword($this->hasher->hash($share->getPassword()));
903
904
				return true;
905
			}
906
		}
907
908
		return false;
909
	}
910
911
	/**
912
	 * Delete all the children of this share
913
	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
914
	 *
915
	 * @param \OCP\Share\IShare $share
916
	 * @return \OCP\Share\IShare[] List of deleted shares
917
	 */
918
	protected function deleteChildren(\OCP\Share\IShare $share) {
919
		$deletedShares = [];
920
921
		$provider = $this->factory->getProviderForType($share->getShareType());
922
923
		foreach ($provider->getChildren($share) as $child) {
924
			$deletedChildren = $this->deleteChildren($child);
925
			$deletedShares = array_merge($deletedShares, $deletedChildren);
926
927
			$provider->delete($child);
928
			$deletedShares[] = $child;
929
		}
930
931
		return $deletedShares;
932
	}
933
934
	/**
935
	 * Delete a share
936
	 *
937
	 * @param \OCP\Share\IShare $share
938
	 * @throws ShareNotFound
939
	 * @throws \InvalidArgumentException
940
	 */
941
	public function deleteShare(\OCP\Share\IShare $share) {
942
943
		try {
944
			$share->getFullId();
945
		} catch (\UnexpectedValueException $e) {
946
			throw new \InvalidArgumentException('Share does not have a full id');
947
		}
948
949
		$event = new GenericEvent($share);
950
		$this->eventDispatcher->dispatch('OCP\Share::preUnshare', $event);
951
952
		// Get all children and delete them as well
953
		$deletedShares = $this->deleteChildren($share);
954
955
		// Do the actual delete
956
		$provider = $this->factory->getProviderForType($share->getShareType());
957
		$provider->delete($share);
958
959
		// All the deleted shares caused by this delete
960
		$deletedShares[] = $share;
961
962
		// Emit post hook
963
		$event->setArgument('deletedShares', $deletedShares);
964
		$this->eventDispatcher->dispatch('OCP\Share::postUnshare', $event);
965
	}
966
967
968
	/**
969
	 * Unshare a file as the recipient.
970
	 * This can be different from a regular delete for example when one of
971
	 * the users in a groups deletes that share. But the provider should
972
	 * handle this.
973
	 *
974
	 * @param \OCP\Share\IShare $share
975
	 * @param string $recipientId
976
	 */
977
	public function deleteFromSelf(\OCP\Share\IShare $share, $recipientId) {
978
		list($providerId, ) = $this->splitFullId($share->getFullId());
979
		$provider = $this->factory->getProvider($providerId);
980
981
		$provider->deleteFromSelf($share, $recipientId);
982
		$event = new GenericEvent($share);
983
		$this->eventDispatcher->dispatch('OCP\Share::postUnshareFromSelf', $event);
984
	}
985
986
	public function restoreShare(IShare $share, string $recipientId): IShare {
987
		list($providerId, ) = $this->splitFullId($share->getFullId());
988
		$provider = $this->factory->getProvider($providerId);
989
990
		return $provider->restore($share, $recipientId);
991
	}
992
993
	/**
994
	 * @inheritdoc
995
	 */
996
	public function moveShare(\OCP\Share\IShare $share, $recipientId) {
997
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
998
			throw new \InvalidArgumentException('Can’t change target of link share');
999
		}
1000
1001
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && $share->getSharedWith() !== $recipientId) {
1002
			throw new \InvalidArgumentException('Invalid recipient');
1003
		}
1004
1005
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
1006
			$sharedWith = $this->groupManager->get($share->getSharedWith());
1007
			if (is_null($sharedWith)) {
1008
				throw new \InvalidArgumentException('Group "' . $share->getSharedWith() . '" does not exist');
1009
			}
1010
			$recipient = $this->userManager->get($recipientId);
1011
			if (!$sharedWith->inGroup($recipient)) {
0 ignored issues
show
Bug introduced by
It seems like $recipient defined by $this->userManager->get($recipientId) on line 1010 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...
1012
				throw new \InvalidArgumentException('Invalid recipient');
1013
			}
1014
		}
1015
1016
		list($providerId, ) = $this->splitFullId($share->getFullId());
1017
		$provider = $this->factory->getProvider($providerId);
1018
1019
		$provider->move($share, $recipientId);
1020
	}
1021
1022
	public function getSharesInFolder($userId, Folder $node, $reshares = false) {
1023
		$providers = $this->factory->getAllProviders();
1024
1025
		return array_reduce($providers, function($shares, IShareProvider $provider) use ($userId, $node, $reshares) {
1026
			$newShares = $provider->getSharesInFolder($userId, $node, $reshares);
1027
			foreach ($newShares as $fid => $data) {
1028
				if (!isset($shares[$fid])) {
1029
					$shares[$fid] = [];
1030
				}
1031
1032
				$shares[$fid] = array_merge($shares[$fid], $data);
1033
			}
1034
			return $shares;
1035
		}, []);
1036
	}
1037
1038
	/**
1039
	 * @inheritdoc
1040
	 */
1041
	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
1042
		if ($path !== null &&
1043
				!($path instanceof \OCP\Files\File) &&
1044
				!($path instanceof \OCP\Files\Folder)) {
1045
			throw new \InvalidArgumentException('invalid path');
1046
		}
1047
1048
		try {
1049
			$provider = $this->factory->getProviderForType($shareType);
1050
		} catch (ProviderException $e) {
1051
			return [];
1052
		}
1053
1054
		$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1055
1056
		/*
1057
		 * Work around so we don't return expired shares but still follow
1058
		 * proper pagination.
1059
		 */
1060
1061
		$shares2 = [];
1062
1063
		while(true) {
1064
			$added = 0;
1065
			foreach ($shares as $share) {
1066
1067
				try {
1068
					$this->checkExpireDate($share);
1069
				} catch (ShareNotFound $e) {
1070
					//Ignore since this basically means the share is deleted
1071
					continue;
1072
				}
1073
1074
				$added++;
1075
				$shares2[] = $share;
1076
1077
				if (count($shares2) === $limit) {
1078
					break;
1079
				}
1080
			}
1081
1082
			// If we did not fetch more shares than the limit then there are no more shares
1083
			if (count($shares) < $limit) {
1084
				break;
1085
			}
1086
1087
			if (count($shares2) === $limit) {
1088
				break;
1089
			}
1090
1091
			// If there was no limit on the select we are done
1092
			if ($limit === -1) {
1093
				break;
1094
			}
1095
1096
			$offset += $added;
1097
1098
			// Fetch again $limit shares
1099
			$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1100
1101
			// No more shares means we are done
1102
			if (empty($shares)) {
1103
				break;
1104
			}
1105
		}
1106
1107
		$shares = $shares2;
1108
1109
		return $shares;
1110
	}
1111
1112
	/**
1113
	 * @inheritdoc
1114
	 */
1115
	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1116
		try {
1117
			$provider = $this->factory->getProviderForType($shareType);
1118
		} catch (ProviderException $e) {
1119
			return [];
1120
		}
1121
1122
		$shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1123
1124
		// remove all shares which are already expired
1125
		foreach ($shares as $key => $share) {
1126
			try {
1127
				$this->checkExpireDate($share);
1128
			} catch (ShareNotFound $e) {
1129
				unset($shares[$key]);
1130
			}
1131
		}
1132
1133
		return $shares;
1134
	}
1135
1136
	/**
1137
	 * @inheritdoc
1138
	 */
1139
	public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1140
		$shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1141
1142
		// Only get deleted shares
1143
		$shares = array_filter($shares, function(IShare $share) {
1144
			return $share->getPermissions() === 0;
1145
		});
1146
1147
		// Only get shares where the owner still exists
1148
		$shares = array_filter($shares, function (IShare $share) {
1149
			return $this->userManager->userExists($share->getShareOwner());
1150
		});
1151
1152
		return $shares;
1153
	}
1154
1155
	/**
1156
	 * @inheritdoc
1157
	 */
1158
	public function getShareById($id, $recipient = null) {
1159
		if ($id === null) {
1160
			throw new ShareNotFound();
1161
		}
1162
1163
		list($providerId, $id) = $this->splitFullId($id);
1164
1165
		try {
1166
			$provider = $this->factory->getProvider($providerId);
1167
		} catch (ProviderException $e) {
1168
			throw new ShareNotFound();
1169
		}
1170
1171
		$share = $provider->getShareById($id, $recipient);
1172
1173
		$this->checkExpireDate($share);
1174
1175
		return $share;
1176
	}
1177
1178
	/**
1179
	 * Get all the shares for a given path
1180
	 *
1181
	 * @param \OCP\Files\Node $path
1182
	 * @param int $page
1183
	 * @param int $perPage
1184
	 *
1185
	 * @return Share[]
1186
	 */
1187
	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...
1188
		return [];
1189
	}
1190
1191
	/**
1192
	 * Get the share by token possible with password
1193
	 *
1194
	 * @param string $token
1195
	 * @return Share
1196
	 *
1197
	 * @throws ShareNotFound
1198
	 */
1199
	public function getShareByToken($token) {
1200
		$share = null;
1201
		try {
1202
			if($this->shareApiAllowLinks()) {
1203
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_LINK);
1204
				$share = $provider->getShareByToken($token);
1205
			}
1206
		} catch (ProviderException $e) {
1207
		} catch (ShareNotFound $e) {
1208
		}
1209
1210
1211
		// If it is not a link share try to fetch a federated share by token
1212 View Code Duplication
		if ($share === null) {
1213
			try {
1214
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_REMOTE);
1215
				$share = $provider->getShareByToken($token);
1216
			} catch (ProviderException $e) {
1217
			} catch (ShareNotFound $e) {
1218
			}
1219
		}
1220
1221
		// If it is not a link share try to fetch a mail share by token
1222 View Code Duplication
		if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
1223
			try {
1224
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_EMAIL);
1225
				$share = $provider->getShareByToken($token);
1226
			} catch (ProviderException $e) {
1227
			} catch (ShareNotFound $e) {
1228
			}
1229
		}
1230
1231 View Code Duplication
		if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_CIRCLE)) {
1232
			try {
1233
				$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_CIRCLE);
1234
				$share = $provider->getShareByToken($token);
1235
			} catch (ProviderException $e) {
1236
			} catch (ShareNotFound $e) {
1237
			}
1238
		}
1239
1240
		if ($share === null) {
1241
			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1242
		}
1243
1244
		$this->checkExpireDate($share);
1245
1246
		/*
1247
		 * Reduce the permissions for link shares if public upload is not enabled
1248
		 */
1249
		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
1250
			!$this->shareApiLinkAllowPublicUpload()) {
1251
			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1252
		}
1253
1254
		return $share;
1255
	}
1256
1257
	protected function checkExpireDate($share) {
1258
		if ($share->getExpirationDate() !== null &&
1259
			$share->getExpirationDate() <= new \DateTime()) {
1260
			$this->deleteShare($share);
1261
			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1262
		}
1263
1264
	}
1265
1266
	/**
1267
	 * Verify the password of a public share
1268
	 *
1269
	 * @param \OCP\Share\IShare $share
1270
	 * @param string $password
1271
	 * @return bool
1272
	 */
1273
	public function checkPassword(\OCP\Share\IShare $share, $password) {
1274
		$passwordProtected = $share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK
1275
			|| $share->getShareType() !== \OCP\Share::SHARE_TYPE_EMAIL;
1276
		if (!$passwordProtected) {
1277
			//TODO maybe exception?
1278
			return false;
1279
		}
1280
1281
		if ($password === null || $share->getPassword() === null) {
1282
			return false;
1283
		}
1284
1285
		$newHash = '';
1286
		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1287
			return false;
1288
		}
1289
1290
		if (!empty($newHash)) {
1291
			$share->setPassword($newHash);
1292
			$provider = $this->factory->getProviderForType($share->getShareType());
1293
			$provider->update($share);
1294
		}
1295
1296
		return true;
1297
	}
1298
1299
	/**
1300
	 * @inheritdoc
1301
	 */
1302
	public function userDeleted($uid) {
1303
		$types = [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE, \OCP\Share::SHARE_TYPE_EMAIL];
1304
1305
		foreach ($types as $type) {
1306
			try {
1307
				$provider = $this->factory->getProviderForType($type);
1308
			} catch (ProviderException $e) {
1309
				continue;
1310
			}
1311
			$provider->userDeleted($uid, $type);
1312
		}
1313
	}
1314
1315
	/**
1316
	 * @inheritdoc
1317
	 */
1318
	public function groupDeleted($gid) {
1319
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1320
		$provider->groupDeleted($gid);
1321
	}
1322
1323
	/**
1324
	 * @inheritdoc
1325
	 */
1326
	public function userDeletedFromGroup($uid, $gid) {
1327
		$provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
1328
		$provider->userDeletedFromGroup($uid, $gid);
1329
	}
1330
1331
	/**
1332
	 * Get access list to a path. This means
1333
	 * all the users that can access a given path.
1334
	 *
1335
	 * Consider:
1336
	 * -root
1337
	 * |-folder1 (23)
1338
	 *  |-folder2 (32)
1339
	 *   |-fileA (42)
1340
	 *
1341
	 * fileA is shared with user1 and user1@server1
1342
	 * folder2 is shared with group2 (user4 is a member of group2)
1343
	 * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
1344
	 *
1345
	 * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
1346
	 * [
1347
	 *  users  => [
1348
	 *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
1349
	 *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
1350
	 *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
1351
	 *  ],
1352
	 *  remote => [
1353
	 *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
1354
	 *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
1355
	 *  ],
1356
	 *  public => bool
1357
	 *  mail => bool
1358
	 * ]
1359
	 *
1360
	 * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
1361
	 * [
1362
	 *  users  => ['user1', 'user2', 'user4'],
1363
	 *  remote => bool,
1364
	 *  public => bool
1365
	 *  mail => bool
1366
	 * ]
1367
	 *
1368
	 * This is required for encryption/activity
1369
	 *
1370
	 * @param \OCP\Files\Node $path
1371
	 * @param bool $recursive Should we check all parent folders as well
1372
	 * @param bool $currentAccess Ensure the recipient has access to the file (e.g. did not unshare it)
1373
	 * @return array
1374
	 */
1375
	public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1376
		$owner = $path->getOwner()->getUID();
1377
1378
		if ($currentAccess) {
1379
			$al = ['users' => [], 'remote' => [], 'public' => false];
1380
		} else {
1381
			$al = ['users' => [], 'remote' => false, 'public' => false];
1382
		}
1383
		if (!$this->userManager->userExists($owner)) {
1384
			return $al;
1385
		}
1386
1387
		//Get node for the owner
1388
		$userFolder = $this->rootFolder->getUserFolder($owner);
1389
		if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1390
			$path = $userFolder->getById($path->getId())[0];
1391
		}
1392
1393
		$providers = $this->factory->getAllProviders();
1394
1395
		/** @var Node[] $nodes */
1396
		$nodes = [];
1397
1398
1399
		if ($currentAccess) {
1400
			$ownerPath = $path->getPath();
1401
			$ownerPath = explode('/', $ownerPath, 4);
1402
			if (count($ownerPath) < 4) {
1403
				$ownerPath = '';
1404
			} else {
1405
				$ownerPath = $ownerPath[3];
1406
			}
1407
			$al['users'][$owner] = [
1408
				'node_id' => $path->getId(),
1409
				'node_path' => '/' . $ownerPath,
1410
			];
1411
		} else {
1412
			$al['users'][] = $owner;
1413
		}
1414
1415
		// Collect all the shares
1416
		while ($path->getPath() !== $userFolder->getPath()) {
1417
			$nodes[] = $path;
1418
			if (!$recursive) {
1419
				break;
1420
			}
1421
			$path = $path->getParent();
1422
		}
1423
1424
		foreach ($providers as $provider) {
1425
			$tmp = $provider->getAccessList($nodes, $currentAccess);
1426
1427
			foreach ($tmp as $k => $v) {
1428
				if (isset($al[$k])) {
1429
					if (is_array($al[$k])) {
1430
						if ($currentAccess) {
1431
							$al[$k] += $v;
1432
						} else {
1433
							$al[$k] = array_merge($al[$k], $v);
1434
							$al[$k] = array_unique($al[$k]);
1435
							$al[$k] = array_values($al[$k]);
1436
						}
1437
					} else {
1438
						$al[$k] = $al[$k] || $v;
1439
					}
1440
				} else {
1441
					$al[$k] = $v;
1442
				}
1443
			}
1444
		}
1445
1446
		return $al;
1447
	}
1448
1449
	/**
1450
	 * Create a new share
1451
	 * @return \OCP\Share\IShare
1452
	 */
1453
	public function newShare() {
1454
		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1455
	}
1456
1457
	/**
1458
	 * Is the share API enabled
1459
	 *
1460
	 * @return bool
1461
	 */
1462
	public function shareApiEnabled() {
1463
		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1464
	}
1465
1466
	/**
1467
	 * Is public link sharing enabled
1468
	 *
1469
	 * @return bool
1470
	 */
1471
	public function shareApiAllowLinks() {
1472
		return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
1473
	}
1474
1475
	/**
1476
	 * Is password on public link requires
1477
	 *
1478
	 * @return bool
1479
	 */
1480
	public function shareApiLinkEnforcePassword() {
1481
		return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1482
	}
1483
1484
	/**
1485
	 * Is default expire date enabled
1486
	 *
1487
	 * @return bool
1488
	 */
1489
	public function shareApiLinkDefaultExpireDate() {
1490
		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1491
	}
1492
1493
	/**
1494
	 * Is default expire date enforced
1495
	 *`
1496
	 * @return bool
1497
	 */
1498
	public function shareApiLinkDefaultExpireDateEnforced() {
1499
		return $this->shareApiLinkDefaultExpireDate() &&
1500
			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1501
	}
1502
1503
	/**
1504
	 * Number of default expire days
1505
	 *shareApiLinkAllowPublicUpload
1506
	 * @return int
1507
	 */
1508
	public function shareApiLinkDefaultExpireDays() {
1509
		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1510
	}
1511
1512
	/**
1513
	 * Allow public upload on link shares
1514
	 *
1515
	 * @return bool
1516
	 */
1517
	public function shareApiLinkAllowPublicUpload() {
1518
		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1519
	}
1520
1521
	/**
1522
	 * check if user can only share with group members
1523
	 * @return bool
1524
	 */
1525
	public function shareWithGroupMembersOnly() {
1526
		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1527
	}
1528
1529
	/**
1530
	 * Check if users can share with groups
1531
	 * @return bool
1532
	 */
1533
	public function allowGroupSharing() {
1534
		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1535
	}
1536
1537
	/**
1538
	 * Copied from \OC_Util::isSharingDisabledForUser
1539
	 *
1540
	 * TODO: Deprecate fuction from OC_Util
1541
	 *
1542
	 * @param string $userId
1543
	 * @return bool
1544
	 */
1545
	public function sharingDisabledForUser($userId) {
1546
		if ($userId === null) {
1547
			return false;
1548
		}
1549
1550
		if (isset($this->sharingDisabledForUsersCache[$userId])) {
1551
			return $this->sharingDisabledForUsersCache[$userId];
1552
		}
1553
1554
		if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
1555
			$groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1556
			$excludedGroups = json_decode($groupsList);
1557 View Code Duplication
			if (is_null($excludedGroups)) {
1558
				$excludedGroups = explode(',', $groupsList);
1559
				$newValue = json_encode($excludedGroups);
1560
				$this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
1561
			}
1562
			$user = $this->userManager->get($userId);
1563
			$usersGroups = $this->groupManager->getUserGroupIds($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($userId) on line 1562 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...
1564 View Code Duplication
			if (!empty($usersGroups)) {
1565
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
1566
				// if the user is only in groups which are disabled for sharing then
1567
				// sharing is also disabled for the user
1568
				if (empty($remainingGroups)) {
1569
					$this->sharingDisabledForUsersCache[$userId] = true;
1570
					return true;
1571
				}
1572
			}
1573
		}
1574
1575
		$this->sharingDisabledForUsersCache[$userId] = false;
1576
		return false;
1577
	}
1578
1579
	/**
1580
	 * @inheritdoc
1581
	 */
1582
	public function outgoingServer2ServerSharesAllowed() {
1583
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1584
	}
1585
1586
	/**
1587
	 * @inheritdoc
1588
	 */
1589
	public function outgoingServer2ServerGroupSharesAllowed() {
1590
		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1591
	}
1592
1593
	/**
1594
	 * @inheritdoc
1595
	 */
1596
	public function shareProviderExists($shareType) {
1597
		try {
1598
			$this->factory->getProviderForType($shareType);
1599
		} catch (ProviderException $e) {
1600
			return false;
1601
		}
1602
1603
		return true;
1604
	}
1605
1606
}
1607