Completed
Push — master ( 3cdfe6...9e455f )
by Daniel
38:42
created
lib/private/Share20/Manager.php 1 patch
Indentation   +1970 added lines, -1970 removed lines patch added patch discarded remove patch
@@ -63,1991 +63,1991 @@
 block discarded – undo
63 63
  */
64 64
 class Manager implements IManager {
65 65
 
66
-	private ?IL10N $l;
67
-	private LegacyHooks $legacyHooks;
68
-
69
-	public function __construct(
70
-		private LoggerInterface $logger,
71
-		private IConfig $config,
72
-		private ISecureRandom $secureRandom,
73
-		private IHasher $hasher,
74
-		private IMountManager $mountManager,
75
-		private IGroupManager $groupManager,
76
-		private IFactory $l10nFactory,
77
-		private IProviderFactory $factory,
78
-		private IUserManager $userManager,
79
-		private IRootFolder $rootFolder,
80
-		private IMailer $mailer,
81
-		private IURLGenerator $urlGenerator,
82
-		private \OC_Defaults $defaults,
83
-		private IEventDispatcher $dispatcher,
84
-		private IUserSession $userSession,
85
-		private KnownUserService $knownUserService,
86
-		private ShareDisableChecker $shareDisableChecker,
87
-		private IDateTimeZone $dateTimeZone,
88
-		private IAppConfig $appConfig,
89
-	) {
90
-		$this->l = $this->l10nFactory->get('lib');
91
-		// The constructor of LegacyHooks registers the listeners of share events
92
-		// do not remove if those are not properly migrated
93
-		$this->legacyHooks = new LegacyHooks($this->dispatcher);
94
-	}
95
-
96
-	/**
97
-	 * Convert from a full share id to a tuple (providerId, shareId)
98
-	 *
99
-	 * @param string $id
100
-	 * @return string[]
101
-	 */
102
-	private function splitFullId($id) {
103
-		return explode(':', $id, 2);
104
-	}
105
-
106
-	/**
107
-	 * Verify if a password meets all requirements
108
-	 *
109
-	 * @param string $password
110
-	 * @throws HintException
111
-	 */
112
-	protected function verifyPassword($password) {
113
-		if ($password === null) {
114
-			// No password is set, check if this is allowed.
115
-			if ($this->shareApiLinkEnforcePassword()) {
116
-				throw new \InvalidArgumentException($this->l->t('Passwords are enforced for link and mail shares'));
117
-			}
118
-
119
-			return;
120
-		}
121
-
122
-		// Let others verify the password
123
-		try {
124
-			$event = new ValidatePasswordPolicyEvent($password, PasswordContext::SHARING);
125
-			$this->dispatcher->dispatchTyped($event);
126
-		} catch (HintException $e) {
127
-			/* Wrap in a 400 bad request error */
128
-			throw new HintException($e->getMessage(), $e->getHint(), 400, $e);
129
-		}
130
-	}
131
-
132
-	/**
133
-	 * Check for generic requirements before creating a share
134
-	 *
135
-	 * @param IShare $share
136
-	 * @throws \InvalidArgumentException
137
-	 * @throws GenericShareException
138
-	 *
139
-	 * @suppress PhanUndeclaredClassMethod
140
-	 */
141
-	protected function generalCreateChecks(IShare $share, bool $isUpdate = false) {
142
-		if ($share->getShareType() === IShare::TYPE_USER) {
143
-			// We expect a valid user as sharedWith for user shares
144
-			if (!$this->userManager->userExists($share->getSharedWith())) {
145
-				throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid user'));
146
-			}
147
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
148
-			// We expect a valid group as sharedWith for group shares
149
-			if (!$this->groupManager->groupExists($share->getSharedWith())) {
150
-				throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid group'));
151
-			}
152
-		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
153
-			// No check for TYPE_EMAIL here as we have a recipient for them
154
-			if ($share->getSharedWith() !== null) {
155
-				throw new \InvalidArgumentException($this->l->t('Share recipient should be empty'));
156
-			}
157
-		} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
158
-			if ($share->getSharedWith() === null) {
159
-				throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
160
-			}
161
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
162
-			if ($share->getSharedWith() === null) {
163
-				throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
164
-			}
165
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
166
-			if ($share->getSharedWith() === null) {
167
-				throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
168
-			}
169
-		} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
170
-			$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith());
171
-			if ($circle === null) {
172
-				throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid circle'));
173
-			}
174
-		} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
175
-		} elseif ($share->getShareType() === IShare::TYPE_DECK) {
176
-		} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
177
-		} else {
178
-			// We cannot handle other types yet
179
-			throw new \InvalidArgumentException($this->l->t('Unknown share type'));
180
-		}
181
-
182
-		// Verify the initiator of the share is set
183
-		if ($share->getSharedBy() === null) {
184
-			throw new \InvalidArgumentException($this->l->t('Share initiator must be set'));
185
-		}
186
-
187
-		// Cannot share with yourself
188
-		if ($share->getShareType() === IShare::TYPE_USER
189
-			&& $share->getSharedWith() === $share->getSharedBy()) {
190
-			throw new \InvalidArgumentException($this->l->t('Cannot share with yourself'));
191
-		}
192
-
193
-		// The path should be set
194
-		if ($share->getNode() === null) {
195
-			throw new \InvalidArgumentException($this->l->t('Shared path must be set'));
196
-		}
197
-
198
-		// And it should be a file or a folder
199
-		if (!($share->getNode() instanceof \OCP\Files\File)
200
-			&& !($share->getNode() instanceof \OCP\Files\Folder)) {
201
-			throw new \InvalidArgumentException($this->l->t('Shared path must be either a file or a folder'));
202
-		}
203
-
204
-		// And you cannot share your rootfolder
205
-		if ($this->userManager->userExists($share->getSharedBy())) {
206
-			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
207
-		} else {
208
-			$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
209
-		}
210
-		if ($userFolder->getId() === $share->getNode()->getId()) {
211
-			throw new \InvalidArgumentException($this->l->t('You cannot share your root folder'));
212
-		}
213
-
214
-		// Check if we actually have share permissions
215
-		if (!$share->getNode()->isShareable()) {
216
-			throw new GenericShareException($this->l->t('You are not allowed to share %s', [$share->getNode()->getName()]), code: 404);
217
-		}
218
-
219
-		// Permissions should be set
220
-		if ($share->getPermissions() === null) {
221
-			throw new \InvalidArgumentException($this->l->t('Valid permissions are required for sharing'));
222
-		}
223
-
224
-		// Permissions must be valid
225
-		if ($share->getPermissions() < 0 || $share->getPermissions() > \OCP\Constants::PERMISSION_ALL) {
226
-			throw new \InvalidArgumentException($this->l->t('Valid permissions are required for sharing'));
227
-		}
228
-
229
-		// Single file shares should never have delete or create permissions
230
-		if (($share->getNode() instanceof File)
231
-			&& (($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE)) !== 0)) {
232
-			throw new \InvalidArgumentException($this->l->t('File shares cannot have create or delete permissions'));
233
-		}
234
-
235
-		$permissions = 0;
236
-		$nodesForUser = $userFolder->getById($share->getNodeId());
237
-		foreach ($nodesForUser as $node) {
238
-			if ($node->getInternalPath() === '' && !$node->getMountPoint() instanceof MoveableMount) {
239
-				// for the root of non-movable mount, the permissions we see if limited by the mount itself,
240
-				// so we instead use the "raw" permissions from the storage
241
-				$permissions |= $node->getStorage()->getPermissions('');
242
-			} else {
243
-				$permissions |= $node->getPermissions();
244
-			}
245
-		}
246
-
247
-		// Check that we do not share with more permissions than we have
248
-		if ($share->getPermissions() & ~$permissions) {
249
-			$path = $userFolder->getRelativePath($share->getNode()->getPath());
250
-			throw new GenericShareException($this->l->t('Cannot increase permissions of %s', [$path]), code: 404);
251
-		}
252
-
253
-		// Check that read permissions are always set
254
-		// Link shares are allowed to have no read permissions to allow upload to hidden folders
255
-		$noReadPermissionRequired = $share->getShareType() === IShare::TYPE_LINK
256
-			|| $share->getShareType() === IShare::TYPE_EMAIL;
257
-		if (!$noReadPermissionRequired
258
-			&& ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
259
-			throw new \InvalidArgumentException($this->l->t('Shares need at least read permissions'));
260
-		}
261
-
262
-		if ($share->getNode() instanceof \OCP\Files\File) {
263
-			if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
264
-				throw new GenericShareException($this->l->t('Files cannot be shared with delete permissions'));
265
-			}
266
-			if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
267
-				throw new GenericShareException($this->l->t('Files cannot be shared with create permissions'));
268
-			}
269
-		}
270
-	}
271
-
272
-	/**
273
-	 * Validate if the expiration date fits the system settings
274
-	 *
275
-	 * @param IShare $share The share to validate the expiration date of
276
-	 * @return IShare The modified share object
277
-	 * @throws GenericShareException
278
-	 * @throws \InvalidArgumentException
279
-	 * @throws \Exception
280
-	 */
281
-	protected function validateExpirationDateInternal(IShare $share) {
282
-		$isRemote = $share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP;
283
-
284
-		$expirationDate = $share->getExpirationDate();
285
-
286
-		if ($isRemote) {
287
-			$defaultExpireDate = $this->shareApiRemoteDefaultExpireDate();
288
-			$defaultExpireDays = $this->shareApiRemoteDefaultExpireDays();
289
-			$configProp = 'remote_defaultExpDays';
290
-			$isEnforced = $this->shareApiRemoteDefaultExpireDateEnforced();
291
-		} else {
292
-			$defaultExpireDate = $this->shareApiInternalDefaultExpireDate();
293
-			$defaultExpireDays = $this->shareApiInternalDefaultExpireDays();
294
-			$configProp = 'internal_defaultExpDays';
295
-			$isEnforced = $this->shareApiInternalDefaultExpireDateEnforced();
296
-		}
297
-
298
-		// If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
299
-		// Then skip expiration date validation as null is accepted
300
-		if (!$share->getNoExpirationDate() || $isEnforced) {
301
-			if ($expirationDate !== null) {
302
-				$expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
303
-				$expirationDate->setTime(0, 0, 0);
304
-
305
-				$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
306
-				$date->setTime(0, 0, 0);
307
-				if ($date >= $expirationDate) {
308
-					throw new GenericShareException($this->l->t('Expiration date is in the past'), code: 404);
309
-				}
310
-			}
311
-
312
-			// If expiredate is empty set a default one if there is a default
313
-			$fullId = null;
314
-			try {
315
-				$fullId = $share->getFullId();
316
-			} catch (\UnexpectedValueException $e) {
317
-				// This is a new share
318
-			}
319
-
320
-			if ($fullId === null && $expirationDate === null && $defaultExpireDate) {
321
-				$expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
322
-				$expirationDate->setTime(0, 0, 0);
323
-				$days = (int)$this->config->getAppValue('core', $configProp, (string)$defaultExpireDays);
324
-				if ($days > $defaultExpireDays) {
325
-					$days = $defaultExpireDays;
326
-				}
327
-				$expirationDate->add(new \DateInterval('P' . $days . 'D'));
328
-			}
329
-
330
-			// If we enforce the expiration date check that is does not exceed
331
-			if ($isEnforced) {
332
-				if (empty($expirationDate)) {
333
-					throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
334
-				}
335
-
336
-				$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
337
-				$date->setTime(0, 0, 0);
338
-				$date->add(new \DateInterval('P' . $defaultExpireDays . 'D'));
339
-				if ($date < $expirationDate) {
340
-					throw new GenericShareException($this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $defaultExpireDays), code: 404);
341
-				}
342
-			}
343
-		}
344
-
345
-		$accepted = true;
346
-		$message = '';
347
-		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
348
-			'expirationDate' => &$expirationDate,
349
-			'accepted' => &$accepted,
350
-			'message' => &$message,
351
-			'passwordSet' => $share->getPassword() !== null,
352
-		]);
353
-
354
-		if (!$accepted) {
355
-			throw new \Exception($message);
356
-		}
357
-
358
-		$share->setExpirationDate($expirationDate);
359
-
360
-		return $share;
361
-	}
362
-
363
-	/**
364
-	 * Validate if the expiration date fits the system settings
365
-	 *
366
-	 * @param IShare $share The share to validate the expiration date of
367
-	 * @return IShare The modified share object
368
-	 * @throws GenericShareException
369
-	 * @throws \InvalidArgumentException
370
-	 * @throws \Exception
371
-	 */
372
-	protected function validateExpirationDateLink(IShare $share) {
373
-		$expirationDate = $share->getExpirationDate();
374
-		$isEnforced = $this->shareApiLinkDefaultExpireDateEnforced();
375
-
376
-		// If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
377
-		// Then skip expiration date validation as null is accepted
378
-		if (!($share->getNoExpirationDate() && !$isEnforced)) {
379
-			if ($expirationDate !== null) {
380
-				$expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
381
-				$expirationDate->setTime(0, 0, 0);
382
-
383
-				$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
384
-				$date->setTime(0, 0, 0);
385
-				if ($date >= $expirationDate) {
386
-					throw new GenericShareException($this->l->t('Expiration date is in the past'), code: 404);
387
-				}
388
-			}
389
-
390
-			// If expiredate is empty set a default one if there is a default
391
-			$fullId = null;
392
-			try {
393
-				$fullId = $share->getFullId();
394
-			} catch (\UnexpectedValueException $e) {
395
-				// This is a new share
396
-			}
397
-
398
-			if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
399
-				$expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
400
-				$expirationDate->setTime(0, 0, 0);
401
-
402
-				$days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', (string)$this->shareApiLinkDefaultExpireDays());
403
-				if ($days > $this->shareApiLinkDefaultExpireDays()) {
404
-					$days = $this->shareApiLinkDefaultExpireDays();
405
-				}
406
-				$expirationDate->add(new \DateInterval('P' . $days . 'D'));
407
-			}
408
-
409
-			// If we enforce the expiration date check that is does not exceed
410
-			if ($isEnforced) {
411
-				if (empty($expirationDate)) {
412
-					throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
413
-				}
414
-
415
-				$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
416
-				$date->setTime(0, 0, 0);
417
-				$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
418
-				if ($date < $expirationDate) {
419
-					throw new GenericShareException(
420
-						$this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $this->shareApiLinkDefaultExpireDays()),
421
-						code: 404,
422
-					);
423
-				}
424
-			}
425
-
426
-		}
427
-
428
-		$accepted = true;
429
-		$message = '';
430
-		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
431
-			'expirationDate' => &$expirationDate,
432
-			'accepted' => &$accepted,
433
-			'message' => &$message,
434
-			'passwordSet' => $share->getPassword() !== null,
435
-		]);
436
-
437
-		if (!$accepted) {
438
-			throw new \Exception($message);
439
-		}
440
-
441
-		$share->setExpirationDate($expirationDate);
442
-
443
-		return $share;
444
-	}
445
-
446
-	/**
447
-	 * Check for pre share requirements for user shares
448
-	 *
449
-	 * @param IShare $share
450
-	 * @throws \Exception
451
-	 */
452
-	protected function userCreateChecks(IShare $share) {
453
-		// Check if we can share with group members only
454
-		if ($this->shareWithGroupMembersOnly()) {
455
-			$sharedBy = $this->userManager->get($share->getSharedBy());
456
-			$sharedWith = $this->userManager->get($share->getSharedWith());
457
-			// Verify we can share with this user
458
-			$groups = array_intersect(
459
-				$this->groupManager->getUserGroupIds($sharedBy),
460
-				$this->groupManager->getUserGroupIds($sharedWith)
461
-			);
462
-
463
-			// optional excluded groups
464
-			$excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
465
-			$groups = array_diff($groups, $excludedGroups);
466
-
467
-			if (empty($groups)) {
468
-				throw new \Exception($this->l->t('Sharing is only allowed with group members'));
469
-			}
470
-		}
471
-
472
-		/*
66
+    private ?IL10N $l;
67
+    private LegacyHooks $legacyHooks;
68
+
69
+    public function __construct(
70
+        private LoggerInterface $logger,
71
+        private IConfig $config,
72
+        private ISecureRandom $secureRandom,
73
+        private IHasher $hasher,
74
+        private IMountManager $mountManager,
75
+        private IGroupManager $groupManager,
76
+        private IFactory $l10nFactory,
77
+        private IProviderFactory $factory,
78
+        private IUserManager $userManager,
79
+        private IRootFolder $rootFolder,
80
+        private IMailer $mailer,
81
+        private IURLGenerator $urlGenerator,
82
+        private \OC_Defaults $defaults,
83
+        private IEventDispatcher $dispatcher,
84
+        private IUserSession $userSession,
85
+        private KnownUserService $knownUserService,
86
+        private ShareDisableChecker $shareDisableChecker,
87
+        private IDateTimeZone $dateTimeZone,
88
+        private IAppConfig $appConfig,
89
+    ) {
90
+        $this->l = $this->l10nFactory->get('lib');
91
+        // The constructor of LegacyHooks registers the listeners of share events
92
+        // do not remove if those are not properly migrated
93
+        $this->legacyHooks = new LegacyHooks($this->dispatcher);
94
+    }
95
+
96
+    /**
97
+     * Convert from a full share id to a tuple (providerId, shareId)
98
+     *
99
+     * @param string $id
100
+     * @return string[]
101
+     */
102
+    private function splitFullId($id) {
103
+        return explode(':', $id, 2);
104
+    }
105
+
106
+    /**
107
+     * Verify if a password meets all requirements
108
+     *
109
+     * @param string $password
110
+     * @throws HintException
111
+     */
112
+    protected function verifyPassword($password) {
113
+        if ($password === null) {
114
+            // No password is set, check if this is allowed.
115
+            if ($this->shareApiLinkEnforcePassword()) {
116
+                throw new \InvalidArgumentException($this->l->t('Passwords are enforced for link and mail shares'));
117
+            }
118
+
119
+            return;
120
+        }
121
+
122
+        // Let others verify the password
123
+        try {
124
+            $event = new ValidatePasswordPolicyEvent($password, PasswordContext::SHARING);
125
+            $this->dispatcher->dispatchTyped($event);
126
+        } catch (HintException $e) {
127
+            /* Wrap in a 400 bad request error */
128
+            throw new HintException($e->getMessage(), $e->getHint(), 400, $e);
129
+        }
130
+    }
131
+
132
+    /**
133
+     * Check for generic requirements before creating a share
134
+     *
135
+     * @param IShare $share
136
+     * @throws \InvalidArgumentException
137
+     * @throws GenericShareException
138
+     *
139
+     * @suppress PhanUndeclaredClassMethod
140
+     */
141
+    protected function generalCreateChecks(IShare $share, bool $isUpdate = false) {
142
+        if ($share->getShareType() === IShare::TYPE_USER) {
143
+            // We expect a valid user as sharedWith for user shares
144
+            if (!$this->userManager->userExists($share->getSharedWith())) {
145
+                throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid user'));
146
+            }
147
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
148
+            // We expect a valid group as sharedWith for group shares
149
+            if (!$this->groupManager->groupExists($share->getSharedWith())) {
150
+                throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid group'));
151
+            }
152
+        } elseif ($share->getShareType() === IShare::TYPE_LINK) {
153
+            // No check for TYPE_EMAIL here as we have a recipient for them
154
+            if ($share->getSharedWith() !== null) {
155
+                throw new \InvalidArgumentException($this->l->t('Share recipient should be empty'));
156
+            }
157
+        } elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
158
+            if ($share->getSharedWith() === null) {
159
+                throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
160
+            }
161
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
162
+            if ($share->getSharedWith() === null) {
163
+                throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
164
+            }
165
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
166
+            if ($share->getSharedWith() === null) {
167
+                throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
168
+            }
169
+        } elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
170
+            $circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith());
171
+            if ($circle === null) {
172
+                throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid circle'));
173
+            }
174
+        } elseif ($share->getShareType() === IShare::TYPE_ROOM) {
175
+        } elseif ($share->getShareType() === IShare::TYPE_DECK) {
176
+        } elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
177
+        } else {
178
+            // We cannot handle other types yet
179
+            throw new \InvalidArgumentException($this->l->t('Unknown share type'));
180
+        }
181
+
182
+        // Verify the initiator of the share is set
183
+        if ($share->getSharedBy() === null) {
184
+            throw new \InvalidArgumentException($this->l->t('Share initiator must be set'));
185
+        }
186
+
187
+        // Cannot share with yourself
188
+        if ($share->getShareType() === IShare::TYPE_USER
189
+            && $share->getSharedWith() === $share->getSharedBy()) {
190
+            throw new \InvalidArgumentException($this->l->t('Cannot share with yourself'));
191
+        }
192
+
193
+        // The path should be set
194
+        if ($share->getNode() === null) {
195
+            throw new \InvalidArgumentException($this->l->t('Shared path must be set'));
196
+        }
197
+
198
+        // And it should be a file or a folder
199
+        if (!($share->getNode() instanceof \OCP\Files\File)
200
+            && !($share->getNode() instanceof \OCP\Files\Folder)) {
201
+            throw new \InvalidArgumentException($this->l->t('Shared path must be either a file or a folder'));
202
+        }
203
+
204
+        // And you cannot share your rootfolder
205
+        if ($this->userManager->userExists($share->getSharedBy())) {
206
+            $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
207
+        } else {
208
+            $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
209
+        }
210
+        if ($userFolder->getId() === $share->getNode()->getId()) {
211
+            throw new \InvalidArgumentException($this->l->t('You cannot share your root folder'));
212
+        }
213
+
214
+        // Check if we actually have share permissions
215
+        if (!$share->getNode()->isShareable()) {
216
+            throw new GenericShareException($this->l->t('You are not allowed to share %s', [$share->getNode()->getName()]), code: 404);
217
+        }
218
+
219
+        // Permissions should be set
220
+        if ($share->getPermissions() === null) {
221
+            throw new \InvalidArgumentException($this->l->t('Valid permissions are required for sharing'));
222
+        }
223
+
224
+        // Permissions must be valid
225
+        if ($share->getPermissions() < 0 || $share->getPermissions() > \OCP\Constants::PERMISSION_ALL) {
226
+            throw new \InvalidArgumentException($this->l->t('Valid permissions are required for sharing'));
227
+        }
228
+
229
+        // Single file shares should never have delete or create permissions
230
+        if (($share->getNode() instanceof File)
231
+            && (($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE)) !== 0)) {
232
+            throw new \InvalidArgumentException($this->l->t('File shares cannot have create or delete permissions'));
233
+        }
234
+
235
+        $permissions = 0;
236
+        $nodesForUser = $userFolder->getById($share->getNodeId());
237
+        foreach ($nodesForUser as $node) {
238
+            if ($node->getInternalPath() === '' && !$node->getMountPoint() instanceof MoveableMount) {
239
+                // for the root of non-movable mount, the permissions we see if limited by the mount itself,
240
+                // so we instead use the "raw" permissions from the storage
241
+                $permissions |= $node->getStorage()->getPermissions('');
242
+            } else {
243
+                $permissions |= $node->getPermissions();
244
+            }
245
+        }
246
+
247
+        // Check that we do not share with more permissions than we have
248
+        if ($share->getPermissions() & ~$permissions) {
249
+            $path = $userFolder->getRelativePath($share->getNode()->getPath());
250
+            throw new GenericShareException($this->l->t('Cannot increase permissions of %s', [$path]), code: 404);
251
+        }
252
+
253
+        // Check that read permissions are always set
254
+        // Link shares are allowed to have no read permissions to allow upload to hidden folders
255
+        $noReadPermissionRequired = $share->getShareType() === IShare::TYPE_LINK
256
+            || $share->getShareType() === IShare::TYPE_EMAIL;
257
+        if (!$noReadPermissionRequired
258
+            && ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
259
+            throw new \InvalidArgumentException($this->l->t('Shares need at least read permissions'));
260
+        }
261
+
262
+        if ($share->getNode() instanceof \OCP\Files\File) {
263
+            if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
264
+                throw new GenericShareException($this->l->t('Files cannot be shared with delete permissions'));
265
+            }
266
+            if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
267
+                throw new GenericShareException($this->l->t('Files cannot be shared with create permissions'));
268
+            }
269
+        }
270
+    }
271
+
272
+    /**
273
+     * Validate if the expiration date fits the system settings
274
+     *
275
+     * @param IShare $share The share to validate the expiration date of
276
+     * @return IShare The modified share object
277
+     * @throws GenericShareException
278
+     * @throws \InvalidArgumentException
279
+     * @throws \Exception
280
+     */
281
+    protected function validateExpirationDateInternal(IShare $share) {
282
+        $isRemote = $share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP;
283
+
284
+        $expirationDate = $share->getExpirationDate();
285
+
286
+        if ($isRemote) {
287
+            $defaultExpireDate = $this->shareApiRemoteDefaultExpireDate();
288
+            $defaultExpireDays = $this->shareApiRemoteDefaultExpireDays();
289
+            $configProp = 'remote_defaultExpDays';
290
+            $isEnforced = $this->shareApiRemoteDefaultExpireDateEnforced();
291
+        } else {
292
+            $defaultExpireDate = $this->shareApiInternalDefaultExpireDate();
293
+            $defaultExpireDays = $this->shareApiInternalDefaultExpireDays();
294
+            $configProp = 'internal_defaultExpDays';
295
+            $isEnforced = $this->shareApiInternalDefaultExpireDateEnforced();
296
+        }
297
+
298
+        // If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
299
+        // Then skip expiration date validation as null is accepted
300
+        if (!$share->getNoExpirationDate() || $isEnforced) {
301
+            if ($expirationDate !== null) {
302
+                $expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
303
+                $expirationDate->setTime(0, 0, 0);
304
+
305
+                $date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
306
+                $date->setTime(0, 0, 0);
307
+                if ($date >= $expirationDate) {
308
+                    throw new GenericShareException($this->l->t('Expiration date is in the past'), code: 404);
309
+                }
310
+            }
311
+
312
+            // If expiredate is empty set a default one if there is a default
313
+            $fullId = null;
314
+            try {
315
+                $fullId = $share->getFullId();
316
+            } catch (\UnexpectedValueException $e) {
317
+                // This is a new share
318
+            }
319
+
320
+            if ($fullId === null && $expirationDate === null && $defaultExpireDate) {
321
+                $expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
322
+                $expirationDate->setTime(0, 0, 0);
323
+                $days = (int)$this->config->getAppValue('core', $configProp, (string)$defaultExpireDays);
324
+                if ($days > $defaultExpireDays) {
325
+                    $days = $defaultExpireDays;
326
+                }
327
+                $expirationDate->add(new \DateInterval('P' . $days . 'D'));
328
+            }
329
+
330
+            // If we enforce the expiration date check that is does not exceed
331
+            if ($isEnforced) {
332
+                if (empty($expirationDate)) {
333
+                    throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
334
+                }
335
+
336
+                $date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
337
+                $date->setTime(0, 0, 0);
338
+                $date->add(new \DateInterval('P' . $defaultExpireDays . 'D'));
339
+                if ($date < $expirationDate) {
340
+                    throw new GenericShareException($this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $defaultExpireDays), code: 404);
341
+                }
342
+            }
343
+        }
344
+
345
+        $accepted = true;
346
+        $message = '';
347
+        \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
348
+            'expirationDate' => &$expirationDate,
349
+            'accepted' => &$accepted,
350
+            'message' => &$message,
351
+            'passwordSet' => $share->getPassword() !== null,
352
+        ]);
353
+
354
+        if (!$accepted) {
355
+            throw new \Exception($message);
356
+        }
357
+
358
+        $share->setExpirationDate($expirationDate);
359
+
360
+        return $share;
361
+    }
362
+
363
+    /**
364
+     * Validate if the expiration date fits the system settings
365
+     *
366
+     * @param IShare $share The share to validate the expiration date of
367
+     * @return IShare The modified share object
368
+     * @throws GenericShareException
369
+     * @throws \InvalidArgumentException
370
+     * @throws \Exception
371
+     */
372
+    protected function validateExpirationDateLink(IShare $share) {
373
+        $expirationDate = $share->getExpirationDate();
374
+        $isEnforced = $this->shareApiLinkDefaultExpireDateEnforced();
375
+
376
+        // If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
377
+        // Then skip expiration date validation as null is accepted
378
+        if (!($share->getNoExpirationDate() && !$isEnforced)) {
379
+            if ($expirationDate !== null) {
380
+                $expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
381
+                $expirationDate->setTime(0, 0, 0);
382
+
383
+                $date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
384
+                $date->setTime(0, 0, 0);
385
+                if ($date >= $expirationDate) {
386
+                    throw new GenericShareException($this->l->t('Expiration date is in the past'), code: 404);
387
+                }
388
+            }
389
+
390
+            // If expiredate is empty set a default one if there is a default
391
+            $fullId = null;
392
+            try {
393
+                $fullId = $share->getFullId();
394
+            } catch (\UnexpectedValueException $e) {
395
+                // This is a new share
396
+            }
397
+
398
+            if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
399
+                $expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
400
+                $expirationDate->setTime(0, 0, 0);
401
+
402
+                $days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', (string)$this->shareApiLinkDefaultExpireDays());
403
+                if ($days > $this->shareApiLinkDefaultExpireDays()) {
404
+                    $days = $this->shareApiLinkDefaultExpireDays();
405
+                }
406
+                $expirationDate->add(new \DateInterval('P' . $days . 'D'));
407
+            }
408
+
409
+            // If we enforce the expiration date check that is does not exceed
410
+            if ($isEnforced) {
411
+                if (empty($expirationDate)) {
412
+                    throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
413
+                }
414
+
415
+                $date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
416
+                $date->setTime(0, 0, 0);
417
+                $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
418
+                if ($date < $expirationDate) {
419
+                    throw new GenericShareException(
420
+                        $this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $this->shareApiLinkDefaultExpireDays()),
421
+                        code: 404,
422
+                    );
423
+                }
424
+            }
425
+
426
+        }
427
+
428
+        $accepted = true;
429
+        $message = '';
430
+        \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
431
+            'expirationDate' => &$expirationDate,
432
+            'accepted' => &$accepted,
433
+            'message' => &$message,
434
+            'passwordSet' => $share->getPassword() !== null,
435
+        ]);
436
+
437
+        if (!$accepted) {
438
+            throw new \Exception($message);
439
+        }
440
+
441
+        $share->setExpirationDate($expirationDate);
442
+
443
+        return $share;
444
+    }
445
+
446
+    /**
447
+     * Check for pre share requirements for user shares
448
+     *
449
+     * @param IShare $share
450
+     * @throws \Exception
451
+     */
452
+    protected function userCreateChecks(IShare $share) {
453
+        // Check if we can share with group members only
454
+        if ($this->shareWithGroupMembersOnly()) {
455
+            $sharedBy = $this->userManager->get($share->getSharedBy());
456
+            $sharedWith = $this->userManager->get($share->getSharedWith());
457
+            // Verify we can share with this user
458
+            $groups = array_intersect(
459
+                $this->groupManager->getUserGroupIds($sharedBy),
460
+                $this->groupManager->getUserGroupIds($sharedWith)
461
+            );
462
+
463
+            // optional excluded groups
464
+            $excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
465
+            $groups = array_diff($groups, $excludedGroups);
466
+
467
+            if (empty($groups)) {
468
+                throw new \Exception($this->l->t('Sharing is only allowed with group members'));
469
+            }
470
+        }
471
+
472
+        /*
473 473
 		 * TODO: Could be costly, fix
474 474
 		 *
475 475
 		 * Also this is not what we want in the future.. then we want to squash identical shares.
476 476
 		 */
477
-		$provider = $this->factory->getProviderForType(IShare::TYPE_USER);
478
-		$existingShares = $provider->getSharesByPath($share->getNode());
479
-		foreach ($existingShares as $existingShare) {
480
-			// Ignore if it is the same share
481
-			try {
482
-				if ($existingShare->getFullId() === $share->getFullId()) {
483
-					continue;
484
-				}
485
-			} catch (\UnexpectedValueException $e) {
486
-				//Shares are not identical
487
-			}
488
-
489
-			// Identical share already exists
490
-			if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
491
-				throw new AlreadySharedException($this->l->t('Sharing %s failed, because this item is already shared with the account %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]), $existingShare);
492
-			}
493
-
494
-			// The share is already shared with this user via a group share
495
-			if ($existingShare->getShareType() === IShare::TYPE_GROUP) {
496
-				$group = $this->groupManager->get($existingShare->getSharedWith());
497
-				if (!is_null($group)) {
498
-					$user = $this->userManager->get($share->getSharedWith());
499
-
500
-					if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
501
-						throw new AlreadySharedException($this->l->t('Sharing %s failed, because this item is already shared with the account %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]), $existingShare);
502
-					}
503
-				}
504
-			}
505
-		}
506
-	}
507
-
508
-	/**
509
-	 * Check for pre share requirements for group shares
510
-	 *
511
-	 * @param IShare $share
512
-	 * @throws \Exception
513
-	 */
514
-	protected function groupCreateChecks(IShare $share) {
515
-		// Verify group shares are allowed
516
-		if (!$this->allowGroupSharing()) {
517
-			throw new \Exception($this->l->t('Group sharing is now allowed'));
518
-		}
519
-
520
-		// Verify if the user can share with this group
521
-		if ($this->shareWithGroupMembersOnly()) {
522
-			$sharedBy = $this->userManager->get($share->getSharedBy());
523
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
524
-
525
-			// optional excluded groups
526
-			$excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
527
-			if (is_null($sharedWith) || in_array($share->getSharedWith(), $excludedGroups) || !$sharedWith->inGroup($sharedBy)) {
528
-				throw new \Exception($this->l->t('Sharing is only allowed within your own groups'));
529
-			}
530
-		}
531
-
532
-		/*
477
+        $provider = $this->factory->getProviderForType(IShare::TYPE_USER);
478
+        $existingShares = $provider->getSharesByPath($share->getNode());
479
+        foreach ($existingShares as $existingShare) {
480
+            // Ignore if it is the same share
481
+            try {
482
+                if ($existingShare->getFullId() === $share->getFullId()) {
483
+                    continue;
484
+                }
485
+            } catch (\UnexpectedValueException $e) {
486
+                //Shares are not identical
487
+            }
488
+
489
+            // Identical share already exists
490
+            if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
491
+                throw new AlreadySharedException($this->l->t('Sharing %s failed, because this item is already shared with the account %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]), $existingShare);
492
+            }
493
+
494
+            // The share is already shared with this user via a group share
495
+            if ($existingShare->getShareType() === IShare::TYPE_GROUP) {
496
+                $group = $this->groupManager->get($existingShare->getSharedWith());
497
+                if (!is_null($group)) {
498
+                    $user = $this->userManager->get($share->getSharedWith());
499
+
500
+                    if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
501
+                        throw new AlreadySharedException($this->l->t('Sharing %s failed, because this item is already shared with the account %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]), $existingShare);
502
+                    }
503
+                }
504
+            }
505
+        }
506
+    }
507
+
508
+    /**
509
+     * Check for pre share requirements for group shares
510
+     *
511
+     * @param IShare $share
512
+     * @throws \Exception
513
+     */
514
+    protected function groupCreateChecks(IShare $share) {
515
+        // Verify group shares are allowed
516
+        if (!$this->allowGroupSharing()) {
517
+            throw new \Exception($this->l->t('Group sharing is now allowed'));
518
+        }
519
+
520
+        // Verify if the user can share with this group
521
+        if ($this->shareWithGroupMembersOnly()) {
522
+            $sharedBy = $this->userManager->get($share->getSharedBy());
523
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
524
+
525
+            // optional excluded groups
526
+            $excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
527
+            if (is_null($sharedWith) || in_array($share->getSharedWith(), $excludedGroups) || !$sharedWith->inGroup($sharedBy)) {
528
+                throw new \Exception($this->l->t('Sharing is only allowed within your own groups'));
529
+            }
530
+        }
531
+
532
+        /*
533 533
 		 * TODO: Could be costly, fix
534 534
 		 *
535 535
 		 * Also this is not what we want in the future.. then we want to squash identical shares.
536 536
 		 */
537
-		$provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
538
-		$existingShares = $provider->getSharesByPath($share->getNode());
539
-		foreach ($existingShares as $existingShare) {
540
-			try {
541
-				if ($existingShare->getFullId() === $share->getFullId()) {
542
-					continue;
543
-				}
544
-			} catch (\UnexpectedValueException $e) {
545
-				//It is a new share so just continue
546
-			}
547
-
548
-			if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
549
-				throw new AlreadySharedException($this->l->t('Path is already shared with this group'), $existingShare);
550
-			}
551
-		}
552
-	}
553
-
554
-	/**
555
-	 * Check for pre share requirements for link shares
556
-	 *
557
-	 * @param IShare $share
558
-	 * @throws \Exception
559
-	 */
560
-	protected function linkCreateChecks(IShare $share) {
561
-		// Are link shares allowed?
562
-		if (!$this->shareApiAllowLinks()) {
563
-			throw new \Exception($this->l->t('Link sharing is not allowed'));
564
-		}
565
-
566
-		// Check if public upload is allowed
567
-		if ($share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()
568
-			&& ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
569
-			throw new \InvalidArgumentException($this->l->t('Public upload is not allowed'));
570
-		}
571
-	}
572
-
573
-	/**
574
-	 * To make sure we don't get invisible link shares we set the parent
575
-	 * of a link if it is a reshare. This is a quick word around
576
-	 * until we can properly display multiple link shares in the UI
577
-	 *
578
-	 * See: https://github.com/owncloud/core/issues/22295
579
-	 *
580
-	 * FIXME: Remove once multiple link shares can be properly displayed
581
-	 *
582
-	 * @param IShare $share
583
-	 */
584
-	protected function setLinkParent(IShare $share) {
585
-		$storage = $share->getNode()->getStorage();
586
-		if ($storage->instanceOfStorage(SharedStorage::class)) {
587
-			/** @var \OCA\Files_Sharing\SharedStorage $storage */
588
-			$share->setParent((int)$storage->getShareId());
589
-		}
590
-	}
591
-
592
-	/**
593
-	 * @param File|Folder $path
594
-	 */
595
-	protected function pathCreateChecks($path) {
596
-		// Make sure that we do not share a path that contains a shared mountpoint
597
-		if ($path instanceof \OCP\Files\Folder) {
598
-			$mounts = $this->mountManager->findIn($path->getPath());
599
-			foreach ($mounts as $mount) {
600
-				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
601
-					// Using a flat sharing model ensures the file owner can always see who has access.
602
-					// Allowing parent folder sharing would require tracking inherited access, which adds complexity
603
-					// and hurts performance/scalability.
604
-					// So we forbid sharing a parent folder of a share you received.
605
-					throw new \InvalidArgumentException($this->l->t('You cannot share a folder that contains other shares'));
606
-				}
607
-			}
608
-		}
609
-	}
610
-
611
-	/**
612
-	 * Check if the user that is sharing can actually share
613
-	 *
614
-	 * @param IShare $share
615
-	 * @throws \Exception
616
-	 */
617
-	protected function canShare(IShare $share) {
618
-		if (!$this->shareApiEnabled()) {
619
-			throw new \Exception($this->l->t('Sharing is disabled'));
620
-		}
621
-
622
-		if ($this->sharingDisabledForUser($share->getSharedBy())) {
623
-			throw new \Exception($this->l->t('Sharing is disabled for you'));
624
-		}
625
-	}
626
-
627
-	/**
628
-	 * Share a path
629
-	 *
630
-	 * @param IShare $share
631
-	 * @return IShare The share object
632
-	 * @throws \Exception
633
-	 *
634
-	 * TODO: handle link share permissions or check them
635
-	 */
636
-	public function createShare(IShare $share) {
637
-		$this->canShare($share);
638
-
639
-		$this->generalCreateChecks($share);
640
-
641
-		// Verify if there are any issues with the path
642
-		$this->pathCreateChecks($share->getNode());
643
-
644
-		/*
537
+        $provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
538
+        $existingShares = $provider->getSharesByPath($share->getNode());
539
+        foreach ($existingShares as $existingShare) {
540
+            try {
541
+                if ($existingShare->getFullId() === $share->getFullId()) {
542
+                    continue;
543
+                }
544
+            } catch (\UnexpectedValueException $e) {
545
+                //It is a new share so just continue
546
+            }
547
+
548
+            if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
549
+                throw new AlreadySharedException($this->l->t('Path is already shared with this group'), $existingShare);
550
+            }
551
+        }
552
+    }
553
+
554
+    /**
555
+     * Check for pre share requirements for link shares
556
+     *
557
+     * @param IShare $share
558
+     * @throws \Exception
559
+     */
560
+    protected function linkCreateChecks(IShare $share) {
561
+        // Are link shares allowed?
562
+        if (!$this->shareApiAllowLinks()) {
563
+            throw new \Exception($this->l->t('Link sharing is not allowed'));
564
+        }
565
+
566
+        // Check if public upload is allowed
567
+        if ($share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()
568
+            && ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
569
+            throw new \InvalidArgumentException($this->l->t('Public upload is not allowed'));
570
+        }
571
+    }
572
+
573
+    /**
574
+     * To make sure we don't get invisible link shares we set the parent
575
+     * of a link if it is a reshare. This is a quick word around
576
+     * until we can properly display multiple link shares in the UI
577
+     *
578
+     * See: https://github.com/owncloud/core/issues/22295
579
+     *
580
+     * FIXME: Remove once multiple link shares can be properly displayed
581
+     *
582
+     * @param IShare $share
583
+     */
584
+    protected function setLinkParent(IShare $share) {
585
+        $storage = $share->getNode()->getStorage();
586
+        if ($storage->instanceOfStorage(SharedStorage::class)) {
587
+            /** @var \OCA\Files_Sharing\SharedStorage $storage */
588
+            $share->setParent((int)$storage->getShareId());
589
+        }
590
+    }
591
+
592
+    /**
593
+     * @param File|Folder $path
594
+     */
595
+    protected function pathCreateChecks($path) {
596
+        // Make sure that we do not share a path that contains a shared mountpoint
597
+        if ($path instanceof \OCP\Files\Folder) {
598
+            $mounts = $this->mountManager->findIn($path->getPath());
599
+            foreach ($mounts as $mount) {
600
+                if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
601
+                    // Using a flat sharing model ensures the file owner can always see who has access.
602
+                    // Allowing parent folder sharing would require tracking inherited access, which adds complexity
603
+                    // and hurts performance/scalability.
604
+                    // So we forbid sharing a parent folder of a share you received.
605
+                    throw new \InvalidArgumentException($this->l->t('You cannot share a folder that contains other shares'));
606
+                }
607
+            }
608
+        }
609
+    }
610
+
611
+    /**
612
+     * Check if the user that is sharing can actually share
613
+     *
614
+     * @param IShare $share
615
+     * @throws \Exception
616
+     */
617
+    protected function canShare(IShare $share) {
618
+        if (!$this->shareApiEnabled()) {
619
+            throw new \Exception($this->l->t('Sharing is disabled'));
620
+        }
621
+
622
+        if ($this->sharingDisabledForUser($share->getSharedBy())) {
623
+            throw new \Exception($this->l->t('Sharing is disabled for you'));
624
+        }
625
+    }
626
+
627
+    /**
628
+     * Share a path
629
+     *
630
+     * @param IShare $share
631
+     * @return IShare The share object
632
+     * @throws \Exception
633
+     *
634
+     * TODO: handle link share permissions or check them
635
+     */
636
+    public function createShare(IShare $share) {
637
+        $this->canShare($share);
638
+
639
+        $this->generalCreateChecks($share);
640
+
641
+        // Verify if there are any issues with the path
642
+        $this->pathCreateChecks($share->getNode());
643
+
644
+        /*
645 645
 		 * On creation of a share the owner is always the owner of the path
646 646
 		 * Except for mounted federated shares.
647 647
 		 */
648
-		$storage = $share->getNode()->getStorage();
649
-		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
650
-			$parent = $share->getNode()->getParent();
651
-			while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
652
-				$parent = $parent->getParent();
653
-			}
654
-			$share->setShareOwner($parent->getOwner()->getUID());
655
-		} else {
656
-			if ($share->getNode()->getOwner()) {
657
-				$share->setShareOwner($share->getNode()->getOwner()->getUID());
658
-			} else {
659
-				$share->setShareOwner($share->getSharedBy());
660
-			}
661
-		}
662
-
663
-		try {
664
-			// Verify share type
665
-			if ($share->getShareType() === IShare::TYPE_USER) {
666
-				$this->userCreateChecks($share);
667
-
668
-				// Verify the expiration date
669
-				$share = $this->validateExpirationDateInternal($share);
670
-			} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
671
-				$this->groupCreateChecks($share);
672
-
673
-				// Verify the expiration date
674
-				$share = $this->validateExpirationDateInternal($share);
675
-			} elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
676
-				// Verify the expiration date
677
-				$share = $this->validateExpirationDateInternal($share);
678
-			} elseif ($share->getShareType() === IShare::TYPE_LINK
679
-				|| $share->getShareType() === IShare::TYPE_EMAIL) {
680
-				$this->linkCreateChecks($share);
681
-				$this->setLinkParent($share);
682
-
683
-				$token = $this->generateToken();
684
-				// Set the unique token
685
-				$share->setToken($token);
686
-
687
-				// Verify the expiration date
688
-				$share = $this->validateExpirationDateLink($share);
689
-
690
-				// Verify the password
691
-				$this->verifyPassword($share->getPassword());
692
-
693
-				// If a password is set. Hash it!
694
-				if ($share->getShareType() === IShare::TYPE_LINK
695
-					&& $share->getPassword() !== null) {
696
-					$share->setPassword($this->hasher->hash($share->getPassword()));
697
-				}
698
-			}
699
-
700
-			// Cannot share with the owner
701
-			if ($share->getShareType() === IShare::TYPE_USER
702
-				&& $share->getSharedWith() === $share->getShareOwner()) {
703
-				throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
704
-			}
705
-
706
-			// Generate the target
707
-			$shareFolder = $this->config->getSystemValue('share_folder', '/');
708
-			if ($share->getShareType() === IShare::TYPE_USER) {
709
-				$allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
710
-				if ($allowCustomShareFolder) {
711
-					$shareFolder = $this->config->getUserValue($share->getSharedWith(), Application::APP_ID, 'share_folder', $shareFolder);
712
-				}
713
-			}
714
-
715
-			$target = $shareFolder . '/' . $share->getNode()->getName();
716
-			$target = \OC\Files\Filesystem::normalizePath($target);
717
-			$share->setTarget($target);
718
-
719
-			// Pre share event
720
-			$event = new Share\Events\BeforeShareCreatedEvent($share);
721
-			$this->dispatchEvent($event, 'before share created');
722
-			if ($event->isPropagationStopped() && $event->getError()) {
723
-				throw new \Exception($event->getError());
724
-			}
725
-
726
-			$oldShare = $share;
727
-			$provider = $this->factory->getProviderForType($share->getShareType());
728
-			$share = $provider->create($share);
729
-
730
-			// Reuse the node we already have
731
-			$share->setNode($oldShare->getNode());
732
-
733
-			// Reset the target if it is null for the new share
734
-			if ($share->getTarget() === '') {
735
-				$share->setTarget($target);
736
-			}
737
-		} catch (AlreadySharedException $e) {
738
-			// If a share for the same target already exists, dont create a new one,
739
-			// but do trigger the hooks and notifications again
740
-			$oldShare = $share;
741
-
742
-			// Reuse the node we already have
743
-			$share = $e->getExistingShare();
744
-			$share->setNode($oldShare->getNode());
745
-		}
746
-
747
-		// Post share event
748
-		$this->dispatchEvent(new ShareCreatedEvent($share), 'share created');
749
-
750
-		// Send email if needed
751
-		if ($this->config->getSystemValueBool('sharing.enable_share_mail', true)) {
752
-			if ($share->getMailSend()) {
753
-				$provider = $this->factory->getProviderForType($share->getShareType());
754
-				if ($provider instanceof IShareProviderWithNotification) {
755
-					$provider->sendMailNotification($share);
756
-				} else {
757
-					$this->logger->debug('Share notification not sent because the provider does not support it.', ['app' => 'share']);
758
-				}
759
-			} else {
760
-				$this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']);
761
-			}
762
-		} else {
763
-			$this->logger->debug('Share notification not sent because sharing notification emails is disabled.', ['app' => 'share']);
764
-		}
765
-
766
-		return $share;
767
-	}
768
-
769
-	/**
770
-	 * Update a share
771
-	 *
772
-	 * @param IShare $share
773
-	 * @return IShare The share object
774
-	 * @throws \InvalidArgumentException
775
-	 * @throws HintException
776
-	 */
777
-	public function updateShare(IShare $share, bool $onlyValid = true) {
778
-		$expirationDateUpdated = false;
779
-
780
-		$this->canShare($share);
781
-
782
-		try {
783
-			$originalShare = $this->getShareById($share->getFullId(), onlyValid: $onlyValid);
784
-		} catch (\UnexpectedValueException $e) {
785
-			throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
786
-		}
787
-
788
-		// We cannot change the share type!
789
-		if ($share->getShareType() !== $originalShare->getShareType()) {
790
-			throw new \InvalidArgumentException($this->l->t('Cannot change share type'));
791
-		}
792
-
793
-		// We can only change the recipient on user shares
794
-		if ($share->getSharedWith() !== $originalShare->getSharedWith()
795
-			&& $share->getShareType() !== IShare::TYPE_USER) {
796
-			throw new \InvalidArgumentException($this->l->t('Can only update recipient on user shares'));
797
-		}
798
-
799
-		// Cannot share with the owner
800
-		if ($share->getShareType() === IShare::TYPE_USER
801
-			&& $share->getSharedWith() === $share->getShareOwner()) {
802
-			throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
803
-		}
804
-
805
-		$this->generalCreateChecks($share, true);
806
-
807
-		if ($share->getShareType() === IShare::TYPE_USER) {
808
-			$this->userCreateChecks($share);
809
-
810
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
811
-				// Verify the expiration date
812
-				$this->validateExpirationDateInternal($share);
813
-				$expirationDateUpdated = true;
814
-			}
815
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
816
-			$this->groupCreateChecks($share);
817
-
818
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
819
-				// Verify the expiration date
820
-				$this->validateExpirationDateInternal($share);
821
-				$expirationDateUpdated = true;
822
-			}
823
-		} elseif ($share->getShareType() === IShare::TYPE_LINK
824
-			|| $share->getShareType() === IShare::TYPE_EMAIL) {
825
-			$this->linkCreateChecks($share);
826
-
827
-			// The new password is not set again if it is the same as the old
828
-			// one, unless when switching from sending by Talk to sending by
829
-			// mail.
830
-			$plainTextPassword = $share->getPassword();
831
-			$updatedPassword = $this->updateSharePasswordIfNeeded($share, $originalShare);
832
-
833
-			/**
834
-			 * Cannot enable the getSendPasswordByTalk if there is no password set
835
-			 */
836
-			if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) {
837
-				throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk with an empty password'));
838
-			}
839
-
840
-			/**
841
-			 * If we're in a mail share, we need to force a password change
842
-			 * as either the user is not aware of the password or is already (received by mail)
843
-			 * Thus the SendPasswordByTalk feature would not make sense
844
-			 */
845
-			if (!$updatedPassword && $share->getShareType() === IShare::TYPE_EMAIL) {
846
-				if (!$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) {
847
-					throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk without setting a new password'));
848
-				}
849
-				if ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) {
850
-					throw new \InvalidArgumentException($this->l->t('Cannot disable sending the password by Talk without setting a new password'));
851
-				}
852
-			}
853
-
854
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
855
-				// Verify the expiration date
856
-				$this->validateExpirationDateLink($share);
857
-				$expirationDateUpdated = true;
858
-			}
859
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
860
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
861
-				// Verify the expiration date
862
-				$this->validateExpirationDateInternal($share);
863
-				$expirationDateUpdated = true;
864
-			}
865
-		}
866
-
867
-		$this->pathCreateChecks($share->getNode());
868
-
869
-		// Now update the share!
870
-		$provider = $this->factory->getProviderForType($share->getShareType());
871
-		if ($share->getShareType() === IShare::TYPE_EMAIL) {
872
-			/** @var ShareByMailProvider $provider */
873
-			$share = $provider->update($share, $plainTextPassword);
874
-		} else {
875
-			$share = $provider->update($share);
876
-		}
877
-
878
-		if ($expirationDateUpdated === true) {
879
-			\OC_Hook::emit(Share::class, 'post_set_expiration_date', [
880
-				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
881
-				'itemSource' => $share->getNode()->getId(),
882
-				'date' => $share->getExpirationDate(),
883
-				'uidOwner' => $share->getSharedBy(),
884
-			]);
885
-		}
886
-
887
-		if ($share->getPassword() !== $originalShare->getPassword()) {
888
-			\OC_Hook::emit(Share::class, 'post_update_password', [
889
-				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
890
-				'itemSource' => $share->getNode()->getId(),
891
-				'uidOwner' => $share->getSharedBy(),
892
-				'token' => $share->getToken(),
893
-				'disabled' => is_null($share->getPassword()),
894
-			]);
895
-		}
896
-
897
-		if ($share->getPermissions() !== $originalShare->getPermissions()) {
898
-			if ($this->userManager->userExists($share->getShareOwner())) {
899
-				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
900
-			} else {
901
-				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
902
-			}
903
-			\OC_Hook::emit(Share::class, 'post_update_permissions', [
904
-				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
905
-				'itemSource' => $share->getNode()->getId(),
906
-				'shareType' => $share->getShareType(),
907
-				'shareWith' => $share->getSharedWith(),
908
-				'uidOwner' => $share->getSharedBy(),
909
-				'permissions' => $share->getPermissions(),
910
-				'attributes' => $share->getAttributes() !== null ? $share->getAttributes()->toArray() : null,
911
-				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
912
-			]);
913
-		}
914
-
915
-		return $share;
916
-	}
917
-
918
-	/**
919
-	 * Accept a share.
920
-	 *
921
-	 * @param IShare $share
922
-	 * @param string $recipientId
923
-	 * @return IShare The share object
924
-	 * @throws \InvalidArgumentException Thrown if the provider does not implement `IShareProviderSupportsAccept`
925
-	 * @since 9.0.0
926
-	 */
927
-	public function acceptShare(IShare $share, string $recipientId): IShare {
928
-		[$providerId,] = $this->splitFullId($share->getFullId());
929
-		$provider = $this->factory->getProvider($providerId);
930
-
931
-		if (!($provider instanceof IShareProviderSupportsAccept)) {
932
-			throw new \InvalidArgumentException($this->l->t('Share provider does not support accepting'));
933
-		}
934
-		/** @var IShareProvider&IShareProviderSupportsAccept $provider */
935
-		$provider->acceptShare($share, $recipientId);
936
-
937
-		$event = new ShareAcceptedEvent($share);
938
-		$this->dispatchEvent($event, 'share accepted');
939
-
940
-		return $share;
941
-	}
942
-
943
-	/**
944
-	 * Updates the password of the given share if it is not the same as the
945
-	 * password of the original share.
946
-	 *
947
-	 * @param IShare $share the share to update its password.
948
-	 * @param IShare $originalShare the original share to compare its
949
-	 *                              password with.
950
-	 * @return boolean whether the password was updated or not.
951
-	 */
952
-	private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare) {
953
-		$passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword())
954
-			&& (($share->getPassword() !== null && $originalShare->getPassword() === null)
955
-				|| ($share->getPassword() === null && $originalShare->getPassword() !== null)
956
-				|| ($share->getPassword() !== null && $originalShare->getPassword() !== null
957
-					&& !$this->hasher->verify($share->getPassword(), $originalShare->getPassword())));
958
-
959
-		// Password updated.
960
-		if ($passwordsAreDifferent) {
961
-			// Verify the password
962
-			$this->verifyPassword($share->getPassword());
963
-
964
-			// If a password is set. Hash it!
965
-			if (!empty($share->getPassword())) {
966
-				$share->setPassword($this->hasher->hash($share->getPassword()));
967
-				if ($share->getShareType() === IShare::TYPE_EMAIL) {
968
-					// Shares shared by email have temporary passwords
969
-					$this->setSharePasswordExpirationTime($share);
970
-				}
971
-
972
-				return true;
973
-			} else {
974
-				// Empty string and null are seen as NOT password protected
975
-				$share->setPassword(null);
976
-				if ($share->getShareType() === IShare::TYPE_EMAIL) {
977
-					$share->setPasswordExpirationTime(null);
978
-				}
979
-				return true;
980
-			}
981
-		} else {
982
-			// Reset the password to the original one, as it is either the same
983
-			// as the "new" password or a hashed version of it.
984
-			$share->setPassword($originalShare->getPassword());
985
-		}
986
-
987
-		return false;
988
-	}
989
-
990
-	/**
991
-	 * Set the share's password expiration time
992
-	 */
993
-	private function setSharePasswordExpirationTime(IShare $share): void {
994
-		if (!$this->config->getSystemValueBool('sharing.enable_mail_link_password_expiration', false)) {
995
-			// Sets password expiration date to NULL
996
-			$share->setPasswordExpirationTime();
997
-			return;
998
-		}
999
-		// Sets password expiration date
1000
-		$expirationTime = null;
1001
-		$now = new \DateTime();
1002
-		$expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
1003
-		$expirationTime = $now->add(new \DateInterval('PT' . $expirationInterval . 'S'));
1004
-		$share->setPasswordExpirationTime($expirationTime);
1005
-	}
1006
-
1007
-
1008
-	/**
1009
-	 * Delete all the children of this share
1010
-	 *
1011
-	 * @param IShare $share
1012
-	 * @return IShare[] List of deleted shares
1013
-	 */
1014
-	protected function deleteChildren(IShare $share) {
1015
-		$deletedShares = [];
1016
-
1017
-		$provider = $this->factory->getProviderForType($share->getShareType());
1018
-
1019
-		foreach ($provider->getChildren($share) as $child) {
1020
-			$this->dispatchEvent(new BeforeShareDeletedEvent($child), 'before share deleted');
1021
-
1022
-			$deletedChildren = $this->deleteChildren($child);
1023
-			$deletedShares = array_merge($deletedShares, $deletedChildren);
1024
-
1025
-			$provider->delete($child);
1026
-			$this->dispatchEvent(new ShareDeletedEvent($child), 'share deleted');
1027
-			$deletedShares[] = $child;
1028
-		}
1029
-
1030
-		return $deletedShares;
1031
-	}
1032
-
1033
-	/** Promote re-shares into direct shares so that target user keeps access */
1034
-	protected function promoteReshares(IShare $share): void {
1035
-		try {
1036
-			$node = $share->getNode();
1037
-		} catch (NotFoundException) {
1038
-			/* Skip if node not found */
1039
-			return;
1040
-		}
1041
-
1042
-		$userIds = [];
1043
-
1044
-		if ($share->getShareType() === IShare::TYPE_USER) {
1045
-			$userIds[] = $share->getSharedWith();
1046
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1047
-			$group = $this->groupManager->get($share->getSharedWith());
1048
-			$users = $group?->getUsers() ?? [];
1049
-
1050
-			foreach ($users as $user) {
1051
-				/* Skip share owner */
1052
-				if ($user->getUID() === $share->getShareOwner() || $user->getUID() === $share->getSharedBy()) {
1053
-					continue;
1054
-				}
1055
-				$userIds[] = $user->getUID();
1056
-			}
1057
-		} else {
1058
-			/* We only support user and group shares */
1059
-			return;
1060
-		}
1061
-
1062
-		$reshareRecords = [];
1063
-		$shareTypes = [
1064
-			IShare::TYPE_GROUP,
1065
-			IShare::TYPE_USER,
1066
-			IShare::TYPE_LINK,
1067
-			IShare::TYPE_REMOTE,
1068
-			IShare::TYPE_EMAIL,
1069
-		];
1070
-
1071
-		foreach ($userIds as $userId) {
1072
-			foreach ($shareTypes as $shareType) {
1073
-				try {
1074
-					$provider = $this->factory->getProviderForType($shareType);
1075
-				} catch (ProviderException $e) {
1076
-					continue;
1077
-				}
1078
-
1079
-				if ($node instanceof Folder) {
1080
-					/* We need to get all shares by this user to get subshares */
1081
-					$shares = $provider->getSharesBy($userId, $shareType, null, false, -1, 0);
1082
-
1083
-					foreach ($shares as $share) {
1084
-						try {
1085
-							$path = $share->getNode()->getPath();
1086
-						} catch (NotFoundException) {
1087
-							/* Ignore share of non-existing node */
1088
-							continue;
1089
-						}
1090
-						if ($node->getRelativePath($path) !== null) {
1091
-							/* If relative path is not null it means the shared node is the same or in a subfolder */
1092
-							$reshareRecords[] = $share;
1093
-						}
1094
-					}
1095
-				} else {
1096
-					$shares = $provider->getSharesBy($userId, $shareType, $node, false, -1, 0);
1097
-					foreach ($shares as $child) {
1098
-						$reshareRecords[] = $child;
1099
-					}
1100
-				}
1101
-			}
1102
-		}
1103
-
1104
-		foreach ($reshareRecords as $child) {
1105
-			try {
1106
-				/* Check if the share is still valid (means the resharer still has access to the file through another mean) */
1107
-				$this->generalCreateChecks($child);
1108
-			} catch (GenericShareException $e) {
1109
-				/* The check is invalid, promote it to a direct share from the sharer of parent share */
1110
-				$this->logger->debug('Promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1111
-				try {
1112
-					$child->setSharedBy($share->getSharedBy());
1113
-					$this->updateShare($child);
1114
-				} catch (GenericShareException|\InvalidArgumentException $e) {
1115
-					$this->logger->warning('Failed to promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1116
-				}
1117
-			}
1118
-		}
1119
-	}
1120
-
1121
-	/**
1122
-	 * Delete a share
1123
-	 *
1124
-	 * @param IShare $share
1125
-	 * @throws ShareNotFound
1126
-	 * @throws \InvalidArgumentException
1127
-	 */
1128
-	public function deleteShare(IShare $share) {
1129
-		try {
1130
-			$share->getFullId();
1131
-		} catch (\UnexpectedValueException $e) {
1132
-			throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
1133
-		}
1134
-
1135
-		$this->dispatchEvent(new BeforeShareDeletedEvent($share), 'before share deleted');
1136
-
1137
-		// Get all children and delete them as well
1138
-		$this->deleteChildren($share);
1139
-
1140
-		// Do the actual delete
1141
-		$provider = $this->factory->getProviderForType($share->getShareType());
1142
-		$provider->delete($share);
1143
-
1144
-		$this->dispatchEvent(new ShareDeletedEvent($share), 'share deleted');
1145
-
1146
-		// Promote reshares of the deleted share
1147
-		$this->promoteReshares($share);
1148
-	}
1149
-
1150
-
1151
-	/**
1152
-	 * Unshare a file as the recipient.
1153
-	 * This can be different from a regular delete for example when one of
1154
-	 * the users in a groups deletes that share. But the provider should
1155
-	 * handle this.
1156
-	 *
1157
-	 * @param IShare $share
1158
-	 * @param string $recipientId
1159
-	 */
1160
-	public function deleteFromSelf(IShare $share, $recipientId) {
1161
-		[$providerId,] = $this->splitFullId($share->getFullId());
1162
-		$provider = $this->factory->getProvider($providerId);
1163
-
1164
-		$provider->deleteFromSelf($share, $recipientId);
1165
-		$event = new ShareDeletedFromSelfEvent($share);
1166
-		$this->dispatchEvent($event, 'leave share');
1167
-	}
1168
-
1169
-	public function restoreShare(IShare $share, string $recipientId): IShare {
1170
-		[$providerId,] = $this->splitFullId($share->getFullId());
1171
-		$provider = $this->factory->getProvider($providerId);
1172
-
1173
-		return $provider->restore($share, $recipientId);
1174
-	}
1175
-
1176
-	/**
1177
-	 * @inheritdoc
1178
-	 */
1179
-	public function moveShare(IShare $share, $recipientId) {
1180
-		if ($share->getShareType() === IShare::TYPE_LINK
1181
-			|| $share->getShareType() === IShare::TYPE_EMAIL) {
1182
-			throw new \InvalidArgumentException($this->l->t('Cannot change target of link share'));
1183
-		}
1184
-
1185
-		if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) {
1186
-			throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1187
-		}
1188
-
1189
-		if ($share->getShareType() === IShare::TYPE_GROUP) {
1190
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
1191
-			if (is_null($sharedWith)) {
1192
-				throw new \InvalidArgumentException($this->l->t('Group "%s" does not exist', [$share->getSharedWith()]));
1193
-			}
1194
-			$recipient = $this->userManager->get($recipientId);
1195
-			if (!$sharedWith->inGroup($recipient)) {
1196
-				throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1197
-			}
1198
-		}
1199
-
1200
-		[$providerId,] = $this->splitFullId($share->getFullId());
1201
-		$provider = $this->factory->getProvider($providerId);
1202
-
1203
-		return $provider->move($share, $recipientId);
1204
-	}
1205
-
1206
-	public function getSharesInFolder($userId, Folder $node, $reshares = false, $shallow = true) {
1207
-		$providers = $this->factory->getAllProviders();
1208
-		if (!$shallow) {
1209
-			throw new \Exception('non-shallow getSharesInFolder is no longer supported');
1210
-		}
1211
-
1212
-		$isOwnerless = $node->getMountPoint() instanceof IShareOwnerlessMount;
1213
-
1214
-		$shares = [];
1215
-		foreach ($providers as $provider) {
1216
-			if ($isOwnerless) {
1217
-				// If the provider does not implement the additional interface,
1218
-				// we lack a performant way of querying all shares and therefore ignore the provider.
1219
-				if ($provider instanceof IShareProviderSupportsAllSharesInFolder) {
1220
-					foreach ($provider->getAllSharesInFolder($node) as $fid => $data) {
1221
-						$shares[$fid] ??= [];
1222
-						$shares[$fid] = array_merge($shares[$fid], $data);
1223
-					}
1224
-				}
1225
-			} else {
1226
-				foreach ($provider->getSharesInFolder($userId, $node, $reshares) as $fid => $data) {
1227
-					$shares[$fid] ??= [];
1228
-					$shares[$fid] = array_merge($shares[$fid], $data);
1229
-				}
1230
-			}
1231
-		}
1232
-
1233
-		return $shares;
1234
-	}
1235
-
1236
-	/**
1237
-	 * @inheritdoc
1238
-	 */
1239
-	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, bool $onlyValid = true) {
1240
-		if ($path !== null
1241
-			&& !($path instanceof \OCP\Files\File)
1242
-			&& !($path instanceof \OCP\Files\Folder)) {
1243
-			throw new \InvalidArgumentException($this->l->t('Invalid path'));
1244
-		}
1245
-
1246
-		try {
1247
-			$provider = $this->factory->getProviderForType($shareType);
1248
-		} catch (ProviderException $e) {
1249
-			return [];
1250
-		}
1251
-
1252
-		if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1253
-			$shares = array_filter($provider->getSharesByPath($path), static fn (IShare $share) => $share->getShareType() === $shareType);
1254
-		} else {
1255
-			$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1256
-		}
1257
-
1258
-		/*
648
+        $storage = $share->getNode()->getStorage();
649
+        if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
650
+            $parent = $share->getNode()->getParent();
651
+            while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
652
+                $parent = $parent->getParent();
653
+            }
654
+            $share->setShareOwner($parent->getOwner()->getUID());
655
+        } else {
656
+            if ($share->getNode()->getOwner()) {
657
+                $share->setShareOwner($share->getNode()->getOwner()->getUID());
658
+            } else {
659
+                $share->setShareOwner($share->getSharedBy());
660
+            }
661
+        }
662
+
663
+        try {
664
+            // Verify share type
665
+            if ($share->getShareType() === IShare::TYPE_USER) {
666
+                $this->userCreateChecks($share);
667
+
668
+                // Verify the expiration date
669
+                $share = $this->validateExpirationDateInternal($share);
670
+            } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
671
+                $this->groupCreateChecks($share);
672
+
673
+                // Verify the expiration date
674
+                $share = $this->validateExpirationDateInternal($share);
675
+            } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
676
+                // Verify the expiration date
677
+                $share = $this->validateExpirationDateInternal($share);
678
+            } elseif ($share->getShareType() === IShare::TYPE_LINK
679
+                || $share->getShareType() === IShare::TYPE_EMAIL) {
680
+                $this->linkCreateChecks($share);
681
+                $this->setLinkParent($share);
682
+
683
+                $token = $this->generateToken();
684
+                // Set the unique token
685
+                $share->setToken($token);
686
+
687
+                // Verify the expiration date
688
+                $share = $this->validateExpirationDateLink($share);
689
+
690
+                // Verify the password
691
+                $this->verifyPassword($share->getPassword());
692
+
693
+                // If a password is set. Hash it!
694
+                if ($share->getShareType() === IShare::TYPE_LINK
695
+                    && $share->getPassword() !== null) {
696
+                    $share->setPassword($this->hasher->hash($share->getPassword()));
697
+                }
698
+            }
699
+
700
+            // Cannot share with the owner
701
+            if ($share->getShareType() === IShare::TYPE_USER
702
+                && $share->getSharedWith() === $share->getShareOwner()) {
703
+                throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
704
+            }
705
+
706
+            // Generate the target
707
+            $shareFolder = $this->config->getSystemValue('share_folder', '/');
708
+            if ($share->getShareType() === IShare::TYPE_USER) {
709
+                $allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
710
+                if ($allowCustomShareFolder) {
711
+                    $shareFolder = $this->config->getUserValue($share->getSharedWith(), Application::APP_ID, 'share_folder', $shareFolder);
712
+                }
713
+            }
714
+
715
+            $target = $shareFolder . '/' . $share->getNode()->getName();
716
+            $target = \OC\Files\Filesystem::normalizePath($target);
717
+            $share->setTarget($target);
718
+
719
+            // Pre share event
720
+            $event = new Share\Events\BeforeShareCreatedEvent($share);
721
+            $this->dispatchEvent($event, 'before share created');
722
+            if ($event->isPropagationStopped() && $event->getError()) {
723
+                throw new \Exception($event->getError());
724
+            }
725
+
726
+            $oldShare = $share;
727
+            $provider = $this->factory->getProviderForType($share->getShareType());
728
+            $share = $provider->create($share);
729
+
730
+            // Reuse the node we already have
731
+            $share->setNode($oldShare->getNode());
732
+
733
+            // Reset the target if it is null for the new share
734
+            if ($share->getTarget() === '') {
735
+                $share->setTarget($target);
736
+            }
737
+        } catch (AlreadySharedException $e) {
738
+            // If a share for the same target already exists, dont create a new one,
739
+            // but do trigger the hooks and notifications again
740
+            $oldShare = $share;
741
+
742
+            // Reuse the node we already have
743
+            $share = $e->getExistingShare();
744
+            $share->setNode($oldShare->getNode());
745
+        }
746
+
747
+        // Post share event
748
+        $this->dispatchEvent(new ShareCreatedEvent($share), 'share created');
749
+
750
+        // Send email if needed
751
+        if ($this->config->getSystemValueBool('sharing.enable_share_mail', true)) {
752
+            if ($share->getMailSend()) {
753
+                $provider = $this->factory->getProviderForType($share->getShareType());
754
+                if ($provider instanceof IShareProviderWithNotification) {
755
+                    $provider->sendMailNotification($share);
756
+                } else {
757
+                    $this->logger->debug('Share notification not sent because the provider does not support it.', ['app' => 'share']);
758
+                }
759
+            } else {
760
+                $this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']);
761
+            }
762
+        } else {
763
+            $this->logger->debug('Share notification not sent because sharing notification emails is disabled.', ['app' => 'share']);
764
+        }
765
+
766
+        return $share;
767
+    }
768
+
769
+    /**
770
+     * Update a share
771
+     *
772
+     * @param IShare $share
773
+     * @return IShare The share object
774
+     * @throws \InvalidArgumentException
775
+     * @throws HintException
776
+     */
777
+    public function updateShare(IShare $share, bool $onlyValid = true) {
778
+        $expirationDateUpdated = false;
779
+
780
+        $this->canShare($share);
781
+
782
+        try {
783
+            $originalShare = $this->getShareById($share->getFullId(), onlyValid: $onlyValid);
784
+        } catch (\UnexpectedValueException $e) {
785
+            throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
786
+        }
787
+
788
+        // We cannot change the share type!
789
+        if ($share->getShareType() !== $originalShare->getShareType()) {
790
+            throw new \InvalidArgumentException($this->l->t('Cannot change share type'));
791
+        }
792
+
793
+        // We can only change the recipient on user shares
794
+        if ($share->getSharedWith() !== $originalShare->getSharedWith()
795
+            && $share->getShareType() !== IShare::TYPE_USER) {
796
+            throw new \InvalidArgumentException($this->l->t('Can only update recipient on user shares'));
797
+        }
798
+
799
+        // Cannot share with the owner
800
+        if ($share->getShareType() === IShare::TYPE_USER
801
+            && $share->getSharedWith() === $share->getShareOwner()) {
802
+            throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
803
+        }
804
+
805
+        $this->generalCreateChecks($share, true);
806
+
807
+        if ($share->getShareType() === IShare::TYPE_USER) {
808
+            $this->userCreateChecks($share);
809
+
810
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
811
+                // Verify the expiration date
812
+                $this->validateExpirationDateInternal($share);
813
+                $expirationDateUpdated = true;
814
+            }
815
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
816
+            $this->groupCreateChecks($share);
817
+
818
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
819
+                // Verify the expiration date
820
+                $this->validateExpirationDateInternal($share);
821
+                $expirationDateUpdated = true;
822
+            }
823
+        } elseif ($share->getShareType() === IShare::TYPE_LINK
824
+            || $share->getShareType() === IShare::TYPE_EMAIL) {
825
+            $this->linkCreateChecks($share);
826
+
827
+            // The new password is not set again if it is the same as the old
828
+            // one, unless when switching from sending by Talk to sending by
829
+            // mail.
830
+            $plainTextPassword = $share->getPassword();
831
+            $updatedPassword = $this->updateSharePasswordIfNeeded($share, $originalShare);
832
+
833
+            /**
834
+             * Cannot enable the getSendPasswordByTalk if there is no password set
835
+             */
836
+            if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) {
837
+                throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk with an empty password'));
838
+            }
839
+
840
+            /**
841
+             * If we're in a mail share, we need to force a password change
842
+             * as either the user is not aware of the password or is already (received by mail)
843
+             * Thus the SendPasswordByTalk feature would not make sense
844
+             */
845
+            if (!$updatedPassword && $share->getShareType() === IShare::TYPE_EMAIL) {
846
+                if (!$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) {
847
+                    throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk without setting a new password'));
848
+                }
849
+                if ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) {
850
+                    throw new \InvalidArgumentException($this->l->t('Cannot disable sending the password by Talk without setting a new password'));
851
+                }
852
+            }
853
+
854
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
855
+                // Verify the expiration date
856
+                $this->validateExpirationDateLink($share);
857
+                $expirationDateUpdated = true;
858
+            }
859
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
860
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
861
+                // Verify the expiration date
862
+                $this->validateExpirationDateInternal($share);
863
+                $expirationDateUpdated = true;
864
+            }
865
+        }
866
+
867
+        $this->pathCreateChecks($share->getNode());
868
+
869
+        // Now update the share!
870
+        $provider = $this->factory->getProviderForType($share->getShareType());
871
+        if ($share->getShareType() === IShare::TYPE_EMAIL) {
872
+            /** @var ShareByMailProvider $provider */
873
+            $share = $provider->update($share, $plainTextPassword);
874
+        } else {
875
+            $share = $provider->update($share);
876
+        }
877
+
878
+        if ($expirationDateUpdated === true) {
879
+            \OC_Hook::emit(Share::class, 'post_set_expiration_date', [
880
+                'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
881
+                'itemSource' => $share->getNode()->getId(),
882
+                'date' => $share->getExpirationDate(),
883
+                'uidOwner' => $share->getSharedBy(),
884
+            ]);
885
+        }
886
+
887
+        if ($share->getPassword() !== $originalShare->getPassword()) {
888
+            \OC_Hook::emit(Share::class, 'post_update_password', [
889
+                'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
890
+                'itemSource' => $share->getNode()->getId(),
891
+                'uidOwner' => $share->getSharedBy(),
892
+                'token' => $share->getToken(),
893
+                'disabled' => is_null($share->getPassword()),
894
+            ]);
895
+        }
896
+
897
+        if ($share->getPermissions() !== $originalShare->getPermissions()) {
898
+            if ($this->userManager->userExists($share->getShareOwner())) {
899
+                $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
900
+            } else {
901
+                $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
902
+            }
903
+            \OC_Hook::emit(Share::class, 'post_update_permissions', [
904
+                'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
905
+                'itemSource' => $share->getNode()->getId(),
906
+                'shareType' => $share->getShareType(),
907
+                'shareWith' => $share->getSharedWith(),
908
+                'uidOwner' => $share->getSharedBy(),
909
+                'permissions' => $share->getPermissions(),
910
+                'attributes' => $share->getAttributes() !== null ? $share->getAttributes()->toArray() : null,
911
+                'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
912
+            ]);
913
+        }
914
+
915
+        return $share;
916
+    }
917
+
918
+    /**
919
+     * Accept a share.
920
+     *
921
+     * @param IShare $share
922
+     * @param string $recipientId
923
+     * @return IShare The share object
924
+     * @throws \InvalidArgumentException Thrown if the provider does not implement `IShareProviderSupportsAccept`
925
+     * @since 9.0.0
926
+     */
927
+    public function acceptShare(IShare $share, string $recipientId): IShare {
928
+        [$providerId,] = $this->splitFullId($share->getFullId());
929
+        $provider = $this->factory->getProvider($providerId);
930
+
931
+        if (!($provider instanceof IShareProviderSupportsAccept)) {
932
+            throw new \InvalidArgumentException($this->l->t('Share provider does not support accepting'));
933
+        }
934
+        /** @var IShareProvider&IShareProviderSupportsAccept $provider */
935
+        $provider->acceptShare($share, $recipientId);
936
+
937
+        $event = new ShareAcceptedEvent($share);
938
+        $this->dispatchEvent($event, 'share accepted');
939
+
940
+        return $share;
941
+    }
942
+
943
+    /**
944
+     * Updates the password of the given share if it is not the same as the
945
+     * password of the original share.
946
+     *
947
+     * @param IShare $share the share to update its password.
948
+     * @param IShare $originalShare the original share to compare its
949
+     *                              password with.
950
+     * @return boolean whether the password was updated or not.
951
+     */
952
+    private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare) {
953
+        $passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword())
954
+            && (($share->getPassword() !== null && $originalShare->getPassword() === null)
955
+                || ($share->getPassword() === null && $originalShare->getPassword() !== null)
956
+                || ($share->getPassword() !== null && $originalShare->getPassword() !== null
957
+                    && !$this->hasher->verify($share->getPassword(), $originalShare->getPassword())));
958
+
959
+        // Password updated.
960
+        if ($passwordsAreDifferent) {
961
+            // Verify the password
962
+            $this->verifyPassword($share->getPassword());
963
+
964
+            // If a password is set. Hash it!
965
+            if (!empty($share->getPassword())) {
966
+                $share->setPassword($this->hasher->hash($share->getPassword()));
967
+                if ($share->getShareType() === IShare::TYPE_EMAIL) {
968
+                    // Shares shared by email have temporary passwords
969
+                    $this->setSharePasswordExpirationTime($share);
970
+                }
971
+
972
+                return true;
973
+            } else {
974
+                // Empty string and null are seen as NOT password protected
975
+                $share->setPassword(null);
976
+                if ($share->getShareType() === IShare::TYPE_EMAIL) {
977
+                    $share->setPasswordExpirationTime(null);
978
+                }
979
+                return true;
980
+            }
981
+        } else {
982
+            // Reset the password to the original one, as it is either the same
983
+            // as the "new" password or a hashed version of it.
984
+            $share->setPassword($originalShare->getPassword());
985
+        }
986
+
987
+        return false;
988
+    }
989
+
990
+    /**
991
+     * Set the share's password expiration time
992
+     */
993
+    private function setSharePasswordExpirationTime(IShare $share): void {
994
+        if (!$this->config->getSystemValueBool('sharing.enable_mail_link_password_expiration', false)) {
995
+            // Sets password expiration date to NULL
996
+            $share->setPasswordExpirationTime();
997
+            return;
998
+        }
999
+        // Sets password expiration date
1000
+        $expirationTime = null;
1001
+        $now = new \DateTime();
1002
+        $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
1003
+        $expirationTime = $now->add(new \DateInterval('PT' . $expirationInterval . 'S'));
1004
+        $share->setPasswordExpirationTime($expirationTime);
1005
+    }
1006
+
1007
+
1008
+    /**
1009
+     * Delete all the children of this share
1010
+     *
1011
+     * @param IShare $share
1012
+     * @return IShare[] List of deleted shares
1013
+     */
1014
+    protected function deleteChildren(IShare $share) {
1015
+        $deletedShares = [];
1016
+
1017
+        $provider = $this->factory->getProviderForType($share->getShareType());
1018
+
1019
+        foreach ($provider->getChildren($share) as $child) {
1020
+            $this->dispatchEvent(new BeforeShareDeletedEvent($child), 'before share deleted');
1021
+
1022
+            $deletedChildren = $this->deleteChildren($child);
1023
+            $deletedShares = array_merge($deletedShares, $deletedChildren);
1024
+
1025
+            $provider->delete($child);
1026
+            $this->dispatchEvent(new ShareDeletedEvent($child), 'share deleted');
1027
+            $deletedShares[] = $child;
1028
+        }
1029
+
1030
+        return $deletedShares;
1031
+    }
1032
+
1033
+    /** Promote re-shares into direct shares so that target user keeps access */
1034
+    protected function promoteReshares(IShare $share): void {
1035
+        try {
1036
+            $node = $share->getNode();
1037
+        } catch (NotFoundException) {
1038
+            /* Skip if node not found */
1039
+            return;
1040
+        }
1041
+
1042
+        $userIds = [];
1043
+
1044
+        if ($share->getShareType() === IShare::TYPE_USER) {
1045
+            $userIds[] = $share->getSharedWith();
1046
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1047
+            $group = $this->groupManager->get($share->getSharedWith());
1048
+            $users = $group?->getUsers() ?? [];
1049
+
1050
+            foreach ($users as $user) {
1051
+                /* Skip share owner */
1052
+                if ($user->getUID() === $share->getShareOwner() || $user->getUID() === $share->getSharedBy()) {
1053
+                    continue;
1054
+                }
1055
+                $userIds[] = $user->getUID();
1056
+            }
1057
+        } else {
1058
+            /* We only support user and group shares */
1059
+            return;
1060
+        }
1061
+
1062
+        $reshareRecords = [];
1063
+        $shareTypes = [
1064
+            IShare::TYPE_GROUP,
1065
+            IShare::TYPE_USER,
1066
+            IShare::TYPE_LINK,
1067
+            IShare::TYPE_REMOTE,
1068
+            IShare::TYPE_EMAIL,
1069
+        ];
1070
+
1071
+        foreach ($userIds as $userId) {
1072
+            foreach ($shareTypes as $shareType) {
1073
+                try {
1074
+                    $provider = $this->factory->getProviderForType($shareType);
1075
+                } catch (ProviderException $e) {
1076
+                    continue;
1077
+                }
1078
+
1079
+                if ($node instanceof Folder) {
1080
+                    /* We need to get all shares by this user to get subshares */
1081
+                    $shares = $provider->getSharesBy($userId, $shareType, null, false, -1, 0);
1082
+
1083
+                    foreach ($shares as $share) {
1084
+                        try {
1085
+                            $path = $share->getNode()->getPath();
1086
+                        } catch (NotFoundException) {
1087
+                            /* Ignore share of non-existing node */
1088
+                            continue;
1089
+                        }
1090
+                        if ($node->getRelativePath($path) !== null) {
1091
+                            /* If relative path is not null it means the shared node is the same or in a subfolder */
1092
+                            $reshareRecords[] = $share;
1093
+                        }
1094
+                    }
1095
+                } else {
1096
+                    $shares = $provider->getSharesBy($userId, $shareType, $node, false, -1, 0);
1097
+                    foreach ($shares as $child) {
1098
+                        $reshareRecords[] = $child;
1099
+                    }
1100
+                }
1101
+            }
1102
+        }
1103
+
1104
+        foreach ($reshareRecords as $child) {
1105
+            try {
1106
+                /* Check if the share is still valid (means the resharer still has access to the file through another mean) */
1107
+                $this->generalCreateChecks($child);
1108
+            } catch (GenericShareException $e) {
1109
+                /* The check is invalid, promote it to a direct share from the sharer of parent share */
1110
+                $this->logger->debug('Promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1111
+                try {
1112
+                    $child->setSharedBy($share->getSharedBy());
1113
+                    $this->updateShare($child);
1114
+                } catch (GenericShareException|\InvalidArgumentException $e) {
1115
+                    $this->logger->warning('Failed to promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1116
+                }
1117
+            }
1118
+        }
1119
+    }
1120
+
1121
+    /**
1122
+     * Delete a share
1123
+     *
1124
+     * @param IShare $share
1125
+     * @throws ShareNotFound
1126
+     * @throws \InvalidArgumentException
1127
+     */
1128
+    public function deleteShare(IShare $share) {
1129
+        try {
1130
+            $share->getFullId();
1131
+        } catch (\UnexpectedValueException $e) {
1132
+            throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
1133
+        }
1134
+
1135
+        $this->dispatchEvent(new BeforeShareDeletedEvent($share), 'before share deleted');
1136
+
1137
+        // Get all children and delete them as well
1138
+        $this->deleteChildren($share);
1139
+
1140
+        // Do the actual delete
1141
+        $provider = $this->factory->getProviderForType($share->getShareType());
1142
+        $provider->delete($share);
1143
+
1144
+        $this->dispatchEvent(new ShareDeletedEvent($share), 'share deleted');
1145
+
1146
+        // Promote reshares of the deleted share
1147
+        $this->promoteReshares($share);
1148
+    }
1149
+
1150
+
1151
+    /**
1152
+     * Unshare a file as the recipient.
1153
+     * This can be different from a regular delete for example when one of
1154
+     * the users in a groups deletes that share. But the provider should
1155
+     * handle this.
1156
+     *
1157
+     * @param IShare $share
1158
+     * @param string $recipientId
1159
+     */
1160
+    public function deleteFromSelf(IShare $share, $recipientId) {
1161
+        [$providerId,] = $this->splitFullId($share->getFullId());
1162
+        $provider = $this->factory->getProvider($providerId);
1163
+
1164
+        $provider->deleteFromSelf($share, $recipientId);
1165
+        $event = new ShareDeletedFromSelfEvent($share);
1166
+        $this->dispatchEvent($event, 'leave share');
1167
+    }
1168
+
1169
+    public function restoreShare(IShare $share, string $recipientId): IShare {
1170
+        [$providerId,] = $this->splitFullId($share->getFullId());
1171
+        $provider = $this->factory->getProvider($providerId);
1172
+
1173
+        return $provider->restore($share, $recipientId);
1174
+    }
1175
+
1176
+    /**
1177
+     * @inheritdoc
1178
+     */
1179
+    public function moveShare(IShare $share, $recipientId) {
1180
+        if ($share->getShareType() === IShare::TYPE_LINK
1181
+            || $share->getShareType() === IShare::TYPE_EMAIL) {
1182
+            throw new \InvalidArgumentException($this->l->t('Cannot change target of link share'));
1183
+        }
1184
+
1185
+        if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) {
1186
+            throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1187
+        }
1188
+
1189
+        if ($share->getShareType() === IShare::TYPE_GROUP) {
1190
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
1191
+            if (is_null($sharedWith)) {
1192
+                throw new \InvalidArgumentException($this->l->t('Group "%s" does not exist', [$share->getSharedWith()]));
1193
+            }
1194
+            $recipient = $this->userManager->get($recipientId);
1195
+            if (!$sharedWith->inGroup($recipient)) {
1196
+                throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1197
+            }
1198
+        }
1199
+
1200
+        [$providerId,] = $this->splitFullId($share->getFullId());
1201
+        $provider = $this->factory->getProvider($providerId);
1202
+
1203
+        return $provider->move($share, $recipientId);
1204
+    }
1205
+
1206
+    public function getSharesInFolder($userId, Folder $node, $reshares = false, $shallow = true) {
1207
+        $providers = $this->factory->getAllProviders();
1208
+        if (!$shallow) {
1209
+            throw new \Exception('non-shallow getSharesInFolder is no longer supported');
1210
+        }
1211
+
1212
+        $isOwnerless = $node->getMountPoint() instanceof IShareOwnerlessMount;
1213
+
1214
+        $shares = [];
1215
+        foreach ($providers as $provider) {
1216
+            if ($isOwnerless) {
1217
+                // If the provider does not implement the additional interface,
1218
+                // we lack a performant way of querying all shares and therefore ignore the provider.
1219
+                if ($provider instanceof IShareProviderSupportsAllSharesInFolder) {
1220
+                    foreach ($provider->getAllSharesInFolder($node) as $fid => $data) {
1221
+                        $shares[$fid] ??= [];
1222
+                        $shares[$fid] = array_merge($shares[$fid], $data);
1223
+                    }
1224
+                }
1225
+            } else {
1226
+                foreach ($provider->getSharesInFolder($userId, $node, $reshares) as $fid => $data) {
1227
+                    $shares[$fid] ??= [];
1228
+                    $shares[$fid] = array_merge($shares[$fid], $data);
1229
+                }
1230
+            }
1231
+        }
1232
+
1233
+        return $shares;
1234
+    }
1235
+
1236
+    /**
1237
+     * @inheritdoc
1238
+     */
1239
+    public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, bool $onlyValid = true) {
1240
+        if ($path !== null
1241
+            && !($path instanceof \OCP\Files\File)
1242
+            && !($path instanceof \OCP\Files\Folder)) {
1243
+            throw new \InvalidArgumentException($this->l->t('Invalid path'));
1244
+        }
1245
+
1246
+        try {
1247
+            $provider = $this->factory->getProviderForType($shareType);
1248
+        } catch (ProviderException $e) {
1249
+            return [];
1250
+        }
1251
+
1252
+        if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1253
+            $shares = array_filter($provider->getSharesByPath($path), static fn (IShare $share) => $share->getShareType() === $shareType);
1254
+        } else {
1255
+            $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1256
+        }
1257
+
1258
+        /*
1259 1259
 		 * Work around so we don't return expired shares but still follow
1260 1260
 		 * proper pagination.
1261 1261
 		 */
1262 1262
 
1263
-		$shares2 = [];
1264
-
1265
-		while (true) {
1266
-			$added = 0;
1267
-			foreach ($shares as $share) {
1268
-				if ($onlyValid) {
1269
-					try {
1270
-						$this->checkShare($share);
1271
-					} catch (ShareNotFound $e) {
1272
-						// Ignore since this basically means the share is deleted
1273
-						continue;
1274
-					}
1275
-				}
1276
-
1277
-				$added++;
1278
-				$shares2[] = $share;
1279
-
1280
-				if (count($shares2) === $limit) {
1281
-					break;
1282
-				}
1283
-			}
1284
-
1285
-			// If we did not fetch more shares than the limit then there are no more shares
1286
-			if (count($shares) < $limit) {
1287
-				break;
1288
-			}
1289
-
1290
-			if (count($shares2) === $limit) {
1291
-				break;
1292
-			}
1293
-
1294
-			// If there was no limit on the select we are done
1295
-			if ($limit === -1) {
1296
-				break;
1297
-			}
1298
-
1299
-			$offset += $added;
1300
-
1301
-			// Fetch again $limit shares
1302
-			if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1303
-				// We already fetched all shares, so end here
1304
-				$shares = [];
1305
-			} else {
1306
-				$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1307
-			}
1308
-
1309
-			// No more shares means we are done
1310
-			if (empty($shares)) {
1311
-				break;
1312
-			}
1313
-		}
1314
-
1315
-		$shares = $shares2;
1316
-
1317
-		return $shares;
1318
-	}
1319
-
1320
-	/**
1321
-	 * @inheritdoc
1322
-	 */
1323
-	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1324
-		try {
1325
-			$provider = $this->factory->getProviderForType($shareType);
1326
-		} catch (ProviderException $e) {
1327
-			return [];
1328
-		}
1329
-
1330
-		$shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1331
-
1332
-		// remove all shares which are already expired
1333
-		foreach ($shares as $key => $share) {
1334
-			try {
1335
-				$this->checkShare($share);
1336
-			} catch (ShareNotFound $e) {
1337
-				unset($shares[$key]);
1338
-			}
1339
-		}
1340
-
1341
-		return $shares;
1342
-	}
1343
-
1344
-	/**
1345
-	 * @inheritdoc
1346
-	 */
1347
-	public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1348
-		$shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1349
-
1350
-		// Only get deleted shares
1351
-		$shares = array_filter($shares, function (IShare $share) {
1352
-			return $share->getPermissions() === 0;
1353
-		});
1354
-
1355
-		// Only get shares where the owner still exists
1356
-		$shares = array_filter($shares, function (IShare $share) {
1357
-			return $this->userManager->userExists($share->getShareOwner());
1358
-		});
1359
-
1360
-		return $shares;
1361
-	}
1362
-
1363
-	/**
1364
-	 * @inheritdoc
1365
-	 */
1366
-	public function getShareById($id, $recipient = null, bool $onlyValid = true) {
1367
-		if ($id === null) {
1368
-			throw new ShareNotFound();
1369
-		}
1370
-
1371
-		[$providerId, $id] = $this->splitFullId($id);
1372
-
1373
-		try {
1374
-			$provider = $this->factory->getProvider($providerId);
1375
-		} catch (ProviderException $e) {
1376
-			throw new ShareNotFound();
1377
-		}
1378
-
1379
-		$share = $provider->getShareById($id, $recipient);
1380
-
1381
-		if ($onlyValid) {
1382
-			$this->checkShare($share);
1383
-		}
1384
-
1385
-		return $share;
1386
-	}
1387
-
1388
-	/**
1389
-	 * Get all the shares for a given path
1390
-	 *
1391
-	 * @param \OCP\Files\Node $path
1392
-	 * @param int $page
1393
-	 * @param int $perPage
1394
-	 *
1395
-	 * @return Share[]
1396
-	 */
1397
-	public function getSharesByPath(\OCP\Files\Node $path, $page = 0, $perPage = 50) {
1398
-		return [];
1399
-	}
1400
-
1401
-	/**
1402
-	 * Get the share by token possible with password
1403
-	 *
1404
-	 * @param string $token
1405
-	 * @return IShare
1406
-	 *
1407
-	 * @throws ShareNotFound
1408
-	 */
1409
-	public function getShareByToken($token) {
1410
-		// tokens cannot be valid local user names
1411
-		if ($this->userManager->userExists($token)) {
1412
-			throw new ShareNotFound();
1413
-		}
1414
-		$share = null;
1415
-		try {
1416
-			if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes') {
1417
-				$provider = $this->factory->getProviderForType(IShare::TYPE_LINK);
1418
-				$share = $provider->getShareByToken($token);
1419
-			}
1420
-		} catch (ProviderException $e) {
1421
-		} catch (ShareNotFound $e) {
1422
-		}
1423
-
1424
-
1425
-		// If it is not a link share try to fetch a federated share by token
1426
-		if ($share === null) {
1427
-			try {
1428
-				$provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE);
1429
-				$share = $provider->getShareByToken($token);
1430
-			} catch (ProviderException $e) {
1431
-			} catch (ShareNotFound $e) {
1432
-			}
1433
-		}
1434
-
1435
-		// If it is not a link share try to fetch a mail share by token
1436
-		if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
1437
-			try {
1438
-				$provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL);
1439
-				$share = $provider->getShareByToken($token);
1440
-			} catch (ProviderException $e) {
1441
-			} catch (ShareNotFound $e) {
1442
-			}
1443
-		}
1444
-
1445
-		if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
1446
-			try {
1447
-				$provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE);
1448
-				$share = $provider->getShareByToken($token);
1449
-			} catch (ProviderException $e) {
1450
-			} catch (ShareNotFound $e) {
1451
-			}
1452
-		}
1453
-
1454
-		if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) {
1455
-			try {
1456
-				$provider = $this->factory->getProviderForType(IShare::TYPE_ROOM);
1457
-				$share = $provider->getShareByToken($token);
1458
-			} catch (ProviderException $e) {
1459
-			} catch (ShareNotFound $e) {
1460
-			}
1461
-		}
1462
-
1463
-		if ($share === null) {
1464
-			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1465
-		}
1466
-
1467
-		$this->checkShare($share);
1468
-
1469
-		/*
1263
+        $shares2 = [];
1264
+
1265
+        while (true) {
1266
+            $added = 0;
1267
+            foreach ($shares as $share) {
1268
+                if ($onlyValid) {
1269
+                    try {
1270
+                        $this->checkShare($share);
1271
+                    } catch (ShareNotFound $e) {
1272
+                        // Ignore since this basically means the share is deleted
1273
+                        continue;
1274
+                    }
1275
+                }
1276
+
1277
+                $added++;
1278
+                $shares2[] = $share;
1279
+
1280
+                if (count($shares2) === $limit) {
1281
+                    break;
1282
+                }
1283
+            }
1284
+
1285
+            // If we did not fetch more shares than the limit then there are no more shares
1286
+            if (count($shares) < $limit) {
1287
+                break;
1288
+            }
1289
+
1290
+            if (count($shares2) === $limit) {
1291
+                break;
1292
+            }
1293
+
1294
+            // If there was no limit on the select we are done
1295
+            if ($limit === -1) {
1296
+                break;
1297
+            }
1298
+
1299
+            $offset += $added;
1300
+
1301
+            // Fetch again $limit shares
1302
+            if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1303
+                // We already fetched all shares, so end here
1304
+                $shares = [];
1305
+            } else {
1306
+                $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1307
+            }
1308
+
1309
+            // No more shares means we are done
1310
+            if (empty($shares)) {
1311
+                break;
1312
+            }
1313
+        }
1314
+
1315
+        $shares = $shares2;
1316
+
1317
+        return $shares;
1318
+    }
1319
+
1320
+    /**
1321
+     * @inheritdoc
1322
+     */
1323
+    public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1324
+        try {
1325
+            $provider = $this->factory->getProviderForType($shareType);
1326
+        } catch (ProviderException $e) {
1327
+            return [];
1328
+        }
1329
+
1330
+        $shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1331
+
1332
+        // remove all shares which are already expired
1333
+        foreach ($shares as $key => $share) {
1334
+            try {
1335
+                $this->checkShare($share);
1336
+            } catch (ShareNotFound $e) {
1337
+                unset($shares[$key]);
1338
+            }
1339
+        }
1340
+
1341
+        return $shares;
1342
+    }
1343
+
1344
+    /**
1345
+     * @inheritdoc
1346
+     */
1347
+    public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1348
+        $shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1349
+
1350
+        // Only get deleted shares
1351
+        $shares = array_filter($shares, function (IShare $share) {
1352
+            return $share->getPermissions() === 0;
1353
+        });
1354
+
1355
+        // Only get shares where the owner still exists
1356
+        $shares = array_filter($shares, function (IShare $share) {
1357
+            return $this->userManager->userExists($share->getShareOwner());
1358
+        });
1359
+
1360
+        return $shares;
1361
+    }
1362
+
1363
+    /**
1364
+     * @inheritdoc
1365
+     */
1366
+    public function getShareById($id, $recipient = null, bool $onlyValid = true) {
1367
+        if ($id === null) {
1368
+            throw new ShareNotFound();
1369
+        }
1370
+
1371
+        [$providerId, $id] = $this->splitFullId($id);
1372
+
1373
+        try {
1374
+            $provider = $this->factory->getProvider($providerId);
1375
+        } catch (ProviderException $e) {
1376
+            throw new ShareNotFound();
1377
+        }
1378
+
1379
+        $share = $provider->getShareById($id, $recipient);
1380
+
1381
+        if ($onlyValid) {
1382
+            $this->checkShare($share);
1383
+        }
1384
+
1385
+        return $share;
1386
+    }
1387
+
1388
+    /**
1389
+     * Get all the shares for a given path
1390
+     *
1391
+     * @param \OCP\Files\Node $path
1392
+     * @param int $page
1393
+     * @param int $perPage
1394
+     *
1395
+     * @return Share[]
1396
+     */
1397
+    public function getSharesByPath(\OCP\Files\Node $path, $page = 0, $perPage = 50) {
1398
+        return [];
1399
+    }
1400
+
1401
+    /**
1402
+     * Get the share by token possible with password
1403
+     *
1404
+     * @param string $token
1405
+     * @return IShare
1406
+     *
1407
+     * @throws ShareNotFound
1408
+     */
1409
+    public function getShareByToken($token) {
1410
+        // tokens cannot be valid local user names
1411
+        if ($this->userManager->userExists($token)) {
1412
+            throw new ShareNotFound();
1413
+        }
1414
+        $share = null;
1415
+        try {
1416
+            if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes') {
1417
+                $provider = $this->factory->getProviderForType(IShare::TYPE_LINK);
1418
+                $share = $provider->getShareByToken($token);
1419
+            }
1420
+        } catch (ProviderException $e) {
1421
+        } catch (ShareNotFound $e) {
1422
+        }
1423
+
1424
+
1425
+        // If it is not a link share try to fetch a federated share by token
1426
+        if ($share === null) {
1427
+            try {
1428
+                $provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE);
1429
+                $share = $provider->getShareByToken($token);
1430
+            } catch (ProviderException $e) {
1431
+            } catch (ShareNotFound $e) {
1432
+            }
1433
+        }
1434
+
1435
+        // If it is not a link share try to fetch a mail share by token
1436
+        if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
1437
+            try {
1438
+                $provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL);
1439
+                $share = $provider->getShareByToken($token);
1440
+            } catch (ProviderException $e) {
1441
+            } catch (ShareNotFound $e) {
1442
+            }
1443
+        }
1444
+
1445
+        if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
1446
+            try {
1447
+                $provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE);
1448
+                $share = $provider->getShareByToken($token);
1449
+            } catch (ProviderException $e) {
1450
+            } catch (ShareNotFound $e) {
1451
+            }
1452
+        }
1453
+
1454
+        if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) {
1455
+            try {
1456
+                $provider = $this->factory->getProviderForType(IShare::TYPE_ROOM);
1457
+                $share = $provider->getShareByToken($token);
1458
+            } catch (ProviderException $e) {
1459
+            } catch (ShareNotFound $e) {
1460
+            }
1461
+        }
1462
+
1463
+        if ($share === null) {
1464
+            throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1465
+        }
1466
+
1467
+        $this->checkShare($share);
1468
+
1469
+        /*
1470 1470
 		 * Reduce the permissions for link or email shares if public upload is not enabled
1471 1471
 		 */
1472
-		if (($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL)
1473
-			&& $share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()) {
1474
-			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1475
-		}
1476
-
1477
-		return $share;
1478
-	}
1479
-
1480
-	/**
1481
-	 * Check expire date and disabled owner
1482
-	 *
1483
-	 * @throws ShareNotFound
1484
-	 */
1485
-	protected function checkShare(IShare $share): void {
1486
-		if ($share->isExpired()) {
1487
-			$this->deleteShare($share);
1488
-			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1489
-		}
1490
-		if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
1491
-			$uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
1492
-			foreach ($uids as $uid) {
1493
-				$user = $this->userManager->get($uid);
1494
-				if ($user?->isEnabled() === false) {
1495
-					throw new ShareNotFound($this->l->t('The requested share comes from a disabled user'));
1496
-				}
1497
-			}
1498
-		}
1499
-
1500
-		// For link and email shares, verify the share owner can still create such shares
1501
-		if ($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL) {
1502
-			$shareOwner = $this->userManager->get($share->getShareOwner());
1503
-			if ($shareOwner === null) {
1504
-				throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1505
-			}
1506
-			if (!$this->userCanCreateLinkShares($shareOwner)) {
1507
-				throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1508
-			}
1509
-		}
1510
-	}
1511
-
1512
-	/**
1513
-	 * Verify the password of a public share
1514
-	 *
1515
-	 * @param IShare $share
1516
-	 * @param ?string $password
1517
-	 * @return bool
1518
-	 */
1519
-	public function checkPassword(IShare $share, $password) {
1520
-
1521
-		// if there is no password on the share object / passsword is null, there is nothing to check
1522
-		if ($password === null || $share->getPassword() === null) {
1523
-			return false;
1524
-		}
1525
-
1526
-		// Makes sure password hasn't expired
1527
-		$expirationTime = $share->getPasswordExpirationTime();
1528
-		if ($expirationTime !== null && $expirationTime < new \DateTime()) {
1529
-			return false;
1530
-		}
1531
-
1532
-		$newHash = '';
1533
-		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1534
-			return false;
1535
-		}
1536
-
1537
-		if (!empty($newHash)) {
1538
-			$share->setPassword($newHash);
1539
-			$provider = $this->factory->getProviderForType($share->getShareType());
1540
-			$provider->update($share);
1541
-		}
1542
-
1543
-		return true;
1544
-	}
1545
-
1546
-	/**
1547
-	 * @inheritdoc
1548
-	 */
1549
-	public function userDeleted($uid) {
1550
-		$types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
1551
-
1552
-		foreach ($types as $type) {
1553
-			try {
1554
-				$provider = $this->factory->getProviderForType($type);
1555
-			} catch (ProviderException $e) {
1556
-				continue;
1557
-			}
1558
-			$provider->userDeleted($uid, $type);
1559
-		}
1560
-	}
1561
-
1562
-	/**
1563
-	 * @inheritdoc
1564
-	 */
1565
-	public function groupDeleted($gid) {
1566
-		foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1567
-			try {
1568
-				$provider = $this->factory->getProviderForType($type);
1569
-			} catch (ProviderException $e) {
1570
-				continue;
1571
-			}
1572
-			$provider->groupDeleted($gid);
1573
-		}
1574
-
1575
-		$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1576
-		if ($excludedGroups === '') {
1577
-			return;
1578
-		}
1579
-
1580
-		$excludedGroups = json_decode($excludedGroups, true);
1581
-		if (json_last_error() !== JSON_ERROR_NONE) {
1582
-			return;
1583
-		}
1584
-
1585
-		$excludedGroups = array_diff($excludedGroups, [$gid]);
1586
-		$this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
1587
-	}
1588
-
1589
-	/**
1590
-	 * @inheritdoc
1591
-	 */
1592
-	public function userDeletedFromGroup($uid, $gid) {
1593
-		foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1594
-			try {
1595
-				$provider = $this->factory->getProviderForType($type);
1596
-			} catch (ProviderException $e) {
1597
-				continue;
1598
-			}
1599
-			$provider->userDeletedFromGroup($uid, $gid);
1600
-		}
1601
-	}
1602
-
1603
-	#[\Override]
1604
-	public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1605
-		$owner = $path->getOwner();
1606
-
1607
-		if ($owner === null) {
1608
-			return [];
1609
-		}
1610
-
1611
-		$owner = $owner->getUID();
1612
-
1613
-		if ($currentAccess) {
1614
-			$al = ['users' => [], 'remote' => [], 'public' => false, 'mail' => []];
1615
-		} else {
1616
-			$al = ['users' => [], 'remote' => false, 'public' => false, 'mail' => []];
1617
-		}
1618
-		if (!$this->userManager->userExists($owner)) {
1619
-			return $al;
1620
-		}
1621
-
1622
-		//Get node for the owner and correct the owner in case of external storage
1623
-		$userFolder = $this->rootFolder->getUserFolder($owner);
1624
-		if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1625
-			$path = $userFolder->getFirstNodeById($path->getId());
1626
-			if ($path === null || $path->getOwner() === null) {
1627
-				return [];
1628
-			}
1629
-			$owner = $path->getOwner()->getUID();
1630
-		}
1631
-
1632
-		$providers = $this->factory->getAllProviders();
1633
-
1634
-		/** @var Node[] $nodes */
1635
-		$nodes = [];
1636
-
1637
-
1638
-		if ($currentAccess) {
1639
-			$ownerPath = $path->getPath();
1640
-			$ownerPath = explode('/', $ownerPath, 4);
1641
-			if (count($ownerPath) < 4) {
1642
-				$ownerPath = '';
1643
-			} else {
1644
-				$ownerPath = $ownerPath[3];
1645
-			}
1646
-			$al['users'][$owner] = [
1647
-				'node_id' => $path->getId(),
1648
-				'node_path' => '/' . $ownerPath,
1649
-			];
1650
-		} else {
1651
-			$al['users'][] = $owner;
1652
-		}
1653
-
1654
-		// Collect all the shares
1655
-		while ($path->getPath() !== $userFolder->getPath()) {
1656
-			$nodes[] = $path;
1657
-			if (!$recursive) {
1658
-				break;
1659
-			}
1660
-			$path = $path->getParent();
1661
-		}
1662
-
1663
-		foreach ($providers as $provider) {
1664
-			$tmp = $provider->getAccessList($nodes, $currentAccess);
1665
-
1666
-			foreach ($tmp as $k => $v) {
1667
-				if (isset($al[$k])) {
1668
-					if (is_array($al[$k])) {
1669
-						if ($currentAccess) {
1670
-							$al[$k] += $v;
1671
-						} else {
1672
-							$al[$k] = array_merge($al[$k], $v);
1673
-							$al[$k] = array_unique($al[$k]);
1674
-							$al[$k] = array_values($al[$k]);
1675
-						}
1676
-					} else {
1677
-						$al[$k] = $al[$k] || $v;
1678
-					}
1679
-				} else {
1680
-					$al[$k] = $v;
1681
-				}
1682
-			}
1683
-		}
1684
-
1685
-		return $al;
1686
-	}
1687
-
1688
-	/**
1689
-	 * Create a new share
1690
-	 *
1691
-	 * @return IShare
1692
-	 */
1693
-	public function newShare() {
1694
-		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1695
-	}
1696
-
1697
-	/**
1698
-	 * Is the share API enabled
1699
-	 *
1700
-	 * @return bool
1701
-	 */
1702
-	public function shareApiEnabled() {
1703
-		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1704
-	}
1705
-
1706
-	/**
1707
-	 * Is public link sharing enabled
1708
-	 *
1709
-	 * @param ?IUser $user User to check against group exclusions, defaults to current session user
1710
-	 * @return bool
1711
-	 */
1712
-	public function shareApiAllowLinks(?IUser $user = null) {
1713
-		if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1714
-			return false;
1715
-		}
1716
-
1717
-		$user = $user ?? $this->userSession->getUser();
1718
-		if ($user) {
1719
-			$excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
1720
-			if ($excludedGroups) {
1721
-				$userGroups = $this->groupManager->getUserGroupIds($user);
1722
-				return !(bool)array_intersect($excludedGroups, $userGroups);
1723
-			}
1724
-		}
1725
-
1726
-		return true;
1727
-	}
1728
-
1729
-	/**
1730
-	 * Check if a specific user can create link shares
1731
-	 *
1732
-	 * @param IUser $user The user to check
1733
-	 * @return bool
1734
-	 */
1735
-	protected function userCanCreateLinkShares(IUser $user): bool {
1736
-		return $this->shareApiAllowLinks($user);
1737
-	}
1738
-
1739
-	/**
1740
-	 * Is password on public link requires
1741
-	 *
1742
-	 * @param bool Check group membership exclusion
1743
-	 * @return bool
1744
-	 */
1745
-	public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true) {
1746
-		$excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
1747
-		if ($excludedGroups !== '' && $checkGroupMembership) {
1748
-			$excludedGroups = json_decode($excludedGroups);
1749
-			$user = $this->userSession->getUser();
1750
-			if ($user) {
1751
-				$userGroups = $this->groupManager->getUserGroupIds($user);
1752
-				if ((bool)array_intersect($excludedGroups, $userGroups)) {
1753
-					return false;
1754
-				}
1755
-			}
1756
-		}
1757
-		return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_ENFORCED);
1758
-	}
1759
-
1760
-	/**
1761
-	 * Is default link expire date enabled
1762
-	 *
1763
-	 * @return bool
1764
-	 */
1765
-	public function shareApiLinkDefaultExpireDate() {
1766
-		return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_DEFAULT);
1767
-	}
1768
-
1769
-	/**
1770
-	 * Is default link expire date enforced
1771
-	 *
1772
-	 * @return bool
1773
-	 */
1774
-	public function shareApiLinkDefaultExpireDateEnforced() {
1775
-		return $this->shareApiLinkDefaultExpireDate()
1776
-			&& $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_ENFORCED);
1777
-	}
1778
-
1779
-
1780
-	/**
1781
-	 * Number of default link expire days
1782
-	 *
1783
-	 * @return int
1784
-	 */
1785
-	public function shareApiLinkDefaultExpireDays() {
1786
-		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1787
-	}
1788
-
1789
-	/**
1790
-	 * Is default internal expire date enabled
1791
-	 *
1792
-	 * @return bool
1793
-	 */
1794
-	public function shareApiInternalDefaultExpireDate(): bool {
1795
-		return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
1796
-	}
1797
-
1798
-	/**
1799
-	 * Is default remote expire date enabled
1800
-	 *
1801
-	 * @return bool
1802
-	 */
1803
-	public function shareApiRemoteDefaultExpireDate(): bool {
1804
-		return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
1805
-	}
1806
-
1807
-	/**
1808
-	 * Is default expire date enforced
1809
-	 *
1810
-	 * @return bool
1811
-	 */
1812
-	public function shareApiInternalDefaultExpireDateEnforced(): bool {
1813
-		return $this->shareApiInternalDefaultExpireDate()
1814
-			&& $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
1815
-	}
1816
-
1817
-	/**
1818
-	 * Is default expire date enforced for remote shares
1819
-	 *
1820
-	 * @return bool
1821
-	 */
1822
-	public function shareApiRemoteDefaultExpireDateEnforced(): bool {
1823
-		return $this->shareApiRemoteDefaultExpireDate()
1824
-			&& $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
1825
-	}
1826
-
1827
-	/**
1828
-	 * Number of default expire days
1829
-	 *
1830
-	 * @return int
1831
-	 */
1832
-	public function shareApiInternalDefaultExpireDays(): int {
1833
-		return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
1834
-	}
1835
-
1836
-	/**
1837
-	 * Number of default expire days for remote shares
1838
-	 *
1839
-	 * @return int
1840
-	 */
1841
-	public function shareApiRemoteDefaultExpireDays(): int {
1842
-		return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
1843
-	}
1844
-
1845
-	/**
1846
-	 * Allow public upload on link shares
1847
-	 *
1848
-	 * @return bool
1849
-	 */
1850
-	public function shareApiLinkAllowPublicUpload() {
1851
-		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1852
-	}
1853
-
1854
-	/**
1855
-	 * check if user can only share with group members
1856
-	 *
1857
-	 * @return bool
1858
-	 */
1859
-	public function shareWithGroupMembersOnly() {
1860
-		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1861
-	}
1862
-
1863
-	/**
1864
-	 * If shareWithGroupMembersOnly is enabled, return an optional
1865
-	 * list of groups that must be excluded from the principle of
1866
-	 * belonging to the same group.
1867
-	 *
1868
-	 * @return array
1869
-	 */
1870
-	public function shareWithGroupMembersOnlyExcludeGroupsList() {
1871
-		if (!$this->shareWithGroupMembersOnly()) {
1872
-			return [];
1873
-		}
1874
-		$excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
1875
-		return json_decode($excludeGroups, true) ?? [];
1876
-	}
1877
-
1878
-	/**
1879
-	 * Check if users can share with groups
1880
-	 *
1881
-	 * @return bool
1882
-	 */
1883
-	public function allowGroupSharing() {
1884
-		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1885
-	}
1886
-
1887
-	public function allowEnumeration(): bool {
1888
-		return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
1889
-	}
1890
-
1891
-	public function limitEnumerationToGroups(): bool {
1892
-		return $this->allowEnumeration()
1893
-			&& $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
1894
-	}
1895
-
1896
-	public function limitEnumerationToPhone(): bool {
1897
-		return $this->allowEnumeration()
1898
-			&& $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
1899
-	}
1900
-
1901
-	public function allowEnumerationFullMatch(): bool {
1902
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
1903
-	}
1904
-
1905
-	public function matchEmail(): bool {
1906
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
1907
-	}
1908
-
1909
-	public function matchUserId(): bool {
1910
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes') === 'yes';
1911
-	}
1912
-
1913
-	public function ignoreSecondDisplayName(): bool {
1914
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
1915
-	}
1916
-
1917
-	public function allowCustomTokens(): bool {
1918
-		return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_CUSTOM_TOKEN);
1919
-	}
1920
-
1921
-	public function allowViewWithoutDownload(): bool {
1922
-		return $this->appConfig->getValueBool('core', 'shareapi_allow_view_without_download', true);
1923
-	}
1924
-
1925
-	public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool {
1926
-		if ($this->allowEnumerationFullMatch()) {
1927
-			return true;
1928
-		}
1929
-
1930
-		if (!$this->allowEnumeration()) {
1931
-			return false;
1932
-		}
1933
-
1934
-		if (!$this->limitEnumerationToPhone() && !$this->limitEnumerationToGroups()) {
1935
-			// Enumeration is enabled and not restricted: OK
1936
-			return true;
1937
-		}
1938
-
1939
-		if (!$currentUser instanceof IUser) {
1940
-			// Enumeration restrictions require an account
1941
-			return false;
1942
-		}
1943
-
1944
-		// Enumeration is limited to phone match
1945
-		if ($this->limitEnumerationToPhone() && $this->knownUserService->isKnownToUser($currentUser->getUID(), $targetUser->getUID())) {
1946
-			return true;
1947
-		}
1948
-
1949
-		// Enumeration is limited to groups
1950
-		if ($this->limitEnumerationToGroups()) {
1951
-			$currentUserGroupIds = $this->groupManager->getUserGroupIds($currentUser);
1952
-			$targetUserGroupIds = $this->groupManager->getUserGroupIds($targetUser);
1953
-			if (!empty(array_intersect($currentUserGroupIds, $targetUserGroupIds))) {
1954
-				return true;
1955
-			}
1956
-		}
1957
-
1958
-		return false;
1959
-	}
1960
-
1961
-	/**
1962
-	 * Check if sharing is disabled for the current user
1963
-	 */
1964
-	public function sharingDisabledForUser(?string $userId): bool {
1965
-		return $this->shareDisableChecker->sharingDisabledForUser($userId);
1966
-	}
1967
-
1968
-	/**
1969
-	 * @inheritdoc
1970
-	 */
1971
-	public function outgoingServer2ServerSharesAllowed() {
1972
-		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1973
-	}
1974
-
1975
-	/**
1976
-	 * @inheritdoc
1977
-	 */
1978
-	public function outgoingServer2ServerGroupSharesAllowed() {
1979
-		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1980
-	}
1981
-
1982
-	/**
1983
-	 * @inheritdoc
1984
-	 */
1985
-	public function shareProviderExists($shareType) {
1986
-		try {
1987
-			$this->factory->getProviderForType($shareType);
1988
-		} catch (ProviderException $e) {
1989
-			return false;
1990
-		}
1991
-
1992
-		return true;
1993
-	}
1994
-
1995
-	public function registerShareProvider(string $shareProviderClass): void {
1996
-		$this->factory->registerProvider($shareProviderClass);
1997
-	}
1998
-
1999
-	public function getAllShares(): iterable {
2000
-		$providers = $this->factory->getAllProviders();
2001
-
2002
-		foreach ($providers as $provider) {
2003
-			yield from $provider->getAllShares();
2004
-		}
2005
-	}
2006
-
2007
-	public function generateToken(): string {
2008
-		// Initial token length
2009
-		$tokenLength = \OC\Share\Helper::getTokenLength();
2010
-
2011
-		do {
2012
-			$tokenExists = false;
2013
-
2014
-			for ($i = 0; $i <= 2; $i++) {
2015
-				// Generate a new token
2016
-				$token = $this->secureRandom->generate(
2017
-					$tokenLength,
2018
-					ISecureRandom::CHAR_HUMAN_READABLE,
2019
-				);
2020
-
2021
-				try {
2022
-					// Try to fetch a share with the generated token
2023
-					$this->getShareByToken($token);
2024
-					$tokenExists = true; // Token exists, we need to try again
2025
-				} catch (ShareNotFound $e) {
2026
-					// Token is unique, exit the loop
2027
-					$tokenExists = false;
2028
-					break;
2029
-				}
2030
-			}
2031
-
2032
-			// If we've reached the maximum attempts and the token still exists, increase the token length
2033
-			if ($tokenExists) {
2034
-				$tokenLength++;
2035
-
2036
-				// Check if the token length exceeds the maximum allowed length
2037
-				if ($tokenLength > \OC\Share\Constants::MAX_TOKEN_LENGTH) {
2038
-					throw new ShareTokenException('Unable to generate a unique share token. Maximum token length exceeded.');
2039
-				}
2040
-			}
2041
-		} while ($tokenExists);
2042
-
2043
-		return $token;
2044
-	}
2045
-
2046
-	private function dispatchEvent(Event $event, string $name): void {
2047
-		try {
2048
-			$this->dispatcher->dispatchTyped($event);
2049
-		} catch (\Exception $e) {
2050
-			$this->logger->error("Error while sending ' . $name . ' event", ['exception' => $e]);
2051
-		}
2052
-	}
1472
+        if (($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL)
1473
+            && $share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()) {
1474
+            $share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1475
+        }
1476
+
1477
+        return $share;
1478
+    }
1479
+
1480
+    /**
1481
+     * Check expire date and disabled owner
1482
+     *
1483
+     * @throws ShareNotFound
1484
+     */
1485
+    protected function checkShare(IShare $share): void {
1486
+        if ($share->isExpired()) {
1487
+            $this->deleteShare($share);
1488
+            throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1489
+        }
1490
+        if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
1491
+            $uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
1492
+            foreach ($uids as $uid) {
1493
+                $user = $this->userManager->get($uid);
1494
+                if ($user?->isEnabled() === false) {
1495
+                    throw new ShareNotFound($this->l->t('The requested share comes from a disabled user'));
1496
+                }
1497
+            }
1498
+        }
1499
+
1500
+        // For link and email shares, verify the share owner can still create such shares
1501
+        if ($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL) {
1502
+            $shareOwner = $this->userManager->get($share->getShareOwner());
1503
+            if ($shareOwner === null) {
1504
+                throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1505
+            }
1506
+            if (!$this->userCanCreateLinkShares($shareOwner)) {
1507
+                throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1508
+            }
1509
+        }
1510
+    }
1511
+
1512
+    /**
1513
+     * Verify the password of a public share
1514
+     *
1515
+     * @param IShare $share
1516
+     * @param ?string $password
1517
+     * @return bool
1518
+     */
1519
+    public function checkPassword(IShare $share, $password) {
1520
+
1521
+        // if there is no password on the share object / passsword is null, there is nothing to check
1522
+        if ($password === null || $share->getPassword() === null) {
1523
+            return false;
1524
+        }
1525
+
1526
+        // Makes sure password hasn't expired
1527
+        $expirationTime = $share->getPasswordExpirationTime();
1528
+        if ($expirationTime !== null && $expirationTime < new \DateTime()) {
1529
+            return false;
1530
+        }
1531
+
1532
+        $newHash = '';
1533
+        if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1534
+            return false;
1535
+        }
1536
+
1537
+        if (!empty($newHash)) {
1538
+            $share->setPassword($newHash);
1539
+            $provider = $this->factory->getProviderForType($share->getShareType());
1540
+            $provider->update($share);
1541
+        }
1542
+
1543
+        return true;
1544
+    }
1545
+
1546
+    /**
1547
+     * @inheritdoc
1548
+     */
1549
+    public function userDeleted($uid) {
1550
+        $types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
1551
+
1552
+        foreach ($types as $type) {
1553
+            try {
1554
+                $provider = $this->factory->getProviderForType($type);
1555
+            } catch (ProviderException $e) {
1556
+                continue;
1557
+            }
1558
+            $provider->userDeleted($uid, $type);
1559
+        }
1560
+    }
1561
+
1562
+    /**
1563
+     * @inheritdoc
1564
+     */
1565
+    public function groupDeleted($gid) {
1566
+        foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1567
+            try {
1568
+                $provider = $this->factory->getProviderForType($type);
1569
+            } catch (ProviderException $e) {
1570
+                continue;
1571
+            }
1572
+            $provider->groupDeleted($gid);
1573
+        }
1574
+
1575
+        $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1576
+        if ($excludedGroups === '') {
1577
+            return;
1578
+        }
1579
+
1580
+        $excludedGroups = json_decode($excludedGroups, true);
1581
+        if (json_last_error() !== JSON_ERROR_NONE) {
1582
+            return;
1583
+        }
1584
+
1585
+        $excludedGroups = array_diff($excludedGroups, [$gid]);
1586
+        $this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
1587
+    }
1588
+
1589
+    /**
1590
+     * @inheritdoc
1591
+     */
1592
+    public function userDeletedFromGroup($uid, $gid) {
1593
+        foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1594
+            try {
1595
+                $provider = $this->factory->getProviderForType($type);
1596
+            } catch (ProviderException $e) {
1597
+                continue;
1598
+            }
1599
+            $provider->userDeletedFromGroup($uid, $gid);
1600
+        }
1601
+    }
1602
+
1603
+    #[\Override]
1604
+    public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1605
+        $owner = $path->getOwner();
1606
+
1607
+        if ($owner === null) {
1608
+            return [];
1609
+        }
1610
+
1611
+        $owner = $owner->getUID();
1612
+
1613
+        if ($currentAccess) {
1614
+            $al = ['users' => [], 'remote' => [], 'public' => false, 'mail' => []];
1615
+        } else {
1616
+            $al = ['users' => [], 'remote' => false, 'public' => false, 'mail' => []];
1617
+        }
1618
+        if (!$this->userManager->userExists($owner)) {
1619
+            return $al;
1620
+        }
1621
+
1622
+        //Get node for the owner and correct the owner in case of external storage
1623
+        $userFolder = $this->rootFolder->getUserFolder($owner);
1624
+        if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1625
+            $path = $userFolder->getFirstNodeById($path->getId());
1626
+            if ($path === null || $path->getOwner() === null) {
1627
+                return [];
1628
+            }
1629
+            $owner = $path->getOwner()->getUID();
1630
+        }
1631
+
1632
+        $providers = $this->factory->getAllProviders();
1633
+
1634
+        /** @var Node[] $nodes */
1635
+        $nodes = [];
1636
+
1637
+
1638
+        if ($currentAccess) {
1639
+            $ownerPath = $path->getPath();
1640
+            $ownerPath = explode('/', $ownerPath, 4);
1641
+            if (count($ownerPath) < 4) {
1642
+                $ownerPath = '';
1643
+            } else {
1644
+                $ownerPath = $ownerPath[3];
1645
+            }
1646
+            $al['users'][$owner] = [
1647
+                'node_id' => $path->getId(),
1648
+                'node_path' => '/' . $ownerPath,
1649
+            ];
1650
+        } else {
1651
+            $al['users'][] = $owner;
1652
+        }
1653
+
1654
+        // Collect all the shares
1655
+        while ($path->getPath() !== $userFolder->getPath()) {
1656
+            $nodes[] = $path;
1657
+            if (!$recursive) {
1658
+                break;
1659
+            }
1660
+            $path = $path->getParent();
1661
+        }
1662
+
1663
+        foreach ($providers as $provider) {
1664
+            $tmp = $provider->getAccessList($nodes, $currentAccess);
1665
+
1666
+            foreach ($tmp as $k => $v) {
1667
+                if (isset($al[$k])) {
1668
+                    if (is_array($al[$k])) {
1669
+                        if ($currentAccess) {
1670
+                            $al[$k] += $v;
1671
+                        } else {
1672
+                            $al[$k] = array_merge($al[$k], $v);
1673
+                            $al[$k] = array_unique($al[$k]);
1674
+                            $al[$k] = array_values($al[$k]);
1675
+                        }
1676
+                    } else {
1677
+                        $al[$k] = $al[$k] || $v;
1678
+                    }
1679
+                } else {
1680
+                    $al[$k] = $v;
1681
+                }
1682
+            }
1683
+        }
1684
+
1685
+        return $al;
1686
+    }
1687
+
1688
+    /**
1689
+     * Create a new share
1690
+     *
1691
+     * @return IShare
1692
+     */
1693
+    public function newShare() {
1694
+        return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1695
+    }
1696
+
1697
+    /**
1698
+     * Is the share API enabled
1699
+     *
1700
+     * @return bool
1701
+     */
1702
+    public function shareApiEnabled() {
1703
+        return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1704
+    }
1705
+
1706
+    /**
1707
+     * Is public link sharing enabled
1708
+     *
1709
+     * @param ?IUser $user User to check against group exclusions, defaults to current session user
1710
+     * @return bool
1711
+     */
1712
+    public function shareApiAllowLinks(?IUser $user = null) {
1713
+        if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1714
+            return false;
1715
+        }
1716
+
1717
+        $user = $user ?? $this->userSession->getUser();
1718
+        if ($user) {
1719
+            $excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
1720
+            if ($excludedGroups) {
1721
+                $userGroups = $this->groupManager->getUserGroupIds($user);
1722
+                return !(bool)array_intersect($excludedGroups, $userGroups);
1723
+            }
1724
+        }
1725
+
1726
+        return true;
1727
+    }
1728
+
1729
+    /**
1730
+     * Check if a specific user can create link shares
1731
+     *
1732
+     * @param IUser $user The user to check
1733
+     * @return bool
1734
+     */
1735
+    protected function userCanCreateLinkShares(IUser $user): bool {
1736
+        return $this->shareApiAllowLinks($user);
1737
+    }
1738
+
1739
+    /**
1740
+     * Is password on public link requires
1741
+     *
1742
+     * @param bool Check group membership exclusion
1743
+     * @return bool
1744
+     */
1745
+    public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true) {
1746
+        $excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
1747
+        if ($excludedGroups !== '' && $checkGroupMembership) {
1748
+            $excludedGroups = json_decode($excludedGroups);
1749
+            $user = $this->userSession->getUser();
1750
+            if ($user) {
1751
+                $userGroups = $this->groupManager->getUserGroupIds($user);
1752
+                if ((bool)array_intersect($excludedGroups, $userGroups)) {
1753
+                    return false;
1754
+                }
1755
+            }
1756
+        }
1757
+        return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_ENFORCED);
1758
+    }
1759
+
1760
+    /**
1761
+     * Is default link expire date enabled
1762
+     *
1763
+     * @return bool
1764
+     */
1765
+    public function shareApiLinkDefaultExpireDate() {
1766
+        return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_DEFAULT);
1767
+    }
1768
+
1769
+    /**
1770
+     * Is default link expire date enforced
1771
+     *
1772
+     * @return bool
1773
+     */
1774
+    public function shareApiLinkDefaultExpireDateEnforced() {
1775
+        return $this->shareApiLinkDefaultExpireDate()
1776
+            && $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_ENFORCED);
1777
+    }
1778
+
1779
+
1780
+    /**
1781
+     * Number of default link expire days
1782
+     *
1783
+     * @return int
1784
+     */
1785
+    public function shareApiLinkDefaultExpireDays() {
1786
+        return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1787
+    }
1788
+
1789
+    /**
1790
+     * Is default internal expire date enabled
1791
+     *
1792
+     * @return bool
1793
+     */
1794
+    public function shareApiInternalDefaultExpireDate(): bool {
1795
+        return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
1796
+    }
1797
+
1798
+    /**
1799
+     * Is default remote expire date enabled
1800
+     *
1801
+     * @return bool
1802
+     */
1803
+    public function shareApiRemoteDefaultExpireDate(): bool {
1804
+        return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
1805
+    }
1806
+
1807
+    /**
1808
+     * Is default expire date enforced
1809
+     *
1810
+     * @return bool
1811
+     */
1812
+    public function shareApiInternalDefaultExpireDateEnforced(): bool {
1813
+        return $this->shareApiInternalDefaultExpireDate()
1814
+            && $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
1815
+    }
1816
+
1817
+    /**
1818
+     * Is default expire date enforced for remote shares
1819
+     *
1820
+     * @return bool
1821
+     */
1822
+    public function shareApiRemoteDefaultExpireDateEnforced(): bool {
1823
+        return $this->shareApiRemoteDefaultExpireDate()
1824
+            && $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
1825
+    }
1826
+
1827
+    /**
1828
+     * Number of default expire days
1829
+     *
1830
+     * @return int
1831
+     */
1832
+    public function shareApiInternalDefaultExpireDays(): int {
1833
+        return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
1834
+    }
1835
+
1836
+    /**
1837
+     * Number of default expire days for remote shares
1838
+     *
1839
+     * @return int
1840
+     */
1841
+    public function shareApiRemoteDefaultExpireDays(): int {
1842
+        return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
1843
+    }
1844
+
1845
+    /**
1846
+     * Allow public upload on link shares
1847
+     *
1848
+     * @return bool
1849
+     */
1850
+    public function shareApiLinkAllowPublicUpload() {
1851
+        return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1852
+    }
1853
+
1854
+    /**
1855
+     * check if user can only share with group members
1856
+     *
1857
+     * @return bool
1858
+     */
1859
+    public function shareWithGroupMembersOnly() {
1860
+        return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1861
+    }
1862
+
1863
+    /**
1864
+     * If shareWithGroupMembersOnly is enabled, return an optional
1865
+     * list of groups that must be excluded from the principle of
1866
+     * belonging to the same group.
1867
+     *
1868
+     * @return array
1869
+     */
1870
+    public function shareWithGroupMembersOnlyExcludeGroupsList() {
1871
+        if (!$this->shareWithGroupMembersOnly()) {
1872
+            return [];
1873
+        }
1874
+        $excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
1875
+        return json_decode($excludeGroups, true) ?? [];
1876
+    }
1877
+
1878
+    /**
1879
+     * Check if users can share with groups
1880
+     *
1881
+     * @return bool
1882
+     */
1883
+    public function allowGroupSharing() {
1884
+        return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1885
+    }
1886
+
1887
+    public function allowEnumeration(): bool {
1888
+        return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
1889
+    }
1890
+
1891
+    public function limitEnumerationToGroups(): bool {
1892
+        return $this->allowEnumeration()
1893
+            && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
1894
+    }
1895
+
1896
+    public function limitEnumerationToPhone(): bool {
1897
+        return $this->allowEnumeration()
1898
+            && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
1899
+    }
1900
+
1901
+    public function allowEnumerationFullMatch(): bool {
1902
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
1903
+    }
1904
+
1905
+    public function matchEmail(): bool {
1906
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
1907
+    }
1908
+
1909
+    public function matchUserId(): bool {
1910
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes') === 'yes';
1911
+    }
1912
+
1913
+    public function ignoreSecondDisplayName(): bool {
1914
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
1915
+    }
1916
+
1917
+    public function allowCustomTokens(): bool {
1918
+        return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_CUSTOM_TOKEN);
1919
+    }
1920
+
1921
+    public function allowViewWithoutDownload(): bool {
1922
+        return $this->appConfig->getValueBool('core', 'shareapi_allow_view_without_download', true);
1923
+    }
1924
+
1925
+    public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool {
1926
+        if ($this->allowEnumerationFullMatch()) {
1927
+            return true;
1928
+        }
1929
+
1930
+        if (!$this->allowEnumeration()) {
1931
+            return false;
1932
+        }
1933
+
1934
+        if (!$this->limitEnumerationToPhone() && !$this->limitEnumerationToGroups()) {
1935
+            // Enumeration is enabled and not restricted: OK
1936
+            return true;
1937
+        }
1938
+
1939
+        if (!$currentUser instanceof IUser) {
1940
+            // Enumeration restrictions require an account
1941
+            return false;
1942
+        }
1943
+
1944
+        // Enumeration is limited to phone match
1945
+        if ($this->limitEnumerationToPhone() && $this->knownUserService->isKnownToUser($currentUser->getUID(), $targetUser->getUID())) {
1946
+            return true;
1947
+        }
1948
+
1949
+        // Enumeration is limited to groups
1950
+        if ($this->limitEnumerationToGroups()) {
1951
+            $currentUserGroupIds = $this->groupManager->getUserGroupIds($currentUser);
1952
+            $targetUserGroupIds = $this->groupManager->getUserGroupIds($targetUser);
1953
+            if (!empty(array_intersect($currentUserGroupIds, $targetUserGroupIds))) {
1954
+                return true;
1955
+            }
1956
+        }
1957
+
1958
+        return false;
1959
+    }
1960
+
1961
+    /**
1962
+     * Check if sharing is disabled for the current user
1963
+     */
1964
+    public function sharingDisabledForUser(?string $userId): bool {
1965
+        return $this->shareDisableChecker->sharingDisabledForUser($userId);
1966
+    }
1967
+
1968
+    /**
1969
+     * @inheritdoc
1970
+     */
1971
+    public function outgoingServer2ServerSharesAllowed() {
1972
+        return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1973
+    }
1974
+
1975
+    /**
1976
+     * @inheritdoc
1977
+     */
1978
+    public function outgoingServer2ServerGroupSharesAllowed() {
1979
+        return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1980
+    }
1981
+
1982
+    /**
1983
+     * @inheritdoc
1984
+     */
1985
+    public function shareProviderExists($shareType) {
1986
+        try {
1987
+            $this->factory->getProviderForType($shareType);
1988
+        } catch (ProviderException $e) {
1989
+            return false;
1990
+        }
1991
+
1992
+        return true;
1993
+    }
1994
+
1995
+    public function registerShareProvider(string $shareProviderClass): void {
1996
+        $this->factory->registerProvider($shareProviderClass);
1997
+    }
1998
+
1999
+    public function getAllShares(): iterable {
2000
+        $providers = $this->factory->getAllProviders();
2001
+
2002
+        foreach ($providers as $provider) {
2003
+            yield from $provider->getAllShares();
2004
+        }
2005
+    }
2006
+
2007
+    public function generateToken(): string {
2008
+        // Initial token length
2009
+        $tokenLength = \OC\Share\Helper::getTokenLength();
2010
+
2011
+        do {
2012
+            $tokenExists = false;
2013
+
2014
+            for ($i = 0; $i <= 2; $i++) {
2015
+                // Generate a new token
2016
+                $token = $this->secureRandom->generate(
2017
+                    $tokenLength,
2018
+                    ISecureRandom::CHAR_HUMAN_READABLE,
2019
+                );
2020
+
2021
+                try {
2022
+                    // Try to fetch a share with the generated token
2023
+                    $this->getShareByToken($token);
2024
+                    $tokenExists = true; // Token exists, we need to try again
2025
+                } catch (ShareNotFound $e) {
2026
+                    // Token is unique, exit the loop
2027
+                    $tokenExists = false;
2028
+                    break;
2029
+                }
2030
+            }
2031
+
2032
+            // If we've reached the maximum attempts and the token still exists, increase the token length
2033
+            if ($tokenExists) {
2034
+                $tokenLength++;
2035
+
2036
+                // Check if the token length exceeds the maximum allowed length
2037
+                if ($tokenLength > \OC\Share\Constants::MAX_TOKEN_LENGTH) {
2038
+                    throw new ShareTokenException('Unable to generate a unique share token. Maximum token length exceeded.');
2039
+                }
2040
+            }
2041
+        } while ($tokenExists);
2042
+
2043
+        return $token;
2044
+    }
2045
+
2046
+    private function dispatchEvent(Event $event, string $name): void {
2047
+        try {
2048
+            $this->dispatcher->dispatchTyped($event);
2049
+        } catch (\Exception $e) {
2050
+            $this->logger->error("Error while sending ' . $name . ' event", ['exception' => $e]);
2051
+        }
2052
+    }
2053 2053
 }
Please login to merge, or discard this patch.
lib/public/Share/IManager.php 1 patch
Indentation   +541 added lines, -541 removed lines patch added patch discarded remove patch
@@ -25,545 +25,545 @@
 block discarded – undo
25 25
  * @since 9.0.0
26 26
  */
27 27
 interface IManager {
28
-	/**
29
-	 * Create a Share
30
-	 *
31
-	 * @param IShare $share
32
-	 * @return IShare The share object
33
-	 * @throws \Exception
34
-	 * @since 9.0.0
35
-	 */
36
-	public function createShare(IShare $share);
37
-
38
-	/**
39
-	 * Update a share.
40
-	 * The target of the share can't be changed this way: use moveShare
41
-	 * The share can't be removed this way (permission 0): use deleteShare
42
-	 * The state can't be changed this way: use acceptShare
43
-	 *
44
-	 * @param IShare $share
45
-	 * @param bool $onlyValid Only updates valid shares, invalid shares will be deleted automatically and are not updated
46
-	 * @return IShare The share object
47
-	 * @throws \InvalidArgumentException
48
-	 * @since 9.0.0
49
-	 */
50
-	public function updateShare(IShare $share, bool $onlyValid = true);
51
-
52
-	/**
53
-	 * Accept a share.
54
-	 *
55
-	 * @param IShare $share
56
-	 * @param string $recipientId
57
-	 * @return IShare The share object
58
-	 * @throws \InvalidArgumentException
59
-	 * @since 18.0.0
60
-	 */
61
-	public function acceptShare(IShare $share, string $recipientId): IShare;
62
-
63
-	/**
64
-	 * Delete a share
65
-	 *
66
-	 * @param IShare $share
67
-	 * @throws ShareNotFound
68
-	 * @throws \InvalidArgumentException
69
-	 * @since 9.0.0
70
-	 */
71
-	public function deleteShare(IShare $share);
72
-
73
-	/**
74
-	 * Unshare a file as the recipient.
75
-	 * This can be different from a regular delete for example when one of
76
-	 * the users in a groups deletes that share. But the provider should
77
-	 * handle this.
78
-	 *
79
-	 * @param IShare $share
80
-	 * @param string $recipientId
81
-	 * @since 9.0.0
82
-	 */
83
-	public function deleteFromSelf(IShare $share, $recipientId);
84
-
85
-	/**
86
-	 * Restore the share when it has been deleted
87
-	 * Certain share types can be restored when they have been deleted
88
-	 * but the provider should properly handle this\
89
-	 *
90
-	 * @param IShare $share The share to restore
91
-	 * @param string $recipientId The user to restore the share for
92
-	 * @return IShare The restored share object
93
-	 * @throws GenericShareException In case restoring the share failed
94
-	 *
95
-	 * @since 14.0.0
96
-	 */
97
-	public function restoreShare(IShare $share, string $recipientId): IShare;
98
-
99
-	/**
100
-	 * Move the share as a recipient of the share.
101
-	 * This is updating the share target. So where the recipient has the share mounted.
102
-	 *
103
-	 * @param IShare $share
104
-	 * @param string $recipientId
105
-	 * @return IShare
106
-	 * @throws \InvalidArgumentException If $share is a link share or the $recipient does not match
107
-	 * @since 9.0.0
108
-	 */
109
-	public function moveShare(IShare $share, $recipientId);
110
-
111
-	/**
112
-	 * Get all shares shared by (initiated) by the provided user in a folder.
113
-	 *
114
-	 * @param string $userId
115
-	 * @param Folder $node
116
-	 * @param bool $reshares
117
-	 * @param bool $shallow Whether the method should stop at the first level, or look into sub-folders.
118
-	 * @return IShare[][] [$fileId => IShare[], ...]
119
-	 * @since 11.0.0
120
-	 */
121
-	public function getSharesInFolder($userId, Folder $node, $reshares = false, $shallow = true);
122
-
123
-	/**
124
-	 * Get shares shared by (initiated) by the provided user.
125
-	 *
126
-	 * @param string $userId
127
-	 * @param int $shareType
128
-	 * @param Node|null $path
129
-	 * @param bool $reshares
130
-	 * @param int $limit The maximum number of returned results, -1 for all results
131
-	 * @param int $offset
132
-	 * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
133
-	 * @return IShare[]
134
-	 * @since 9.0.0
135
-	 */
136
-	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, bool $onlyValid = true);
137
-
138
-	/**
139
-	 * Get shares shared with $user.
140
-	 * Filter by $node if provided
141
-	 *
142
-	 * @param string $userId
143
-	 * @param int $shareType
144
-	 * @param Node|null $node
145
-	 * @param int $limit The maximum number of shares returned, -1 for all
146
-	 * @param int $offset
147
-	 * @return IShare[]
148
-	 * @since 9.0.0
149
-	 */
150
-	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0);
151
-
152
-	/**
153
-	 * Get deleted shares shared with $user.
154
-	 * Filter by $node if provided
155
-	 *
156
-	 * @param string $userId
157
-	 * @param int $shareType
158
-	 * @param Node|null $node
159
-	 * @param int $limit The maximum number of shares returned, -1 for all
160
-	 * @param int $offset
161
-	 * @return IShare[]
162
-	 * @since 14.0.0
163
-	 */
164
-	public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0);
165
-
166
-	/**
167
-	 * Retrieve a share by the share id.
168
-	 * If the recipient is set make sure to retrieve the file for that user.
169
-	 * This makes sure that if a user has moved/deleted a group share this
170
-	 * is reflected.
171
-	 *
172
-	 * @param string $id
173
-	 * @param string|null $recipient userID of the recipient
174
-	 * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
175
-	 * @return IShare
176
-	 * @throws ShareNotFound
177
-	 * @since 9.0.0
178
-	 */
179
-	public function getShareById($id, $recipient = null, bool $onlyValid = true);
180
-
181
-	/**
182
-	 * Get the share by token possible with password
183
-	 *
184
-	 * @param string $token
185
-	 * @return IShare
186
-	 * @throws ShareNotFound
187
-	 * @since 9.0.0
188
-	 */
189
-	public function getShareByToken($token);
190
-
191
-	/**
192
-	 * Verify the password of a public share
193
-	 *
194
-	 * @param IShare $share
195
-	 * @param ?string $password
196
-	 * @return bool
197
-	 * @since 9.0.0
198
-	 */
199
-	public function checkPassword(IShare $share, $password);
200
-
201
-	/**
202
-	 * The user with UID is deleted.
203
-	 * All share providers have to cleanup the shares with this user as well
204
-	 * as shares owned by this user.
205
-	 * Shares only initiated by this user are fine.
206
-	 *
207
-	 * @param string $uid
208
-	 * @since 9.1.0
209
-	 */
210
-	public function userDeleted($uid);
211
-
212
-	/**
213
-	 * The group with $gid is deleted
214
-	 * We need to clear up all shares to this group
215
-	 *
216
-	 * @param string $gid
217
-	 * @since 9.1.0
218
-	 */
219
-	public function groupDeleted($gid);
220
-
221
-	/**
222
-	 * The user $uid is deleted from the group $gid
223
-	 * All user specific group shares have to be removed
224
-	 *
225
-	 * @param string $uid
226
-	 * @param string $gid
227
-	 * @since 9.1.0
228
-	 */
229
-	public function userDeletedFromGroup($uid, $gid);
230
-
231
-	/**
232
-	 * Get access list to a path. This means
233
-	 * all the users that can access a given path.
234
-	 *
235
-	 * Consider:
236
-	 * -root
237
-	 * |-folder1 (23)
238
-	 *  |-folder2 (32)
239
-	 *   |-fileA (42)
240
-	 *
241
-	 * fileA is shared with user1 and user1@server1 and email1@maildomain1
242
-	 * folder2 is shared with group2 (user4 is a member of group2)
243
-	 * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
244
-	 *                        and email2@maildomain2
245
-	 *
246
-	 * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
247
-	 * [
248
-	 *  users  => [
249
-	 *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
250
-	 *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
251
-	 *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
252
-	 *  ],
253
-	 *  remote => [
254
-	 *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
255
-	 *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
256
-	 *  ],
257
-	 *  public => bool
258
-	 *  mail => [
259
-	 *      'email1@maildomain1' => ['node_id' => 42, 'token' => 'aBcDeFg'],
260
-	 *      'email2@maildomain2' => ['node_id' => 23, 'token' => 'hIjKlMn'],
261
-	 *  ]
262
-	 *
263
-	 * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
264
-	 * [
265
-	 *  users  => ['user1', 'user2', 'user4'],
266
-	 *  remote => bool,
267
-	 *  public => bool
268
-	 *  mail => ['email1@maildomain1', 'email2@maildomain2']
269
-	 * ]
270
-	 *
271
-	 * This is required for encryption/activity
272
-	 *
273
-	 * @param \OCP\Files\Node $path
274
-	 * @param bool $recursive Should we check all parent folders as well
275
-	 * @param bool $currentAccess Should the user have currently access to the file
276
-	 * @return ($currentAccess is true
277
-	 * 		? array{
278
-	 *     		users?: array<string, array{node_id: int, node_path: string}>,
279
-	 *     		remote?: array<string, array{node_id: int, node_path: string}>,
280
-	 *     		public?: bool,
281
-	 *     		mail?: array<string, array{node_id: int, node_path: string}>
282
-	 *     	}
283
-	 *      : array{users?: list<string>, remote?: bool, public?: bool, mail?: list<string>})
284
-	 * @since 12.0.0
285
-	 */
286
-	public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false);
287
-
288
-	/**
289
-	 * Instantiates a new share object. This is to be passed to
290
-	 * createShare.
291
-	 *
292
-	 * @return IShare
293
-	 * @since 9.0.0
294
-	 */
295
-	public function newShare();
296
-
297
-	/**
298
-	 * Is the share API enabled
299
-	 *
300
-	 * @return bool
301
-	 * @since 9.0.0
302
-	 */
303
-	public function shareApiEnabled();
304
-
305
-	/**
306
-	 * Is public link sharing enabled
307
-	 *
308
-	 * @param ?IUser $user User to check against group exclusions, defaults to current session user
309
-	 * @return bool
310
-	 * @since 9.0.0
311
-	 * @since 33.0.0 Added optional $user parameter
312
-	 */
313
-	public function shareApiAllowLinks(?IUser $user = null);
314
-
315
-	/**
316
-	 * Is password on public link required
317
-	 *
318
-	 * @param bool $checkGroupMembership Check group membership exclusion
319
-	 * @return bool
320
-	 * @since 9.0.0
321
-	 * @since 24.0.0 Added optional $checkGroupMembership parameter
322
-	 */
323
-	public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true);
324
-
325
-	/**
326
-	 * Is default expire date enabled
327
-	 *
328
-	 * @return bool
329
-	 * @since 9.0.0
330
-	 */
331
-	public function shareApiLinkDefaultExpireDate();
332
-
333
-	/**
334
-	 * Is default expire date enforced
335
-	 *`
336
-	 * @return bool
337
-	 * @since 9.0.0
338
-	 */
339
-	public function shareApiLinkDefaultExpireDateEnforced();
340
-
341
-	/**
342
-	 * Number of default expire days
343
-	 *
344
-	 * @return int
345
-	 * @since 9.0.0
346
-	 */
347
-	public function shareApiLinkDefaultExpireDays();
348
-
349
-	/**
350
-	 * Is default internal expire date enabled
351
-	 *
352
-	 * @return bool
353
-	 * @since 22.0.0
354
-	 */
355
-	public function shareApiInternalDefaultExpireDate(): bool;
356
-
357
-	/**
358
-	 * Is default remote expire date enabled
359
-	 *
360
-	 * @return bool
361
-	 * @since 22.0.0
362
-	 */
363
-	public function shareApiRemoteDefaultExpireDate(): bool;
364
-
365
-	/**
366
-	 * Is default expire date enforced
367
-	 *
368
-	 * @return bool
369
-	 * @since 22.0.0
370
-	 */
371
-	public function shareApiInternalDefaultExpireDateEnforced(): bool;
372
-
373
-	/**
374
-	 * Is default expire date enforced for remote shares
375
-	 *
376
-	 * @return bool
377
-	 * @since 22.0.0
378
-	 */
379
-	public function shareApiRemoteDefaultExpireDateEnforced(): bool;
380
-
381
-	/**
382
-	 * Number of default expire days
383
-	 *
384
-	 * @return int
385
-	 * @since 22.0.0
386
-	 */
387
-	public function shareApiInternalDefaultExpireDays(): int;
388
-
389
-	/**
390
-	 * Number of default expire days for remote shares
391
-	 *
392
-	 * @return int
393
-	 * @since 22.0.0
394
-	 */
395
-	public function shareApiRemoteDefaultExpireDays(): int;
396
-
397
-	/**
398
-	 * Allow public upload on link shares
399
-	 *
400
-	 * @return bool
401
-	 * @since 9.0.0
402
-	 */
403
-	public function shareApiLinkAllowPublicUpload();
404
-
405
-	/**
406
-	 * check if user can only share with group members
407
-	 * @return bool
408
-	 * @since 9.0.0
409
-	 */
410
-	public function shareWithGroupMembersOnly();
411
-
412
-	/**
413
-	 * If shareWithGroupMembersOnly is enabled, return an optional
414
-	 * list of groups that must be excluded from the principle of
415
-	 * belonging to the same group.
416
-	 * @return array
417
-	 * @since 27.0.0
418
-	 */
419
-	public function shareWithGroupMembersOnlyExcludeGroupsList();
420
-
421
-	/**
422
-	 * Check if users can share with groups
423
-	 * @return bool
424
-	 * @since 9.0.1
425
-	 */
426
-	public function allowGroupSharing();
427
-
428
-	/**
429
-	 * Check if user enumeration is allowed
430
-	 *
431
-	 * @return bool
432
-	 * @since 19.0.0
433
-	 */
434
-	public function allowEnumeration(): bool;
435
-
436
-	/**
437
-	 * Check if user enumeration is limited to the users groups
438
-	 *
439
-	 * @return bool
440
-	 * @since 19.0.0
441
-	 */
442
-	public function limitEnumerationToGroups(): bool;
443
-
444
-	/**
445
-	 * Check if user enumeration is limited to the phonebook matches
446
-	 *
447
-	 * @return bool
448
-	 * @since 21.0.1
449
-	 */
450
-	public function limitEnumerationToPhone(): bool;
451
-
452
-	/**
453
-	 * Check if user enumeration is allowed to return also on full match
454
-	 * and ignore limitations to phonebook or groups.
455
-	 *
456
-	 * @return bool
457
-	 * @since 21.0.1
458
-	 */
459
-	public function allowEnumerationFullMatch(): bool;
460
-
461
-	/**
462
-	 * When `allowEnumerationFullMatch` is enabled and `matchEmail` is set,
463
-	 * then also return results for full email matches.
464
-	 *
465
-	 * @return bool
466
-	 * @since 25.0.0
467
-	 */
468
-	public function matchEmail(): bool;
469
-
470
-	/**
471
-	 * When `allowEnumerationFullMatch` is enabled and `matchUserId` is set,
472
-	 * then also return results for full user id matches.
473
-	 *
474
-	 * @return bool
475
-	 * @since 33.0.0
476
-	 */
477
-	public function matchUserId(): bool;
478
-
479
-	/**
480
-	 * When `allowEnumerationFullMatch` is enabled and `ignoreSecondDisplayName` is set,
481
-	 * then the search should ignore matches on the second displayname and only use the first.
482
-	 *
483
-	 * @return bool
484
-	 * @since 25.0.0
485
-	 */
486
-	public function ignoreSecondDisplayName(): bool;
487
-
488
-
489
-	/**
490
-	 * Check if custom tokens are allowed
491
-	 *
492
-	 * @since 31.0.0
493
-	 */
494
-	public function allowCustomTokens(): bool;
495
-
496
-	/**
497
-	 * Check if the current user can view the share
498
-	 * even if the download is disabled.
499
-	 *
500
-	 * @since 32.0.0
501
-	 */
502
-	public function allowViewWithoutDownload(): bool;
503
-
504
-	/**
505
-	 * Check if the current user can enumerate the target user
506
-	 *
507
-	 * @param IUser|null $currentUser
508
-	 * @param IUser $targetUser
509
-	 * @return bool
510
-	 * @since 23.0.0
511
-	 */
512
-	public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool;
513
-
514
-	/**
515
-	 * Check if sharing is disabled for the given user
516
-	 *
517
-	 * @since 9.0.0
518
-	 */
519
-	public function sharingDisabledForUser(?string $userId): bool;
520
-
521
-	/**
522
-	 * Check if outgoing server2server shares are allowed
523
-	 * @return bool
524
-	 * @since 9.0.0
525
-	 */
526
-	public function outgoingServer2ServerSharesAllowed();
527
-
528
-	/**
529
-	 * Check if outgoing server2server shares are allowed
530
-	 * @return bool
531
-	 * @since 14.0.0
532
-	 */
533
-	public function outgoingServer2ServerGroupSharesAllowed();
534
-
535
-
536
-	/**
537
-	 * Check if a given share provider exists
538
-	 * @param int $shareType
539
-	 * @return bool
540
-	 * @since 11.0.0
541
-	 */
542
-	public function shareProviderExists($shareType);
543
-
544
-	/**
545
-	 * @param string $shareProviderClass
546
-	 * @since 21.0.0
547
-	 */
548
-	public function registerShareProvider(string $shareProviderClass): void;
549
-
550
-	/**
551
-	 * @Internal
552
-	 *
553
-	 * Get all the shares as iterable to reduce memory overhead
554
-	 * Note, since this opens up database cursors the iterable should
555
-	 * be fully iterated.
556
-	 *
557
-	 * @return iterable<IShare>
558
-	 * @since 18.0.0
559
-	 */
560
-	public function getAllShares(): iterable;
561
-
562
-	/**
563
-	 * Generate a unique share token
564
-	 *
565
-	 * @throws ShareTokenException Failed to generate a unique token
566
-	 * @since 31.0.0
567
-	 */
568
-	public function generateToken(): string;
28
+    /**
29
+     * Create a Share
30
+     *
31
+     * @param IShare $share
32
+     * @return IShare The share object
33
+     * @throws \Exception
34
+     * @since 9.0.0
35
+     */
36
+    public function createShare(IShare $share);
37
+
38
+    /**
39
+     * Update a share.
40
+     * The target of the share can't be changed this way: use moveShare
41
+     * The share can't be removed this way (permission 0): use deleteShare
42
+     * The state can't be changed this way: use acceptShare
43
+     *
44
+     * @param IShare $share
45
+     * @param bool $onlyValid Only updates valid shares, invalid shares will be deleted automatically and are not updated
46
+     * @return IShare The share object
47
+     * @throws \InvalidArgumentException
48
+     * @since 9.0.0
49
+     */
50
+    public function updateShare(IShare $share, bool $onlyValid = true);
51
+
52
+    /**
53
+     * Accept a share.
54
+     *
55
+     * @param IShare $share
56
+     * @param string $recipientId
57
+     * @return IShare The share object
58
+     * @throws \InvalidArgumentException
59
+     * @since 18.0.0
60
+     */
61
+    public function acceptShare(IShare $share, string $recipientId): IShare;
62
+
63
+    /**
64
+     * Delete a share
65
+     *
66
+     * @param IShare $share
67
+     * @throws ShareNotFound
68
+     * @throws \InvalidArgumentException
69
+     * @since 9.0.0
70
+     */
71
+    public function deleteShare(IShare $share);
72
+
73
+    /**
74
+     * Unshare a file as the recipient.
75
+     * This can be different from a regular delete for example when one of
76
+     * the users in a groups deletes that share. But the provider should
77
+     * handle this.
78
+     *
79
+     * @param IShare $share
80
+     * @param string $recipientId
81
+     * @since 9.0.0
82
+     */
83
+    public function deleteFromSelf(IShare $share, $recipientId);
84
+
85
+    /**
86
+     * Restore the share when it has been deleted
87
+     * Certain share types can be restored when they have been deleted
88
+     * but the provider should properly handle this\
89
+     *
90
+     * @param IShare $share The share to restore
91
+     * @param string $recipientId The user to restore the share for
92
+     * @return IShare The restored share object
93
+     * @throws GenericShareException In case restoring the share failed
94
+     *
95
+     * @since 14.0.0
96
+     */
97
+    public function restoreShare(IShare $share, string $recipientId): IShare;
98
+
99
+    /**
100
+     * Move the share as a recipient of the share.
101
+     * This is updating the share target. So where the recipient has the share mounted.
102
+     *
103
+     * @param IShare $share
104
+     * @param string $recipientId
105
+     * @return IShare
106
+     * @throws \InvalidArgumentException If $share is a link share or the $recipient does not match
107
+     * @since 9.0.0
108
+     */
109
+    public function moveShare(IShare $share, $recipientId);
110
+
111
+    /**
112
+     * Get all shares shared by (initiated) by the provided user in a folder.
113
+     *
114
+     * @param string $userId
115
+     * @param Folder $node
116
+     * @param bool $reshares
117
+     * @param bool $shallow Whether the method should stop at the first level, or look into sub-folders.
118
+     * @return IShare[][] [$fileId => IShare[], ...]
119
+     * @since 11.0.0
120
+     */
121
+    public function getSharesInFolder($userId, Folder $node, $reshares = false, $shallow = true);
122
+
123
+    /**
124
+     * Get shares shared by (initiated) by the provided user.
125
+     *
126
+     * @param string $userId
127
+     * @param int $shareType
128
+     * @param Node|null $path
129
+     * @param bool $reshares
130
+     * @param int $limit The maximum number of returned results, -1 for all results
131
+     * @param int $offset
132
+     * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
133
+     * @return IShare[]
134
+     * @since 9.0.0
135
+     */
136
+    public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, bool $onlyValid = true);
137
+
138
+    /**
139
+     * Get shares shared with $user.
140
+     * Filter by $node if provided
141
+     *
142
+     * @param string $userId
143
+     * @param int $shareType
144
+     * @param Node|null $node
145
+     * @param int $limit The maximum number of shares returned, -1 for all
146
+     * @param int $offset
147
+     * @return IShare[]
148
+     * @since 9.0.0
149
+     */
150
+    public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0);
151
+
152
+    /**
153
+     * Get deleted shares shared with $user.
154
+     * Filter by $node if provided
155
+     *
156
+     * @param string $userId
157
+     * @param int $shareType
158
+     * @param Node|null $node
159
+     * @param int $limit The maximum number of shares returned, -1 for all
160
+     * @param int $offset
161
+     * @return IShare[]
162
+     * @since 14.0.0
163
+     */
164
+    public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0);
165
+
166
+    /**
167
+     * Retrieve a share by the share id.
168
+     * If the recipient is set make sure to retrieve the file for that user.
169
+     * This makes sure that if a user has moved/deleted a group share this
170
+     * is reflected.
171
+     *
172
+     * @param string $id
173
+     * @param string|null $recipient userID of the recipient
174
+     * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
175
+     * @return IShare
176
+     * @throws ShareNotFound
177
+     * @since 9.0.0
178
+     */
179
+    public function getShareById($id, $recipient = null, bool $onlyValid = true);
180
+
181
+    /**
182
+     * Get the share by token possible with password
183
+     *
184
+     * @param string $token
185
+     * @return IShare
186
+     * @throws ShareNotFound
187
+     * @since 9.0.0
188
+     */
189
+    public function getShareByToken($token);
190
+
191
+    /**
192
+     * Verify the password of a public share
193
+     *
194
+     * @param IShare $share
195
+     * @param ?string $password
196
+     * @return bool
197
+     * @since 9.0.0
198
+     */
199
+    public function checkPassword(IShare $share, $password);
200
+
201
+    /**
202
+     * The user with UID is deleted.
203
+     * All share providers have to cleanup the shares with this user as well
204
+     * as shares owned by this user.
205
+     * Shares only initiated by this user are fine.
206
+     *
207
+     * @param string $uid
208
+     * @since 9.1.0
209
+     */
210
+    public function userDeleted($uid);
211
+
212
+    /**
213
+     * The group with $gid is deleted
214
+     * We need to clear up all shares to this group
215
+     *
216
+     * @param string $gid
217
+     * @since 9.1.0
218
+     */
219
+    public function groupDeleted($gid);
220
+
221
+    /**
222
+     * The user $uid is deleted from the group $gid
223
+     * All user specific group shares have to be removed
224
+     *
225
+     * @param string $uid
226
+     * @param string $gid
227
+     * @since 9.1.0
228
+     */
229
+    public function userDeletedFromGroup($uid, $gid);
230
+
231
+    /**
232
+     * Get access list to a path. This means
233
+     * all the users that can access a given path.
234
+     *
235
+     * Consider:
236
+     * -root
237
+     * |-folder1 (23)
238
+     *  |-folder2 (32)
239
+     *   |-fileA (42)
240
+     *
241
+     * fileA is shared with user1 and user1@server1 and email1@maildomain1
242
+     * folder2 is shared with group2 (user4 is a member of group2)
243
+     * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
244
+     *                        and email2@maildomain2
245
+     *
246
+     * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
247
+     * [
248
+     *  users  => [
249
+     *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
250
+     *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
251
+     *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
252
+     *  ],
253
+     *  remote => [
254
+     *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
255
+     *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
256
+     *  ],
257
+     *  public => bool
258
+     *  mail => [
259
+     *      'email1@maildomain1' => ['node_id' => 42, 'token' => 'aBcDeFg'],
260
+     *      'email2@maildomain2' => ['node_id' => 23, 'token' => 'hIjKlMn'],
261
+     *  ]
262
+     *
263
+     * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
264
+     * [
265
+     *  users  => ['user1', 'user2', 'user4'],
266
+     *  remote => bool,
267
+     *  public => bool
268
+     *  mail => ['email1@maildomain1', 'email2@maildomain2']
269
+     * ]
270
+     *
271
+     * This is required for encryption/activity
272
+     *
273
+     * @param \OCP\Files\Node $path
274
+     * @param bool $recursive Should we check all parent folders as well
275
+     * @param bool $currentAccess Should the user have currently access to the file
276
+     * @return ($currentAccess is true
277
+     * 		? array{
278
+     *     		users?: array<string, array{node_id: int, node_path: string}>,
279
+     *     		remote?: array<string, array{node_id: int, node_path: string}>,
280
+     *     		public?: bool,
281
+     *     		mail?: array<string, array{node_id: int, node_path: string}>
282
+     *     	}
283
+     *      : array{users?: list<string>, remote?: bool, public?: bool, mail?: list<string>})
284
+     * @since 12.0.0
285
+     */
286
+    public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false);
287
+
288
+    /**
289
+     * Instantiates a new share object. This is to be passed to
290
+     * createShare.
291
+     *
292
+     * @return IShare
293
+     * @since 9.0.0
294
+     */
295
+    public function newShare();
296
+
297
+    /**
298
+     * Is the share API enabled
299
+     *
300
+     * @return bool
301
+     * @since 9.0.0
302
+     */
303
+    public function shareApiEnabled();
304
+
305
+    /**
306
+     * Is public link sharing enabled
307
+     *
308
+     * @param ?IUser $user User to check against group exclusions, defaults to current session user
309
+     * @return bool
310
+     * @since 9.0.0
311
+     * @since 33.0.0 Added optional $user parameter
312
+     */
313
+    public function shareApiAllowLinks(?IUser $user = null);
314
+
315
+    /**
316
+     * Is password on public link required
317
+     *
318
+     * @param bool $checkGroupMembership Check group membership exclusion
319
+     * @return bool
320
+     * @since 9.0.0
321
+     * @since 24.0.0 Added optional $checkGroupMembership parameter
322
+     */
323
+    public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true);
324
+
325
+    /**
326
+     * Is default expire date enabled
327
+     *
328
+     * @return bool
329
+     * @since 9.0.0
330
+     */
331
+    public function shareApiLinkDefaultExpireDate();
332
+
333
+    /**
334
+     * Is default expire date enforced
335
+     *`
336
+     * @return bool
337
+     * @since 9.0.0
338
+     */
339
+    public function shareApiLinkDefaultExpireDateEnforced();
340
+
341
+    /**
342
+     * Number of default expire days
343
+     *
344
+     * @return int
345
+     * @since 9.0.0
346
+     */
347
+    public function shareApiLinkDefaultExpireDays();
348
+
349
+    /**
350
+     * Is default internal expire date enabled
351
+     *
352
+     * @return bool
353
+     * @since 22.0.0
354
+     */
355
+    public function shareApiInternalDefaultExpireDate(): bool;
356
+
357
+    /**
358
+     * Is default remote expire date enabled
359
+     *
360
+     * @return bool
361
+     * @since 22.0.0
362
+     */
363
+    public function shareApiRemoteDefaultExpireDate(): bool;
364
+
365
+    /**
366
+     * Is default expire date enforced
367
+     *
368
+     * @return bool
369
+     * @since 22.0.0
370
+     */
371
+    public function shareApiInternalDefaultExpireDateEnforced(): bool;
372
+
373
+    /**
374
+     * Is default expire date enforced for remote shares
375
+     *
376
+     * @return bool
377
+     * @since 22.0.0
378
+     */
379
+    public function shareApiRemoteDefaultExpireDateEnforced(): bool;
380
+
381
+    /**
382
+     * Number of default expire days
383
+     *
384
+     * @return int
385
+     * @since 22.0.0
386
+     */
387
+    public function shareApiInternalDefaultExpireDays(): int;
388
+
389
+    /**
390
+     * Number of default expire days for remote shares
391
+     *
392
+     * @return int
393
+     * @since 22.0.0
394
+     */
395
+    public function shareApiRemoteDefaultExpireDays(): int;
396
+
397
+    /**
398
+     * Allow public upload on link shares
399
+     *
400
+     * @return bool
401
+     * @since 9.0.0
402
+     */
403
+    public function shareApiLinkAllowPublicUpload();
404
+
405
+    /**
406
+     * check if user can only share with group members
407
+     * @return bool
408
+     * @since 9.0.0
409
+     */
410
+    public function shareWithGroupMembersOnly();
411
+
412
+    /**
413
+     * If shareWithGroupMembersOnly is enabled, return an optional
414
+     * list of groups that must be excluded from the principle of
415
+     * belonging to the same group.
416
+     * @return array
417
+     * @since 27.0.0
418
+     */
419
+    public function shareWithGroupMembersOnlyExcludeGroupsList();
420
+
421
+    /**
422
+     * Check if users can share with groups
423
+     * @return bool
424
+     * @since 9.0.1
425
+     */
426
+    public function allowGroupSharing();
427
+
428
+    /**
429
+     * Check if user enumeration is allowed
430
+     *
431
+     * @return bool
432
+     * @since 19.0.0
433
+     */
434
+    public function allowEnumeration(): bool;
435
+
436
+    /**
437
+     * Check if user enumeration is limited to the users groups
438
+     *
439
+     * @return bool
440
+     * @since 19.0.0
441
+     */
442
+    public function limitEnumerationToGroups(): bool;
443
+
444
+    /**
445
+     * Check if user enumeration is limited to the phonebook matches
446
+     *
447
+     * @return bool
448
+     * @since 21.0.1
449
+     */
450
+    public function limitEnumerationToPhone(): bool;
451
+
452
+    /**
453
+     * Check if user enumeration is allowed to return also on full match
454
+     * and ignore limitations to phonebook or groups.
455
+     *
456
+     * @return bool
457
+     * @since 21.0.1
458
+     */
459
+    public function allowEnumerationFullMatch(): bool;
460
+
461
+    /**
462
+     * When `allowEnumerationFullMatch` is enabled and `matchEmail` is set,
463
+     * then also return results for full email matches.
464
+     *
465
+     * @return bool
466
+     * @since 25.0.0
467
+     */
468
+    public function matchEmail(): bool;
469
+
470
+    /**
471
+     * When `allowEnumerationFullMatch` is enabled and `matchUserId` is set,
472
+     * then also return results for full user id matches.
473
+     *
474
+     * @return bool
475
+     * @since 33.0.0
476
+     */
477
+    public function matchUserId(): bool;
478
+
479
+    /**
480
+     * When `allowEnumerationFullMatch` is enabled and `ignoreSecondDisplayName` is set,
481
+     * then the search should ignore matches on the second displayname and only use the first.
482
+     *
483
+     * @return bool
484
+     * @since 25.0.0
485
+     */
486
+    public function ignoreSecondDisplayName(): bool;
487
+
488
+
489
+    /**
490
+     * Check if custom tokens are allowed
491
+     *
492
+     * @since 31.0.0
493
+     */
494
+    public function allowCustomTokens(): bool;
495
+
496
+    /**
497
+     * Check if the current user can view the share
498
+     * even if the download is disabled.
499
+     *
500
+     * @since 32.0.0
501
+     */
502
+    public function allowViewWithoutDownload(): bool;
503
+
504
+    /**
505
+     * Check if the current user can enumerate the target user
506
+     *
507
+     * @param IUser|null $currentUser
508
+     * @param IUser $targetUser
509
+     * @return bool
510
+     * @since 23.0.0
511
+     */
512
+    public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool;
513
+
514
+    /**
515
+     * Check if sharing is disabled for the given user
516
+     *
517
+     * @since 9.0.0
518
+     */
519
+    public function sharingDisabledForUser(?string $userId): bool;
520
+
521
+    /**
522
+     * Check if outgoing server2server shares are allowed
523
+     * @return bool
524
+     * @since 9.0.0
525
+     */
526
+    public function outgoingServer2ServerSharesAllowed();
527
+
528
+    /**
529
+     * Check if outgoing server2server shares are allowed
530
+     * @return bool
531
+     * @since 14.0.0
532
+     */
533
+    public function outgoingServer2ServerGroupSharesAllowed();
534
+
535
+
536
+    /**
537
+     * Check if a given share provider exists
538
+     * @param int $shareType
539
+     * @return bool
540
+     * @since 11.0.0
541
+     */
542
+    public function shareProviderExists($shareType);
543
+
544
+    /**
545
+     * @param string $shareProviderClass
546
+     * @since 21.0.0
547
+     */
548
+    public function registerShareProvider(string $shareProviderClass): void;
549
+
550
+    /**
551
+     * @Internal
552
+     *
553
+     * Get all the shares as iterable to reduce memory overhead
554
+     * Note, since this opens up database cursors the iterable should
555
+     * be fully iterated.
556
+     *
557
+     * @return iterable<IShare>
558
+     * @since 18.0.0
559
+     */
560
+    public function getAllShares(): iterable;
561
+
562
+    /**
563
+     * Generate a unique share token
564
+     *
565
+     * @throws ShareTokenException Failed to generate a unique token
566
+     * @since 31.0.0
567
+     */
568
+    public function generateToken(): string;
569 569
 }
Please login to merge, or discard this patch.