Completed
Push — master ( 10fa17...197db6 )
by
unknown
57:25 queued 27:04
created
lib/private/Share20/Manager.php 1 patch
Indentation   +1973 added lines, -1973 removed lines patch added patch discarded remove patch
@@ -63,1994 +63,1994 @@
 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
-				$added++;
1269
-				if ($onlyValid) {
1270
-					try {
1271
-						$this->checkShare($share, $added);
1272
-					} catch (ShareNotFound $e) {
1273
-						// Ignore since this basically means the share is deleted
1274
-						continue;
1275
-					}
1276
-				}
1277
-
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
+                $added++;
1269
+                if ($onlyValid) {
1270
+                    try {
1271
+                        $this->checkShare($share, $added);
1272
+                    } catch (ShareNotFound $e) {
1273
+                        // Ignore since this basically means the share is deleted
1274
+                        continue;
1275
+                    }
1276
+                }
1277
+
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
-	 * @param int &$added If given, will be decremented if the share is deleted
1484
-	 * @throws ShareNotFound
1485
-	 */
1486
-	private function checkShare(IShare $share, int &$added = 1): void {
1487
-		if ($share->isExpired()) {
1488
-			$this->deleteShare($share);
1489
-			// Remove 1 to added, because this share was deleted
1490
-			$added--;
1491
-			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1492
-		}
1493
-		if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
1494
-			$uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
1495
-			foreach ($uids as $uid) {
1496
-				$user = $this->userManager->get($uid);
1497
-				if ($user?->isEnabled() === false) {
1498
-					throw new ShareNotFound($this->l->t('The requested share comes from a disabled user'));
1499
-				}
1500
-			}
1501
-		}
1502
-
1503
-		// For link and email shares, verify the share owner can still create such shares
1504
-		if ($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL) {
1505
-			$shareOwner = $this->userManager->get($share->getShareOwner());
1506
-			if ($shareOwner === null) {
1507
-				throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1508
-			}
1509
-			if (!$this->userCanCreateLinkShares($shareOwner)) {
1510
-				throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1511
-			}
1512
-		}
1513
-	}
1514
-
1515
-	/**
1516
-	 * Verify the password of a public share
1517
-	 *
1518
-	 * @param IShare $share
1519
-	 * @param ?string $password
1520
-	 * @return bool
1521
-	 */
1522
-	public function checkPassword(IShare $share, $password) {
1523
-
1524
-		// if there is no password on the share object / passsword is null, there is nothing to check
1525
-		if ($password === null || $share->getPassword() === null) {
1526
-			return false;
1527
-		}
1528
-
1529
-		// Makes sure password hasn't expired
1530
-		$expirationTime = $share->getPasswordExpirationTime();
1531
-		if ($expirationTime !== null && $expirationTime < new \DateTime()) {
1532
-			return false;
1533
-		}
1534
-
1535
-		$newHash = '';
1536
-		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1537
-			return false;
1538
-		}
1539
-
1540
-		if (!empty($newHash)) {
1541
-			$share->setPassword($newHash);
1542
-			$provider = $this->factory->getProviderForType($share->getShareType());
1543
-			$provider->update($share);
1544
-		}
1545
-
1546
-		return true;
1547
-	}
1548
-
1549
-	/**
1550
-	 * @inheritdoc
1551
-	 */
1552
-	public function userDeleted($uid) {
1553
-		$types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
1554
-
1555
-		foreach ($types as $type) {
1556
-			try {
1557
-				$provider = $this->factory->getProviderForType($type);
1558
-			} catch (ProviderException $e) {
1559
-				continue;
1560
-			}
1561
-			$provider->userDeleted($uid, $type);
1562
-		}
1563
-	}
1564
-
1565
-	/**
1566
-	 * @inheritdoc
1567
-	 */
1568
-	public function groupDeleted($gid) {
1569
-		foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1570
-			try {
1571
-				$provider = $this->factory->getProviderForType($type);
1572
-			} catch (ProviderException $e) {
1573
-				continue;
1574
-			}
1575
-			$provider->groupDeleted($gid);
1576
-		}
1577
-
1578
-		$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1579
-		if ($excludedGroups === '') {
1580
-			return;
1581
-		}
1582
-
1583
-		$excludedGroups = json_decode($excludedGroups, true);
1584
-		if (json_last_error() !== JSON_ERROR_NONE) {
1585
-			return;
1586
-		}
1587
-
1588
-		$excludedGroups = array_diff($excludedGroups, [$gid]);
1589
-		$this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
1590
-	}
1591
-
1592
-	/**
1593
-	 * @inheritdoc
1594
-	 */
1595
-	public function userDeletedFromGroup($uid, $gid) {
1596
-		foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1597
-			try {
1598
-				$provider = $this->factory->getProviderForType($type);
1599
-			} catch (ProviderException $e) {
1600
-				continue;
1601
-			}
1602
-			$provider->userDeletedFromGroup($uid, $gid);
1603
-		}
1604
-	}
1605
-
1606
-	#[\Override]
1607
-	public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1608
-		$owner = $path->getOwner();
1609
-
1610
-		if ($owner === null) {
1611
-			return [];
1612
-		}
1613
-
1614
-		$owner = $owner->getUID();
1615
-
1616
-		if ($currentAccess) {
1617
-			$al = ['users' => [], 'remote' => [], 'public' => false, 'mail' => []];
1618
-		} else {
1619
-			$al = ['users' => [], 'remote' => false, 'public' => false, 'mail' => []];
1620
-		}
1621
-		if (!$this->userManager->userExists($owner)) {
1622
-			return $al;
1623
-		}
1624
-
1625
-		//Get node for the owner and correct the owner in case of external storage
1626
-		$userFolder = $this->rootFolder->getUserFolder($owner);
1627
-		if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1628
-			$path = $userFolder->getFirstNodeById($path->getId());
1629
-			if ($path === null || $path->getOwner() === null) {
1630
-				return [];
1631
-			}
1632
-			$owner = $path->getOwner()->getUID();
1633
-		}
1634
-
1635
-		$providers = $this->factory->getAllProviders();
1636
-
1637
-		/** @var Node[] $nodes */
1638
-		$nodes = [];
1639
-
1640
-
1641
-		if ($currentAccess) {
1642
-			$ownerPath = $path->getPath();
1643
-			$ownerPath = explode('/', $ownerPath, 4);
1644
-			if (count($ownerPath) < 4) {
1645
-				$ownerPath = '';
1646
-			} else {
1647
-				$ownerPath = $ownerPath[3];
1648
-			}
1649
-			$al['users'][$owner] = [
1650
-				'node_id' => $path->getId(),
1651
-				'node_path' => '/' . $ownerPath,
1652
-			];
1653
-		} else {
1654
-			$al['users'][] = $owner;
1655
-		}
1656
-
1657
-		// Collect all the shares
1658
-		while ($path->getPath() !== $userFolder->getPath()) {
1659
-			$nodes[] = $path;
1660
-			if (!$recursive) {
1661
-				break;
1662
-			}
1663
-			$path = $path->getParent();
1664
-		}
1665
-
1666
-		foreach ($providers as $provider) {
1667
-			$tmp = $provider->getAccessList($nodes, $currentAccess);
1668
-
1669
-			foreach ($tmp as $k => $v) {
1670
-				if (isset($al[$k])) {
1671
-					if (is_array($al[$k])) {
1672
-						if ($currentAccess) {
1673
-							$al[$k] += $v;
1674
-						} else {
1675
-							$al[$k] = array_merge($al[$k], $v);
1676
-							$al[$k] = array_unique($al[$k]);
1677
-							$al[$k] = array_values($al[$k]);
1678
-						}
1679
-					} else {
1680
-						$al[$k] = $al[$k] || $v;
1681
-					}
1682
-				} else {
1683
-					$al[$k] = $v;
1684
-				}
1685
-			}
1686
-		}
1687
-
1688
-		return $al;
1689
-	}
1690
-
1691
-	/**
1692
-	 * Create a new share
1693
-	 *
1694
-	 * @return IShare
1695
-	 */
1696
-	public function newShare() {
1697
-		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1698
-	}
1699
-
1700
-	/**
1701
-	 * Is the share API enabled
1702
-	 *
1703
-	 * @return bool
1704
-	 */
1705
-	public function shareApiEnabled() {
1706
-		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1707
-	}
1708
-
1709
-	/**
1710
-	 * Is public link sharing enabled
1711
-	 *
1712
-	 * @param ?IUser $user User to check against group exclusions, defaults to current session user
1713
-	 * @return bool
1714
-	 */
1715
-	public function shareApiAllowLinks(?IUser $user = null) {
1716
-		if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1717
-			return false;
1718
-		}
1719
-
1720
-		$user = $user ?? $this->userSession->getUser();
1721
-		if ($user) {
1722
-			$excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
1723
-			if ($excludedGroups) {
1724
-				$userGroups = $this->groupManager->getUserGroupIds($user);
1725
-				return !(bool)array_intersect($excludedGroups, $userGroups);
1726
-			}
1727
-		}
1728
-
1729
-		return true;
1730
-	}
1731
-
1732
-	/**
1733
-	 * Check if a specific user can create link shares
1734
-	 *
1735
-	 * @param IUser $user The user to check
1736
-	 * @return bool
1737
-	 */
1738
-	protected function userCanCreateLinkShares(IUser $user): bool {
1739
-		return $this->shareApiAllowLinks($user);
1740
-	}
1741
-
1742
-	/**
1743
-	 * Is password on public link requires
1744
-	 *
1745
-	 * @param bool Check group membership exclusion
1746
-	 * @return bool
1747
-	 */
1748
-	public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true) {
1749
-		$excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
1750
-		if ($excludedGroups !== '' && $checkGroupMembership) {
1751
-			$excludedGroups = json_decode($excludedGroups);
1752
-			$user = $this->userSession->getUser();
1753
-			if ($user) {
1754
-				$userGroups = $this->groupManager->getUserGroupIds($user);
1755
-				if ((bool)array_intersect($excludedGroups, $userGroups)) {
1756
-					return false;
1757
-				}
1758
-			}
1759
-		}
1760
-		return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_ENFORCED);
1761
-	}
1762
-
1763
-	/**
1764
-	 * Is default link expire date enabled
1765
-	 *
1766
-	 * @return bool
1767
-	 */
1768
-	public function shareApiLinkDefaultExpireDate() {
1769
-		return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_DEFAULT);
1770
-	}
1771
-
1772
-	/**
1773
-	 * Is default link expire date enforced
1774
-	 *
1775
-	 * @return bool
1776
-	 */
1777
-	public function shareApiLinkDefaultExpireDateEnforced() {
1778
-		return $this->shareApiLinkDefaultExpireDate()
1779
-			&& $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_ENFORCED);
1780
-	}
1781
-
1782
-
1783
-	/**
1784
-	 * Number of default link expire days
1785
-	 *
1786
-	 * @return int
1787
-	 */
1788
-	public function shareApiLinkDefaultExpireDays() {
1789
-		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1790
-	}
1791
-
1792
-	/**
1793
-	 * Is default internal expire date enabled
1794
-	 *
1795
-	 * @return bool
1796
-	 */
1797
-	public function shareApiInternalDefaultExpireDate(): bool {
1798
-		return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
1799
-	}
1800
-
1801
-	/**
1802
-	 * Is default remote expire date enabled
1803
-	 *
1804
-	 * @return bool
1805
-	 */
1806
-	public function shareApiRemoteDefaultExpireDate(): bool {
1807
-		return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
1808
-	}
1809
-
1810
-	/**
1811
-	 * Is default expire date enforced
1812
-	 *
1813
-	 * @return bool
1814
-	 */
1815
-	public function shareApiInternalDefaultExpireDateEnforced(): bool {
1816
-		return $this->shareApiInternalDefaultExpireDate()
1817
-			&& $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
1818
-	}
1819
-
1820
-	/**
1821
-	 * Is default expire date enforced for remote shares
1822
-	 *
1823
-	 * @return bool
1824
-	 */
1825
-	public function shareApiRemoteDefaultExpireDateEnforced(): bool {
1826
-		return $this->shareApiRemoteDefaultExpireDate()
1827
-			&& $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
1828
-	}
1829
-
1830
-	/**
1831
-	 * Number of default expire days
1832
-	 *
1833
-	 * @return int
1834
-	 */
1835
-	public function shareApiInternalDefaultExpireDays(): int {
1836
-		return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
1837
-	}
1838
-
1839
-	/**
1840
-	 * Number of default expire days for remote shares
1841
-	 *
1842
-	 * @return int
1843
-	 */
1844
-	public function shareApiRemoteDefaultExpireDays(): int {
1845
-		return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
1846
-	}
1847
-
1848
-	/**
1849
-	 * Allow public upload on link shares
1850
-	 *
1851
-	 * @return bool
1852
-	 */
1853
-	public function shareApiLinkAllowPublicUpload() {
1854
-		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1855
-	}
1856
-
1857
-	/**
1858
-	 * check if user can only share with group members
1859
-	 *
1860
-	 * @return bool
1861
-	 */
1862
-	public function shareWithGroupMembersOnly() {
1863
-		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1864
-	}
1865
-
1866
-	/**
1867
-	 * If shareWithGroupMembersOnly is enabled, return an optional
1868
-	 * list of groups that must be excluded from the principle of
1869
-	 * belonging to the same group.
1870
-	 *
1871
-	 * @return array
1872
-	 */
1873
-	public function shareWithGroupMembersOnlyExcludeGroupsList() {
1874
-		if (!$this->shareWithGroupMembersOnly()) {
1875
-			return [];
1876
-		}
1877
-		$excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
1878
-		return json_decode($excludeGroups, true) ?? [];
1879
-	}
1880
-
1881
-	/**
1882
-	 * Check if users can share with groups
1883
-	 *
1884
-	 * @return bool
1885
-	 */
1886
-	public function allowGroupSharing() {
1887
-		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1888
-	}
1889
-
1890
-	public function allowEnumeration(): bool {
1891
-		return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
1892
-	}
1893
-
1894
-	public function limitEnumerationToGroups(): bool {
1895
-		return $this->allowEnumeration()
1896
-			&& $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
1897
-	}
1898
-
1899
-	public function limitEnumerationToPhone(): bool {
1900
-		return $this->allowEnumeration()
1901
-			&& $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
1902
-	}
1903
-
1904
-	public function allowEnumerationFullMatch(): bool {
1905
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
1906
-	}
1907
-
1908
-	public function matchEmail(): bool {
1909
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
1910
-	}
1911
-
1912
-	public function matchUserId(): bool {
1913
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_user_id', 'yes') === 'yes';
1914
-	}
1915
-
1916
-	public function ignoreSecondDisplayName(): bool {
1917
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
1918
-	}
1919
-
1920
-	public function allowCustomTokens(): bool {
1921
-		return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_CUSTOM_TOKEN);
1922
-	}
1923
-
1924
-	public function allowViewWithoutDownload(): bool {
1925
-		return $this->appConfig->getValueBool('core', 'shareapi_allow_view_without_download', true);
1926
-	}
1927
-
1928
-	public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool {
1929
-		if ($this->allowEnumerationFullMatch()) {
1930
-			return true;
1931
-		}
1932
-
1933
-		if (!$this->allowEnumeration()) {
1934
-			return false;
1935
-		}
1936
-
1937
-		if (!$this->limitEnumerationToPhone() && !$this->limitEnumerationToGroups()) {
1938
-			// Enumeration is enabled and not restricted: OK
1939
-			return true;
1940
-		}
1941
-
1942
-		if (!$currentUser instanceof IUser) {
1943
-			// Enumeration restrictions require an account
1944
-			return false;
1945
-		}
1946
-
1947
-		// Enumeration is limited to phone match
1948
-		if ($this->limitEnumerationToPhone() && $this->knownUserService->isKnownToUser($currentUser->getUID(), $targetUser->getUID())) {
1949
-			return true;
1950
-		}
1951
-
1952
-		// Enumeration is limited to groups
1953
-		if ($this->limitEnumerationToGroups()) {
1954
-			$currentUserGroupIds = $this->groupManager->getUserGroupIds($currentUser);
1955
-			$targetUserGroupIds = $this->groupManager->getUserGroupIds($targetUser);
1956
-			if (!empty(array_intersect($currentUserGroupIds, $targetUserGroupIds))) {
1957
-				return true;
1958
-			}
1959
-		}
1960
-
1961
-		return false;
1962
-	}
1963
-
1964
-	/**
1965
-	 * Check if sharing is disabled for the current user
1966
-	 */
1967
-	public function sharingDisabledForUser(?string $userId): bool {
1968
-		return $this->shareDisableChecker->sharingDisabledForUser($userId);
1969
-	}
1970
-
1971
-	/**
1972
-	 * @inheritdoc
1973
-	 */
1974
-	public function outgoingServer2ServerSharesAllowed() {
1975
-		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1976
-	}
1977
-
1978
-	/**
1979
-	 * @inheritdoc
1980
-	 */
1981
-	public function outgoingServer2ServerGroupSharesAllowed() {
1982
-		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1983
-	}
1984
-
1985
-	/**
1986
-	 * @inheritdoc
1987
-	 */
1988
-	public function shareProviderExists($shareType) {
1989
-		try {
1990
-			$this->factory->getProviderForType($shareType);
1991
-		} catch (ProviderException $e) {
1992
-			return false;
1993
-		}
1994
-
1995
-		return true;
1996
-	}
1997
-
1998
-	public function registerShareProvider(string $shareProviderClass): void {
1999
-		$this->factory->registerProvider($shareProviderClass);
2000
-	}
2001
-
2002
-	public function getAllShares(): iterable {
2003
-		$providers = $this->factory->getAllProviders();
2004
-
2005
-		foreach ($providers as $provider) {
2006
-			yield from $provider->getAllShares();
2007
-		}
2008
-	}
2009
-
2010
-	public function generateToken(): string {
2011
-		// Initial token length
2012
-		$tokenLength = \OC\Share\Helper::getTokenLength();
2013
-
2014
-		do {
2015
-			$tokenExists = false;
2016
-
2017
-			for ($i = 0; $i <= 2; $i++) {
2018
-				// Generate a new token
2019
-				$token = $this->secureRandom->generate(
2020
-					$tokenLength,
2021
-					ISecureRandom::CHAR_HUMAN_READABLE,
2022
-				);
2023
-
2024
-				try {
2025
-					// Try to fetch a share with the generated token
2026
-					$this->getShareByToken($token);
2027
-					$tokenExists = true; // Token exists, we need to try again
2028
-				} catch (ShareNotFound $e) {
2029
-					// Token is unique, exit the loop
2030
-					$tokenExists = false;
2031
-					break;
2032
-				}
2033
-			}
2034
-
2035
-			// If we've reached the maximum attempts and the token still exists, increase the token length
2036
-			if ($tokenExists) {
2037
-				$tokenLength++;
2038
-
2039
-				// Check if the token length exceeds the maximum allowed length
2040
-				if ($tokenLength > \OC\Share\Constants::MAX_TOKEN_LENGTH) {
2041
-					throw new ShareTokenException('Unable to generate a unique share token. Maximum token length exceeded.');
2042
-				}
2043
-			}
2044
-		} while ($tokenExists);
2045
-
2046
-		return $token;
2047
-	}
2048
-
2049
-	private function dispatchEvent(Event $event, string $name): void {
2050
-		try {
2051
-			$this->dispatcher->dispatchTyped($event);
2052
-		} catch (\Exception $e) {
2053
-			$this->logger->error("Error while sending ' . $name . ' event", ['exception' => $e]);
2054
-		}
2055
-	}
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
+     * @param int &$added If given, will be decremented if the share is deleted
1484
+     * @throws ShareNotFound
1485
+     */
1486
+    private function checkShare(IShare $share, int &$added = 1): void {
1487
+        if ($share->isExpired()) {
1488
+            $this->deleteShare($share);
1489
+            // Remove 1 to added, because this share was deleted
1490
+            $added--;
1491
+            throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1492
+        }
1493
+        if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
1494
+            $uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
1495
+            foreach ($uids as $uid) {
1496
+                $user = $this->userManager->get($uid);
1497
+                if ($user?->isEnabled() === false) {
1498
+                    throw new ShareNotFound($this->l->t('The requested share comes from a disabled user'));
1499
+                }
1500
+            }
1501
+        }
1502
+
1503
+        // For link and email shares, verify the share owner can still create such shares
1504
+        if ($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL) {
1505
+            $shareOwner = $this->userManager->get($share->getShareOwner());
1506
+            if ($shareOwner === null) {
1507
+                throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1508
+            }
1509
+            if (!$this->userCanCreateLinkShares($shareOwner)) {
1510
+                throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1511
+            }
1512
+        }
1513
+    }
1514
+
1515
+    /**
1516
+     * Verify the password of a public share
1517
+     *
1518
+     * @param IShare $share
1519
+     * @param ?string $password
1520
+     * @return bool
1521
+     */
1522
+    public function checkPassword(IShare $share, $password) {
1523
+
1524
+        // if there is no password on the share object / passsword is null, there is nothing to check
1525
+        if ($password === null || $share->getPassword() === null) {
1526
+            return false;
1527
+        }
1528
+
1529
+        // Makes sure password hasn't expired
1530
+        $expirationTime = $share->getPasswordExpirationTime();
1531
+        if ($expirationTime !== null && $expirationTime < new \DateTime()) {
1532
+            return false;
1533
+        }
1534
+
1535
+        $newHash = '';
1536
+        if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1537
+            return false;
1538
+        }
1539
+
1540
+        if (!empty($newHash)) {
1541
+            $share->setPassword($newHash);
1542
+            $provider = $this->factory->getProviderForType($share->getShareType());
1543
+            $provider->update($share);
1544
+        }
1545
+
1546
+        return true;
1547
+    }
1548
+
1549
+    /**
1550
+     * @inheritdoc
1551
+     */
1552
+    public function userDeleted($uid) {
1553
+        $types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
1554
+
1555
+        foreach ($types as $type) {
1556
+            try {
1557
+                $provider = $this->factory->getProviderForType($type);
1558
+            } catch (ProviderException $e) {
1559
+                continue;
1560
+            }
1561
+            $provider->userDeleted($uid, $type);
1562
+        }
1563
+    }
1564
+
1565
+    /**
1566
+     * @inheritdoc
1567
+     */
1568
+    public function groupDeleted($gid) {
1569
+        foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1570
+            try {
1571
+                $provider = $this->factory->getProviderForType($type);
1572
+            } catch (ProviderException $e) {
1573
+                continue;
1574
+            }
1575
+            $provider->groupDeleted($gid);
1576
+        }
1577
+
1578
+        $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1579
+        if ($excludedGroups === '') {
1580
+            return;
1581
+        }
1582
+
1583
+        $excludedGroups = json_decode($excludedGroups, true);
1584
+        if (json_last_error() !== JSON_ERROR_NONE) {
1585
+            return;
1586
+        }
1587
+
1588
+        $excludedGroups = array_diff($excludedGroups, [$gid]);
1589
+        $this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
1590
+    }
1591
+
1592
+    /**
1593
+     * @inheritdoc
1594
+     */
1595
+    public function userDeletedFromGroup($uid, $gid) {
1596
+        foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1597
+            try {
1598
+                $provider = $this->factory->getProviderForType($type);
1599
+            } catch (ProviderException $e) {
1600
+                continue;
1601
+            }
1602
+            $provider->userDeletedFromGroup($uid, $gid);
1603
+        }
1604
+    }
1605
+
1606
+    #[\Override]
1607
+    public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1608
+        $owner = $path->getOwner();
1609
+
1610
+        if ($owner === null) {
1611
+            return [];
1612
+        }
1613
+
1614
+        $owner = $owner->getUID();
1615
+
1616
+        if ($currentAccess) {
1617
+            $al = ['users' => [], 'remote' => [], 'public' => false, 'mail' => []];
1618
+        } else {
1619
+            $al = ['users' => [], 'remote' => false, 'public' => false, 'mail' => []];
1620
+        }
1621
+        if (!$this->userManager->userExists($owner)) {
1622
+            return $al;
1623
+        }
1624
+
1625
+        //Get node for the owner and correct the owner in case of external storage
1626
+        $userFolder = $this->rootFolder->getUserFolder($owner);
1627
+        if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1628
+            $path = $userFolder->getFirstNodeById($path->getId());
1629
+            if ($path === null || $path->getOwner() === null) {
1630
+                return [];
1631
+            }
1632
+            $owner = $path->getOwner()->getUID();
1633
+        }
1634
+
1635
+        $providers = $this->factory->getAllProviders();
1636
+
1637
+        /** @var Node[] $nodes */
1638
+        $nodes = [];
1639
+
1640
+
1641
+        if ($currentAccess) {
1642
+            $ownerPath = $path->getPath();
1643
+            $ownerPath = explode('/', $ownerPath, 4);
1644
+            if (count($ownerPath) < 4) {
1645
+                $ownerPath = '';
1646
+            } else {
1647
+                $ownerPath = $ownerPath[3];
1648
+            }
1649
+            $al['users'][$owner] = [
1650
+                'node_id' => $path->getId(),
1651
+                'node_path' => '/' . $ownerPath,
1652
+            ];
1653
+        } else {
1654
+            $al['users'][] = $owner;
1655
+        }
1656
+
1657
+        // Collect all the shares
1658
+        while ($path->getPath() !== $userFolder->getPath()) {
1659
+            $nodes[] = $path;
1660
+            if (!$recursive) {
1661
+                break;
1662
+            }
1663
+            $path = $path->getParent();
1664
+        }
1665
+
1666
+        foreach ($providers as $provider) {
1667
+            $tmp = $provider->getAccessList($nodes, $currentAccess);
1668
+
1669
+            foreach ($tmp as $k => $v) {
1670
+                if (isset($al[$k])) {
1671
+                    if (is_array($al[$k])) {
1672
+                        if ($currentAccess) {
1673
+                            $al[$k] += $v;
1674
+                        } else {
1675
+                            $al[$k] = array_merge($al[$k], $v);
1676
+                            $al[$k] = array_unique($al[$k]);
1677
+                            $al[$k] = array_values($al[$k]);
1678
+                        }
1679
+                    } else {
1680
+                        $al[$k] = $al[$k] || $v;
1681
+                    }
1682
+                } else {
1683
+                    $al[$k] = $v;
1684
+                }
1685
+            }
1686
+        }
1687
+
1688
+        return $al;
1689
+    }
1690
+
1691
+    /**
1692
+     * Create a new share
1693
+     *
1694
+     * @return IShare
1695
+     */
1696
+    public function newShare() {
1697
+        return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1698
+    }
1699
+
1700
+    /**
1701
+     * Is the share API enabled
1702
+     *
1703
+     * @return bool
1704
+     */
1705
+    public function shareApiEnabled() {
1706
+        return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1707
+    }
1708
+
1709
+    /**
1710
+     * Is public link sharing enabled
1711
+     *
1712
+     * @param ?IUser $user User to check against group exclusions, defaults to current session user
1713
+     * @return bool
1714
+     */
1715
+    public function shareApiAllowLinks(?IUser $user = null) {
1716
+        if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1717
+            return false;
1718
+        }
1719
+
1720
+        $user = $user ?? $this->userSession->getUser();
1721
+        if ($user) {
1722
+            $excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
1723
+            if ($excludedGroups) {
1724
+                $userGroups = $this->groupManager->getUserGroupIds($user);
1725
+                return !(bool)array_intersect($excludedGroups, $userGroups);
1726
+            }
1727
+        }
1728
+
1729
+        return true;
1730
+    }
1731
+
1732
+    /**
1733
+     * Check if a specific user can create link shares
1734
+     *
1735
+     * @param IUser $user The user to check
1736
+     * @return bool
1737
+     */
1738
+    protected function userCanCreateLinkShares(IUser $user): bool {
1739
+        return $this->shareApiAllowLinks($user);
1740
+    }
1741
+
1742
+    /**
1743
+     * Is password on public link requires
1744
+     *
1745
+     * @param bool Check group membership exclusion
1746
+     * @return bool
1747
+     */
1748
+    public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true) {
1749
+        $excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
1750
+        if ($excludedGroups !== '' && $checkGroupMembership) {
1751
+            $excludedGroups = json_decode($excludedGroups);
1752
+            $user = $this->userSession->getUser();
1753
+            if ($user) {
1754
+                $userGroups = $this->groupManager->getUserGroupIds($user);
1755
+                if ((bool)array_intersect($excludedGroups, $userGroups)) {
1756
+                    return false;
1757
+                }
1758
+            }
1759
+        }
1760
+        return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_ENFORCED);
1761
+    }
1762
+
1763
+    /**
1764
+     * Is default link expire date enabled
1765
+     *
1766
+     * @return bool
1767
+     */
1768
+    public function shareApiLinkDefaultExpireDate() {
1769
+        return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_DEFAULT);
1770
+    }
1771
+
1772
+    /**
1773
+     * Is default link expire date enforced
1774
+     *
1775
+     * @return bool
1776
+     */
1777
+    public function shareApiLinkDefaultExpireDateEnforced() {
1778
+        return $this->shareApiLinkDefaultExpireDate()
1779
+            && $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_ENFORCED);
1780
+    }
1781
+
1782
+
1783
+    /**
1784
+     * Number of default link expire days
1785
+     *
1786
+     * @return int
1787
+     */
1788
+    public function shareApiLinkDefaultExpireDays() {
1789
+        return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1790
+    }
1791
+
1792
+    /**
1793
+     * Is default internal expire date enabled
1794
+     *
1795
+     * @return bool
1796
+     */
1797
+    public function shareApiInternalDefaultExpireDate(): bool {
1798
+        return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
1799
+    }
1800
+
1801
+    /**
1802
+     * Is default remote expire date enabled
1803
+     *
1804
+     * @return bool
1805
+     */
1806
+    public function shareApiRemoteDefaultExpireDate(): bool {
1807
+        return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
1808
+    }
1809
+
1810
+    /**
1811
+     * Is default expire date enforced
1812
+     *
1813
+     * @return bool
1814
+     */
1815
+    public function shareApiInternalDefaultExpireDateEnforced(): bool {
1816
+        return $this->shareApiInternalDefaultExpireDate()
1817
+            && $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
1818
+    }
1819
+
1820
+    /**
1821
+     * Is default expire date enforced for remote shares
1822
+     *
1823
+     * @return bool
1824
+     */
1825
+    public function shareApiRemoteDefaultExpireDateEnforced(): bool {
1826
+        return $this->shareApiRemoteDefaultExpireDate()
1827
+            && $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
1828
+    }
1829
+
1830
+    /**
1831
+     * Number of default expire days
1832
+     *
1833
+     * @return int
1834
+     */
1835
+    public function shareApiInternalDefaultExpireDays(): int {
1836
+        return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
1837
+    }
1838
+
1839
+    /**
1840
+     * Number of default expire days for remote shares
1841
+     *
1842
+     * @return int
1843
+     */
1844
+    public function shareApiRemoteDefaultExpireDays(): int {
1845
+        return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
1846
+    }
1847
+
1848
+    /**
1849
+     * Allow public upload on link shares
1850
+     *
1851
+     * @return bool
1852
+     */
1853
+    public function shareApiLinkAllowPublicUpload() {
1854
+        return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1855
+    }
1856
+
1857
+    /**
1858
+     * check if user can only share with group members
1859
+     *
1860
+     * @return bool
1861
+     */
1862
+    public function shareWithGroupMembersOnly() {
1863
+        return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1864
+    }
1865
+
1866
+    /**
1867
+     * If shareWithGroupMembersOnly is enabled, return an optional
1868
+     * list of groups that must be excluded from the principle of
1869
+     * belonging to the same group.
1870
+     *
1871
+     * @return array
1872
+     */
1873
+    public function shareWithGroupMembersOnlyExcludeGroupsList() {
1874
+        if (!$this->shareWithGroupMembersOnly()) {
1875
+            return [];
1876
+        }
1877
+        $excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
1878
+        return json_decode($excludeGroups, true) ?? [];
1879
+    }
1880
+
1881
+    /**
1882
+     * Check if users can share with groups
1883
+     *
1884
+     * @return bool
1885
+     */
1886
+    public function allowGroupSharing() {
1887
+        return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1888
+    }
1889
+
1890
+    public function allowEnumeration(): bool {
1891
+        return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
1892
+    }
1893
+
1894
+    public function limitEnumerationToGroups(): bool {
1895
+        return $this->allowEnumeration()
1896
+            && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
1897
+    }
1898
+
1899
+    public function limitEnumerationToPhone(): bool {
1900
+        return $this->allowEnumeration()
1901
+            && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
1902
+    }
1903
+
1904
+    public function allowEnumerationFullMatch(): bool {
1905
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
1906
+    }
1907
+
1908
+    public function matchEmail(): bool {
1909
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
1910
+    }
1911
+
1912
+    public function matchUserId(): bool {
1913
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_user_id', 'yes') === 'yes';
1914
+    }
1915
+
1916
+    public function ignoreSecondDisplayName(): bool {
1917
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
1918
+    }
1919
+
1920
+    public function allowCustomTokens(): bool {
1921
+        return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_CUSTOM_TOKEN);
1922
+    }
1923
+
1924
+    public function allowViewWithoutDownload(): bool {
1925
+        return $this->appConfig->getValueBool('core', 'shareapi_allow_view_without_download', true);
1926
+    }
1927
+
1928
+    public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool {
1929
+        if ($this->allowEnumerationFullMatch()) {
1930
+            return true;
1931
+        }
1932
+
1933
+        if (!$this->allowEnumeration()) {
1934
+            return false;
1935
+        }
1936
+
1937
+        if (!$this->limitEnumerationToPhone() && !$this->limitEnumerationToGroups()) {
1938
+            // Enumeration is enabled and not restricted: OK
1939
+            return true;
1940
+        }
1941
+
1942
+        if (!$currentUser instanceof IUser) {
1943
+            // Enumeration restrictions require an account
1944
+            return false;
1945
+        }
1946
+
1947
+        // Enumeration is limited to phone match
1948
+        if ($this->limitEnumerationToPhone() && $this->knownUserService->isKnownToUser($currentUser->getUID(), $targetUser->getUID())) {
1949
+            return true;
1950
+        }
1951
+
1952
+        // Enumeration is limited to groups
1953
+        if ($this->limitEnumerationToGroups()) {
1954
+            $currentUserGroupIds = $this->groupManager->getUserGroupIds($currentUser);
1955
+            $targetUserGroupIds = $this->groupManager->getUserGroupIds($targetUser);
1956
+            if (!empty(array_intersect($currentUserGroupIds, $targetUserGroupIds))) {
1957
+                return true;
1958
+            }
1959
+        }
1960
+
1961
+        return false;
1962
+    }
1963
+
1964
+    /**
1965
+     * Check if sharing is disabled for the current user
1966
+     */
1967
+    public function sharingDisabledForUser(?string $userId): bool {
1968
+        return $this->shareDisableChecker->sharingDisabledForUser($userId);
1969
+    }
1970
+
1971
+    /**
1972
+     * @inheritdoc
1973
+     */
1974
+    public function outgoingServer2ServerSharesAllowed() {
1975
+        return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1976
+    }
1977
+
1978
+    /**
1979
+     * @inheritdoc
1980
+     */
1981
+    public function outgoingServer2ServerGroupSharesAllowed() {
1982
+        return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1983
+    }
1984
+
1985
+    /**
1986
+     * @inheritdoc
1987
+     */
1988
+    public function shareProviderExists($shareType) {
1989
+        try {
1990
+            $this->factory->getProviderForType($shareType);
1991
+        } catch (ProviderException $e) {
1992
+            return false;
1993
+        }
1994
+
1995
+        return true;
1996
+    }
1997
+
1998
+    public function registerShareProvider(string $shareProviderClass): void {
1999
+        $this->factory->registerProvider($shareProviderClass);
2000
+    }
2001
+
2002
+    public function getAllShares(): iterable {
2003
+        $providers = $this->factory->getAllProviders();
2004
+
2005
+        foreach ($providers as $provider) {
2006
+            yield from $provider->getAllShares();
2007
+        }
2008
+    }
2009
+
2010
+    public function generateToken(): string {
2011
+        // Initial token length
2012
+        $tokenLength = \OC\Share\Helper::getTokenLength();
2013
+
2014
+        do {
2015
+            $tokenExists = false;
2016
+
2017
+            for ($i = 0; $i <= 2; $i++) {
2018
+                // Generate a new token
2019
+                $token = $this->secureRandom->generate(
2020
+                    $tokenLength,
2021
+                    ISecureRandom::CHAR_HUMAN_READABLE,
2022
+                );
2023
+
2024
+                try {
2025
+                    // Try to fetch a share with the generated token
2026
+                    $this->getShareByToken($token);
2027
+                    $tokenExists = true; // Token exists, we need to try again
2028
+                } catch (ShareNotFound $e) {
2029
+                    // Token is unique, exit the loop
2030
+                    $tokenExists = false;
2031
+                    break;
2032
+                }
2033
+            }
2034
+
2035
+            // If we've reached the maximum attempts and the token still exists, increase the token length
2036
+            if ($tokenExists) {
2037
+                $tokenLength++;
2038
+
2039
+                // Check if the token length exceeds the maximum allowed length
2040
+                if ($tokenLength > \OC\Share\Constants::MAX_TOKEN_LENGTH) {
2041
+                    throw new ShareTokenException('Unable to generate a unique share token. Maximum token length exceeded.');
2042
+                }
2043
+            }
2044
+        } while ($tokenExists);
2045
+
2046
+        return $token;
2047
+    }
2048
+
2049
+    private function dispatchEvent(Event $event, string $name): void {
2050
+        try {
2051
+            $this->dispatcher->dispatchTyped($event);
2052
+        } catch (\Exception $e) {
2053
+            $this->logger->error("Error while sending ' . $name . ' event", ['exception' => $e]);
2054
+        }
2055
+    }
2056 2056
 }
Please login to merge, or discard this patch.