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