Completed
Push — master ( 835746...319216 )
by John
32:58 queued 15s
created
lib/private/Share20/Manager.php 1 patch
Indentation   +1981 added lines, -1981 removed lines patch added patch discarded remove patch
@@ -59,2002 +59,2002 @@
 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
-					// Using a flat sharing model ensures the file owner can always see who has access.
601
-					// Allowing parent folder sharing would require tracking inherited access, which adds complexity
602
-					// and hurts performance/scalability.
603
-					// So we forbid sharing a parent folder of a share you received.
604
-					throw new \InvalidArgumentException($this->l->t('You cannot share a folder that contains other shares'));
605
-				}
606
-			}
607
-		}
608
-	}
609
-
610
-	/**
611
-	 * Check if the user that is sharing can actually share
612
-	 *
613
-	 * @param IShare $share
614
-	 * @throws \Exception
615
-	 */
616
-	protected function canShare(IShare $share) {
617
-		if (!$this->shareApiEnabled()) {
618
-			throw new \Exception($this->l->t('Sharing is disabled'));
619
-		}
620
-
621
-		if ($this->sharingDisabledForUser($share->getSharedBy())) {
622
-			throw new \Exception($this->l->t('Sharing is disabled for you'));
623
-		}
624
-	}
625
-
626
-	/**
627
-	 * Share a path
628
-	 *
629
-	 * @param IShare $share
630
-	 * @return IShare The share object
631
-	 * @throws \Exception
632
-	 *
633
-	 * TODO: handle link share permissions or check them
634
-	 */
635
-	public function createShare(IShare $share) {
636
-		$this->canShare($share);
637
-
638
-		$this->generalCreateChecks($share);
639
-
640
-		// Verify if there are any issues with the path
641
-		$this->pathCreateChecks($share->getNode());
642
-
643
-		/*
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
+                    // Using a flat sharing model ensures the file owner can always see who has access.
601
+                    // Allowing parent folder sharing would require tracking inherited access, which adds complexity
602
+                    // and hurts performance/scalability.
603
+                    // So we forbid sharing a parent folder of a share you received.
604
+                    throw new \InvalidArgumentException($this->l->t('You cannot share a folder that contains other shares'));
605
+                }
606
+            }
607
+        }
608
+    }
609
+
610
+    /**
611
+     * Check if the user that is sharing can actually share
612
+     *
613
+     * @param IShare $share
614
+     * @throws \Exception
615
+     */
616
+    protected function canShare(IShare $share) {
617
+        if (!$this->shareApiEnabled()) {
618
+            throw new \Exception($this->l->t('Sharing is disabled'));
619
+        }
620
+
621
+        if ($this->sharingDisabledForUser($share->getSharedBy())) {
622
+            throw new \Exception($this->l->t('Sharing is disabled for you'));
623
+        }
624
+    }
625
+
626
+    /**
627
+     * Share a path
628
+     *
629
+     * @param IShare $share
630
+     * @return IShare The share object
631
+     * @throws \Exception
632
+     *
633
+     * TODO: handle link share permissions or check them
634
+     */
635
+    public function createShare(IShare $share) {
636
+        $this->canShare($share);
637
+
638
+        $this->generalCreateChecks($share);
639
+
640
+        // Verify if there are any issues with the path
641
+        $this->pathCreateChecks($share->getNode());
642
+
643
+        /*
644 644
 		 * On creation of a share the owner is always the owner of the path
645 645
 		 * Except for mounted federated shares.
646 646
 		 */
647
-		$storage = $share->getNode()->getStorage();
648
-		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
649
-			$parent = $share->getNode()->getParent();
650
-			while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
651
-				$parent = $parent->getParent();
652
-			}
653
-			$share->setShareOwner($parent->getOwner()->getUID());
654
-		} else {
655
-			if ($share->getNode()->getOwner()) {
656
-				$share->setShareOwner($share->getNode()->getOwner()->getUID());
657
-			} else {
658
-				$share->setShareOwner($share->getSharedBy());
659
-			}
660
-		}
661
-
662
-		try {
663
-			// Verify share type
664
-			if ($share->getShareType() === IShare::TYPE_USER) {
665
-				$this->userCreateChecks($share);
666
-
667
-				// Verify the expiration date
668
-				$share = $this->validateExpirationDateInternal($share);
669
-			} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
670
-				$this->groupCreateChecks($share);
671
-
672
-				// Verify the expiration date
673
-				$share = $this->validateExpirationDateInternal($share);
674
-			} elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
675
-				// Verify the expiration date
676
-				$share = $this->validateExpirationDateInternal($share);
677
-			} elseif ($share->getShareType() === IShare::TYPE_LINK
678
-				|| $share->getShareType() === IShare::TYPE_EMAIL) {
679
-				$this->linkCreateChecks($share);
680
-				$this->setLinkParent($share);
681
-
682
-				$token = $this->generateToken();
683
-				// Set the unique token
684
-				$share->setToken($token);
685
-
686
-				// Verify the expiration date
687
-				$share = $this->validateExpirationDateLink($share);
688
-
689
-				// Verify the password
690
-				$this->verifyPassword($share->getPassword());
691
-
692
-				// If a password is set. Hash it!
693
-				if ($share->getShareType() === IShare::TYPE_LINK
694
-					&& $share->getPassword() !== null) {
695
-					$share->setPassword($this->hasher->hash($share->getPassword()));
696
-				}
697
-			}
698
-
699
-			// Cannot share with the owner
700
-			if ($share->getShareType() === IShare::TYPE_USER &&
701
-				$share->getSharedWith() === $share->getShareOwner()) {
702
-				throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
703
-			}
704
-
705
-			// Generate the target
706
-			$defaultShareFolder = $this->config->getSystemValue('share_folder', '/');
707
-			$allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
708
-			if ($allowCustomShareFolder) {
709
-				$shareFolder = $this->config->getUserValue($share->getSharedWith(), Application::APP_ID, 'share_folder', $defaultShareFolder);
710
-			} else {
711
-				$shareFolder = $defaultShareFolder;
712
-			}
713
-
714
-			$target = $shareFolder . '/' . $share->getNode()->getName();
715
-			$target = \OC\Files\Filesystem::normalizePath($target);
716
-			$share->setTarget($target);
717
-
718
-			// Pre share event
719
-			$event = new Share\Events\BeforeShareCreatedEvent($share);
720
-			$this->dispatcher->dispatchTyped($event);
721
-			if ($event->isPropagationStopped() && $event->getError()) {
722
-				throw new \Exception($event->getError());
723
-			}
724
-
725
-			$oldShare = $share;
726
-			$provider = $this->factory->getProviderForType($share->getShareType());
727
-			$share = $provider->create($share);
728
-
729
-			// Reuse the node we already have
730
-			$share->setNode($oldShare->getNode());
731
-
732
-			// Reset the target if it is null for the new share
733
-			if ($share->getTarget() === '') {
734
-				$share->setTarget($target);
735
-			}
736
-		} catch (AlreadySharedException $e) {
737
-			// If a share for the same target already exists, dont create a new one,
738
-			// but do trigger the hooks and notifications again
739
-			$oldShare = $share;
740
-
741
-			// Reuse the node we already have
742
-			$share = $e->getExistingShare();
743
-			$share->setNode($oldShare->getNode());
744
-		}
745
-
746
-		// Post share event
747
-		$this->dispatcher->dispatchTyped(new ShareCreatedEvent($share));
748
-
749
-		// Send email if needed
750
-		if ($this->config->getSystemValueBool('sharing.enable_share_mail', true)) {
751
-			if ($share->getMailSend()) {
752
-				$provider = $this->factory->getProviderForType($share->getShareType());
753
-				if ($provider instanceof IShareProviderWithNotification) {
754
-					$provider->sendMailNotification($share);
755
-				} else {
756
-					$this->logger->debug('Share notification not sent because the provider does not support it.', ['app' => 'share']);
757
-				}
758
-			} else {
759
-				$this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']);
760
-			}
761
-		} else {
762
-			$this->logger->debug('Share notification not sent because sharing notification emails is disabled.', ['app' => 'share']);
763
-		}
764
-
765
-		return $share;
766
-	}
767
-
768
-	/**
769
-	 * Update a share
770
-	 *
771
-	 * @param IShare $share
772
-	 * @return IShare The share object
773
-	 * @throws \InvalidArgumentException
774
-	 * @throws HintException
775
-	 */
776
-	public function updateShare(IShare $share, bool $onlyValid = true) {
777
-		$expirationDateUpdated = false;
778
-
779
-		$this->canShare($share);
780
-
781
-		try {
782
-			$originalShare = $this->getShareById($share->getFullId(), onlyValid: $onlyValid);
783
-		} catch (\UnexpectedValueException $e) {
784
-			throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
785
-		}
786
-
787
-		// We cannot change the share type!
788
-		if ($share->getShareType() !== $originalShare->getShareType()) {
789
-			throw new \InvalidArgumentException($this->l->t('Cannot change share type'));
790
-		}
791
-
792
-		// We can only change the recipient on user shares
793
-		if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
794
-			$share->getShareType() !== IShare::TYPE_USER) {
795
-			throw new \InvalidArgumentException($this->l->t('Can only update recipient on user shares'));
796
-		}
797
-
798
-		// Cannot share with the owner
799
-		if ($share->getShareType() === IShare::TYPE_USER &&
800
-			$share->getSharedWith() === $share->getShareOwner()) {
801
-			throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
802
-		}
803
-
804
-		$this->generalCreateChecks($share, true);
805
-
806
-		if ($share->getShareType() === IShare::TYPE_USER) {
807
-			$this->userCreateChecks($share);
808
-
809
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
810
-				// Verify the expiration date
811
-				$this->validateExpirationDateInternal($share);
812
-				$expirationDateUpdated = true;
813
-			}
814
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
815
-			$this->groupCreateChecks($share);
816
-
817
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
818
-				// Verify the expiration date
819
-				$this->validateExpirationDateInternal($share);
820
-				$expirationDateUpdated = true;
821
-			}
822
-		} elseif ($share->getShareType() === IShare::TYPE_LINK
823
-			|| $share->getShareType() === IShare::TYPE_EMAIL) {
824
-			$this->linkCreateChecks($share);
825
-
826
-			// The new password is not set again if it is the same as the old
827
-			// one, unless when switching from sending by Talk to sending by
828
-			// mail.
829
-			$plainTextPassword = $share->getPassword();
830
-			$updatedPassword = $this->updateSharePasswordIfNeeded($share, $originalShare);
831
-
832
-			/**
833
-			 * Cannot enable the getSendPasswordByTalk if there is no password set
834
-			 */
835
-			if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) {
836
-				throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk with an empty password'));
837
-			}
838
-
839
-			/**
840
-			 * If we're in a mail share, we need to force a password change
841
-			 * as either the user is not aware of the password or is already (received by mail)
842
-			 * Thus the SendPasswordByTalk feature would not make sense
843
-			 */
844
-			if (!$updatedPassword && $share->getShareType() === IShare::TYPE_EMAIL) {
845
-				if (!$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) {
846
-					throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk without setting a new password'));
847
-				}
848
-				if ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) {
849
-					throw new \InvalidArgumentException($this->l->t('Cannot disable sending the password by Talk without setting a new password'));
850
-				}
851
-			}
852
-
853
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
854
-				// Verify the expiration date
855
-				$this->validateExpirationDateLink($share);
856
-				$expirationDateUpdated = true;
857
-			}
858
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
859
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
860
-				// Verify the expiration date
861
-				$this->validateExpirationDateInternal($share);
862
-				$expirationDateUpdated = true;
863
-			}
864
-		}
865
-
866
-		$this->pathCreateChecks($share->getNode());
867
-
868
-		// Now update the share!
869
-		$provider = $this->factory->getProviderForType($share->getShareType());
870
-		if ($share->getShareType() === IShare::TYPE_EMAIL) {
871
-			$share = $provider->update($share, $plainTextPassword);
872
-		} else {
873
-			$share = $provider->update($share);
874
-		}
875
-
876
-		if ($expirationDateUpdated === true) {
877
-			\OC_Hook::emit(Share::class, 'post_set_expiration_date', [
878
-				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
879
-				'itemSource' => $share->getNode()->getId(),
880
-				'date' => $share->getExpirationDate(),
881
-				'uidOwner' => $share->getSharedBy(),
882
-			]);
883
-		}
884
-
885
-		if ($share->getPassword() !== $originalShare->getPassword()) {
886
-			\OC_Hook::emit(Share::class, 'post_update_password', [
887
-				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
888
-				'itemSource' => $share->getNode()->getId(),
889
-				'uidOwner' => $share->getSharedBy(),
890
-				'token' => $share->getToken(),
891
-				'disabled' => is_null($share->getPassword()),
892
-			]);
893
-		}
894
-
895
-		if ($share->getPermissions() !== $originalShare->getPermissions()) {
896
-			if ($this->userManager->userExists($share->getShareOwner())) {
897
-				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
898
-			} else {
899
-				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
900
-			}
901
-			\OC_Hook::emit(Share::class, 'post_update_permissions', [
902
-				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
903
-				'itemSource' => $share->getNode()->getId(),
904
-				'shareType' => $share->getShareType(),
905
-				'shareWith' => $share->getSharedWith(),
906
-				'uidOwner' => $share->getSharedBy(),
907
-				'permissions' => $share->getPermissions(),
908
-				'attributes' => $share->getAttributes() !== null ? $share->getAttributes()->toArray() : null,
909
-				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
910
-			]);
911
-		}
912
-
913
-		return $share;
914
-	}
915
-
916
-	/**
917
-	 * Accept a share.
918
-	 *
919
-	 * @param IShare $share
920
-	 * @param string $recipientId
921
-	 * @return IShare The share object
922
-	 * @throws \InvalidArgumentException Thrown if the provider does not implement `IShareProviderSupportsAccept`
923
-	 * @since 9.0.0
924
-	 */
925
-	public function acceptShare(IShare $share, string $recipientId): IShare {
926
-		[$providerId,] = $this->splitFullId($share->getFullId());
927
-		$provider = $this->factory->getProvider($providerId);
928
-
929
-		if (!($provider instanceof IShareProviderSupportsAccept)) {
930
-			throw new \InvalidArgumentException($this->l->t('Share provider does not support accepting'));
931
-		}
932
-		/** @var IShareProvider&IShareProviderSupportsAccept $provider */
933
-		$provider->acceptShare($share, $recipientId);
934
-
935
-		$event = new ShareAcceptedEvent($share);
936
-		$this->dispatcher->dispatchTyped($event);
937
-
938
-		return $share;
939
-	}
940
-
941
-	/**
942
-	 * Updates the password of the given share if it is not the same as the
943
-	 * password of the original share.
944
-	 *
945
-	 * @param IShare $share the share to update its password.
946
-	 * @param IShare $originalShare the original share to compare its
947
-	 *                              password with.
948
-	 * @return boolean whether the password was updated or not.
949
-	 */
950
-	private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare) {
951
-		$passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword()) &&
952
-			(($share->getPassword() !== null && $originalShare->getPassword() === null) ||
953
-				($share->getPassword() === null && $originalShare->getPassword() !== null) ||
954
-				($share->getPassword() !== null && $originalShare->getPassword() !== null &&
955
-					!$this->hasher->verify($share->getPassword(), $originalShare->getPassword())));
956
-
957
-		// Password updated.
958
-		if ($passwordsAreDifferent) {
959
-			// Verify the password
960
-			$this->verifyPassword($share->getPassword());
961
-
962
-			// If a password is set. Hash it!
963
-			if (!empty($share->getPassword())) {
964
-				$share->setPassword($this->hasher->hash($share->getPassword()));
965
-				if ($share->getShareType() === IShare::TYPE_EMAIL) {
966
-					// Shares shared by email have temporary passwords
967
-					$this->setSharePasswordExpirationTime($share);
968
-				}
969
-
970
-				return true;
971
-			} else {
972
-				// Empty string and null are seen as NOT password protected
973
-				$share->setPassword(null);
974
-				if ($share->getShareType() === IShare::TYPE_EMAIL) {
975
-					$share->setPasswordExpirationTime(null);
976
-				}
977
-				return true;
978
-			}
979
-		} else {
980
-			// Reset the password to the original one, as it is either the same
981
-			// as the "new" password or a hashed version of it.
982
-			$share->setPassword($originalShare->getPassword());
983
-		}
984
-
985
-		return false;
986
-	}
987
-
988
-	/**
989
-	 * Set the share's password expiration time
990
-	 */
991
-	private function setSharePasswordExpirationTime(IShare $share): void {
992
-		if (!$this->config->getSystemValueBool('sharing.enable_mail_link_password_expiration', false)) {
993
-			// Sets password expiration date to NULL
994
-			$share->setPasswordExpirationTime();
995
-			return;
996
-		}
997
-		// Sets password expiration date
998
-		$expirationTime = null;
999
-		$now = new \DateTime();
1000
-		$expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
1001
-		$expirationTime = $now->add(new \DateInterval('PT' . $expirationInterval . 'S'));
1002
-		$share->setPasswordExpirationTime($expirationTime);
1003
-	}
1004
-
1005
-
1006
-	/**
1007
-	 * Delete all the children of this share
1008
-	 * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
1009
-	 *
1010
-	 * @param IShare $share
1011
-	 * @return IShare[] List of deleted shares
1012
-	 */
1013
-	protected function deleteChildren(IShare $share) {
1014
-		$deletedShares = [];
1015
-
1016
-		$provider = $this->factory->getProviderForType($share->getShareType());
1017
-
1018
-		foreach ($provider->getChildren($share) as $child) {
1019
-			$this->dispatcher->dispatchTyped(new BeforeShareDeletedEvent($child));
1020
-
1021
-			$deletedChildren = $this->deleteChildren($child);
1022
-			$deletedShares = array_merge($deletedShares, $deletedChildren);
1023
-
1024
-			$provider->delete($child);
1025
-			$this->dispatcher->dispatchTyped(new ShareDeletedEvent($child));
1026
-			$deletedShares[] = $child;
1027
-		}
1028
-
1029
-		return $deletedShares;
1030
-	}
1031
-
1032
-	/** Promote re-shares into direct shares so that target user keeps access */
1033
-	protected function promoteReshares(IShare $share): void {
1034
-		try {
1035
-			$node = $share->getNode();
1036
-		} catch (NotFoundException) {
1037
-			/* Skip if node not found */
1038
-			return;
1039
-		}
1040
-
1041
-		$userIds = [];
1042
-
1043
-		if ($share->getShareType() === IShare::TYPE_USER) {
1044
-			$userIds[] = $share->getSharedWith();
1045
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1046
-			$group = $this->groupManager->get($share->getSharedWith());
1047
-			$users = $group?->getUsers() ?? [];
1048
-
1049
-			foreach ($users as $user) {
1050
-				/* Skip share owner */
1051
-				if ($user->getUID() === $share->getShareOwner() || $user->getUID() === $share->getSharedBy()) {
1052
-					continue;
1053
-				}
1054
-				$userIds[] = $user->getUID();
1055
-			}
1056
-		} else {
1057
-			/* We only support user and group shares */
1058
-			return;
1059
-		}
1060
-
1061
-		$reshareRecords = [];
1062
-		$shareTypes = [
1063
-			IShare::TYPE_GROUP,
1064
-			IShare::TYPE_USER,
1065
-			IShare::TYPE_LINK,
1066
-			IShare::TYPE_REMOTE,
1067
-			IShare::TYPE_EMAIL,
1068
-		];
1069
-
1070
-		foreach ($userIds as $userId) {
1071
-			foreach ($shareTypes as $shareType) {
1072
-				try {
1073
-					$provider = $this->factory->getProviderForType($shareType);
1074
-				} catch (ProviderException $e) {
1075
-					continue;
1076
-				}
1077
-
1078
-				if ($node instanceof Folder) {
1079
-					/* We need to get all shares by this user to get subshares */
1080
-					$shares = $provider->getSharesBy($userId, $shareType, null, false, -1, 0);
1081
-
1082
-					foreach ($shares as $share) {
1083
-						try {
1084
-							$path = $share->getNode()->getPath();
1085
-						} catch (NotFoundException) {
1086
-							/* Ignore share of non-existing node */
1087
-							continue;
1088
-						}
1089
-						if ($node->getRelativePath($path) !== null) {
1090
-							/* If relative path is not null it means the shared node is the same or in a subfolder */
1091
-							$reshareRecords[] = $share;
1092
-						}
1093
-					}
1094
-				} else {
1095
-					$shares = $provider->getSharesBy($userId, $shareType, $node, false, -1, 0);
1096
-					foreach ($shares as $child) {
1097
-						$reshareRecords[] = $child;
1098
-					}
1099
-				}
1100
-			}
1101
-		}
1102
-
1103
-		foreach ($reshareRecords as $child) {
1104
-			try {
1105
-				/* Check if the share is still valid (means the resharer still has access to the file through another mean) */
1106
-				$this->generalCreateChecks($child);
1107
-			} catch (GenericShareException $e) {
1108
-				/* The check is invalid, promote it to a direct share from the sharer of parent share */
1109
-				$this->logger->debug('Promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1110
-				try {
1111
-					$child->setSharedBy($share->getSharedBy());
1112
-					$this->updateShare($child);
1113
-				} catch (GenericShareException|\InvalidArgumentException $e) {
1114
-					$this->logger->warning('Failed to promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1115
-				}
1116
-			}
1117
-		}
1118
-	}
1119
-
1120
-	/**
1121
-	 * Delete a share
1122
-	 *
1123
-	 * @param IShare $share
1124
-	 * @throws ShareNotFound
1125
-	 * @throws \InvalidArgumentException
1126
-	 */
1127
-	public function deleteShare(IShare $share) {
1128
-		try {
1129
-			$share->getFullId();
1130
-		} catch (\UnexpectedValueException $e) {
1131
-			throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
1132
-		}
1133
-
1134
-		$this->dispatcher->dispatchTyped(new BeforeShareDeletedEvent($share));
1135
-
1136
-		// Get all children and delete them as well
1137
-		$this->deleteChildren($share);
1138
-
1139
-		// Do the actual delete
1140
-		$provider = $this->factory->getProviderForType($share->getShareType());
1141
-		$provider->delete($share);
1142
-
1143
-		$this->dispatcher->dispatchTyped(new ShareDeletedEvent($share));
1144
-
1145
-		// Promote reshares of the deleted share
1146
-		$this->promoteReshares($share);
1147
-	}
1148
-
1149
-
1150
-	/**
1151
-	 * Unshare a file as the recipient.
1152
-	 * This can be different from a regular delete for example when one of
1153
-	 * the users in a groups deletes that share. But the provider should
1154
-	 * handle this.
1155
-	 *
1156
-	 * @param IShare $share
1157
-	 * @param string $recipientId
1158
-	 */
1159
-	public function deleteFromSelf(IShare $share, $recipientId) {
1160
-		[$providerId,] = $this->splitFullId($share->getFullId());
1161
-		$provider = $this->factory->getProvider($providerId);
1162
-
1163
-		$provider->deleteFromSelf($share, $recipientId);
1164
-		$event = new ShareDeletedFromSelfEvent($share);
1165
-		$this->dispatcher->dispatchTyped($event);
1166
-	}
1167
-
1168
-	public function restoreShare(IShare $share, string $recipientId): IShare {
1169
-		[$providerId,] = $this->splitFullId($share->getFullId());
1170
-		$provider = $this->factory->getProvider($providerId);
1171
-
1172
-		return $provider->restore($share, $recipientId);
1173
-	}
1174
-
1175
-	/**
1176
-	 * @inheritdoc
1177
-	 */
1178
-	public function moveShare(IShare $share, $recipientId) {
1179
-		if ($share->getShareType() === IShare::TYPE_LINK
1180
-			|| $share->getShareType() === IShare::TYPE_EMAIL) {
1181
-			throw new \InvalidArgumentException($this->l->t('Cannot change target of link share'));
1182
-		}
1183
-
1184
-		if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) {
1185
-			throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1186
-		}
1187
-
1188
-		if ($share->getShareType() === IShare::TYPE_GROUP) {
1189
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
1190
-			if (is_null($sharedWith)) {
1191
-				throw new \InvalidArgumentException($this->l->t('Group "%s" does not exist', [$share->getSharedWith()]));
1192
-			}
1193
-			$recipient = $this->userManager->get($recipientId);
1194
-			if (!$sharedWith->inGroup($recipient)) {
1195
-				throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1196
-			}
1197
-		}
1198
-
1199
-		[$providerId,] = $this->splitFullId($share->getFullId());
1200
-		$provider = $this->factory->getProvider($providerId);
1201
-
1202
-		return $provider->move($share, $recipientId);
1203
-	}
1204
-
1205
-	public function getSharesInFolder($userId, Folder $node, $reshares = false, $shallow = true) {
1206
-		$providers = $this->factory->getAllProviders();
1207
-		if (!$shallow) {
1208
-			throw new \Exception('non-shallow getSharesInFolder is no longer supported');
1209
-		}
1210
-
1211
-		$isOwnerless = $node->getMountPoint() instanceof IShareOwnerlessMount;
1212
-
1213
-		$shares = [];
1214
-		foreach ($providers as $provider) {
1215
-			if ($isOwnerless) {
1216
-				foreach ($node->getDirectoryListing() as $childNode) {
1217
-					$data = $provider->getSharesByPath($childNode);
1218
-					$fid = $childNode->getId();
1219
-					$shares[$fid] ??= [];
1220
-					$shares[$fid] = array_merge($shares[$fid], $data);
1221
-				}
1222
-			} else {
1223
-				foreach ($provider->getSharesInFolder($userId, $node, $reshares) as $fid => $data) {
1224
-					$shares[$fid] ??= [];
1225
-					$shares[$fid] = array_merge($shares[$fid], $data);
1226
-				}
1227
-			}
1228
-		}
1229
-
1230
-		return $shares;
1231
-	}
1232
-
1233
-	/**
1234
-	 * @inheritdoc
1235
-	 */
1236
-	public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, bool $onlyValid = true) {
1237
-		if ($path !== null &&
1238
-			!($path instanceof \OCP\Files\File) &&
1239
-			!($path instanceof \OCP\Files\Folder)) {
1240
-			throw new \InvalidArgumentException($this->l->t('Invalid path'));
1241
-		}
1242
-
1243
-		try {
1244
-			$provider = $this->factory->getProviderForType($shareType);
1245
-		} catch (ProviderException $e) {
1246
-			return [];
1247
-		}
1248
-
1249
-		if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1250
-			$shares = array_filter($provider->getSharesByPath($path), static fn (IShare $share) => $share->getShareType() === $shareType);
1251
-		} else {
1252
-			$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1253
-		}
1254
-
1255
-		/*
647
+        $storage = $share->getNode()->getStorage();
648
+        if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
649
+            $parent = $share->getNode()->getParent();
650
+            while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
651
+                $parent = $parent->getParent();
652
+            }
653
+            $share->setShareOwner($parent->getOwner()->getUID());
654
+        } else {
655
+            if ($share->getNode()->getOwner()) {
656
+                $share->setShareOwner($share->getNode()->getOwner()->getUID());
657
+            } else {
658
+                $share->setShareOwner($share->getSharedBy());
659
+            }
660
+        }
661
+
662
+        try {
663
+            // Verify share type
664
+            if ($share->getShareType() === IShare::TYPE_USER) {
665
+                $this->userCreateChecks($share);
666
+
667
+                // Verify the expiration date
668
+                $share = $this->validateExpirationDateInternal($share);
669
+            } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
670
+                $this->groupCreateChecks($share);
671
+
672
+                // Verify the expiration date
673
+                $share = $this->validateExpirationDateInternal($share);
674
+            } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
675
+                // Verify the expiration date
676
+                $share = $this->validateExpirationDateInternal($share);
677
+            } elseif ($share->getShareType() === IShare::TYPE_LINK
678
+                || $share->getShareType() === IShare::TYPE_EMAIL) {
679
+                $this->linkCreateChecks($share);
680
+                $this->setLinkParent($share);
681
+
682
+                $token = $this->generateToken();
683
+                // Set the unique token
684
+                $share->setToken($token);
685
+
686
+                // Verify the expiration date
687
+                $share = $this->validateExpirationDateLink($share);
688
+
689
+                // Verify the password
690
+                $this->verifyPassword($share->getPassword());
691
+
692
+                // If a password is set. Hash it!
693
+                if ($share->getShareType() === IShare::TYPE_LINK
694
+                    && $share->getPassword() !== null) {
695
+                    $share->setPassword($this->hasher->hash($share->getPassword()));
696
+                }
697
+            }
698
+
699
+            // Cannot share with the owner
700
+            if ($share->getShareType() === IShare::TYPE_USER &&
701
+                $share->getSharedWith() === $share->getShareOwner()) {
702
+                throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
703
+            }
704
+
705
+            // Generate the target
706
+            $defaultShareFolder = $this->config->getSystemValue('share_folder', '/');
707
+            $allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
708
+            if ($allowCustomShareFolder) {
709
+                $shareFolder = $this->config->getUserValue($share->getSharedWith(), Application::APP_ID, 'share_folder', $defaultShareFolder);
710
+            } else {
711
+                $shareFolder = $defaultShareFolder;
712
+            }
713
+
714
+            $target = $shareFolder . '/' . $share->getNode()->getName();
715
+            $target = \OC\Files\Filesystem::normalizePath($target);
716
+            $share->setTarget($target);
717
+
718
+            // Pre share event
719
+            $event = new Share\Events\BeforeShareCreatedEvent($share);
720
+            $this->dispatcher->dispatchTyped($event);
721
+            if ($event->isPropagationStopped() && $event->getError()) {
722
+                throw new \Exception($event->getError());
723
+            }
724
+
725
+            $oldShare = $share;
726
+            $provider = $this->factory->getProviderForType($share->getShareType());
727
+            $share = $provider->create($share);
728
+
729
+            // Reuse the node we already have
730
+            $share->setNode($oldShare->getNode());
731
+
732
+            // Reset the target if it is null for the new share
733
+            if ($share->getTarget() === '') {
734
+                $share->setTarget($target);
735
+            }
736
+        } catch (AlreadySharedException $e) {
737
+            // If a share for the same target already exists, dont create a new one,
738
+            // but do trigger the hooks and notifications again
739
+            $oldShare = $share;
740
+
741
+            // Reuse the node we already have
742
+            $share = $e->getExistingShare();
743
+            $share->setNode($oldShare->getNode());
744
+        }
745
+
746
+        // Post share event
747
+        $this->dispatcher->dispatchTyped(new ShareCreatedEvent($share));
748
+
749
+        // Send email if needed
750
+        if ($this->config->getSystemValueBool('sharing.enable_share_mail', true)) {
751
+            if ($share->getMailSend()) {
752
+                $provider = $this->factory->getProviderForType($share->getShareType());
753
+                if ($provider instanceof IShareProviderWithNotification) {
754
+                    $provider->sendMailNotification($share);
755
+                } else {
756
+                    $this->logger->debug('Share notification not sent because the provider does not support it.', ['app' => 'share']);
757
+                }
758
+            } else {
759
+                $this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']);
760
+            }
761
+        } else {
762
+            $this->logger->debug('Share notification not sent because sharing notification emails is disabled.', ['app' => 'share']);
763
+        }
764
+
765
+        return $share;
766
+    }
767
+
768
+    /**
769
+     * Update a share
770
+     *
771
+     * @param IShare $share
772
+     * @return IShare The share object
773
+     * @throws \InvalidArgumentException
774
+     * @throws HintException
775
+     */
776
+    public function updateShare(IShare $share, bool $onlyValid = true) {
777
+        $expirationDateUpdated = false;
778
+
779
+        $this->canShare($share);
780
+
781
+        try {
782
+            $originalShare = $this->getShareById($share->getFullId(), onlyValid: $onlyValid);
783
+        } catch (\UnexpectedValueException $e) {
784
+            throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
785
+        }
786
+
787
+        // We cannot change the share type!
788
+        if ($share->getShareType() !== $originalShare->getShareType()) {
789
+            throw new \InvalidArgumentException($this->l->t('Cannot change share type'));
790
+        }
791
+
792
+        // We can only change the recipient on user shares
793
+        if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
794
+            $share->getShareType() !== IShare::TYPE_USER) {
795
+            throw new \InvalidArgumentException($this->l->t('Can only update recipient on user shares'));
796
+        }
797
+
798
+        // Cannot share with the owner
799
+        if ($share->getShareType() === IShare::TYPE_USER &&
800
+            $share->getSharedWith() === $share->getShareOwner()) {
801
+            throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
802
+        }
803
+
804
+        $this->generalCreateChecks($share, true);
805
+
806
+        if ($share->getShareType() === IShare::TYPE_USER) {
807
+            $this->userCreateChecks($share);
808
+
809
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
810
+                // Verify the expiration date
811
+                $this->validateExpirationDateInternal($share);
812
+                $expirationDateUpdated = true;
813
+            }
814
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
815
+            $this->groupCreateChecks($share);
816
+
817
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
818
+                // Verify the expiration date
819
+                $this->validateExpirationDateInternal($share);
820
+                $expirationDateUpdated = true;
821
+            }
822
+        } elseif ($share->getShareType() === IShare::TYPE_LINK
823
+            || $share->getShareType() === IShare::TYPE_EMAIL) {
824
+            $this->linkCreateChecks($share);
825
+
826
+            // The new password is not set again if it is the same as the old
827
+            // one, unless when switching from sending by Talk to sending by
828
+            // mail.
829
+            $plainTextPassword = $share->getPassword();
830
+            $updatedPassword = $this->updateSharePasswordIfNeeded($share, $originalShare);
831
+
832
+            /**
833
+             * Cannot enable the getSendPasswordByTalk if there is no password set
834
+             */
835
+            if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) {
836
+                throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk with an empty password'));
837
+            }
838
+
839
+            /**
840
+             * If we're in a mail share, we need to force a password change
841
+             * as either the user is not aware of the password or is already (received by mail)
842
+             * Thus the SendPasswordByTalk feature would not make sense
843
+             */
844
+            if (!$updatedPassword && $share->getShareType() === IShare::TYPE_EMAIL) {
845
+                if (!$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) {
846
+                    throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk without setting a new password'));
847
+                }
848
+                if ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) {
849
+                    throw new \InvalidArgumentException($this->l->t('Cannot disable sending the password by Talk without setting a new password'));
850
+                }
851
+            }
852
+
853
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
854
+                // Verify the expiration date
855
+                $this->validateExpirationDateLink($share);
856
+                $expirationDateUpdated = true;
857
+            }
858
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
859
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
860
+                // Verify the expiration date
861
+                $this->validateExpirationDateInternal($share);
862
+                $expirationDateUpdated = true;
863
+            }
864
+        }
865
+
866
+        $this->pathCreateChecks($share->getNode());
867
+
868
+        // Now update the share!
869
+        $provider = $this->factory->getProviderForType($share->getShareType());
870
+        if ($share->getShareType() === IShare::TYPE_EMAIL) {
871
+            $share = $provider->update($share, $plainTextPassword);
872
+        } else {
873
+            $share = $provider->update($share);
874
+        }
875
+
876
+        if ($expirationDateUpdated === true) {
877
+            \OC_Hook::emit(Share::class, 'post_set_expiration_date', [
878
+                'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
879
+                'itemSource' => $share->getNode()->getId(),
880
+                'date' => $share->getExpirationDate(),
881
+                'uidOwner' => $share->getSharedBy(),
882
+            ]);
883
+        }
884
+
885
+        if ($share->getPassword() !== $originalShare->getPassword()) {
886
+            \OC_Hook::emit(Share::class, 'post_update_password', [
887
+                'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
888
+                'itemSource' => $share->getNode()->getId(),
889
+                'uidOwner' => $share->getSharedBy(),
890
+                'token' => $share->getToken(),
891
+                'disabled' => is_null($share->getPassword()),
892
+            ]);
893
+        }
894
+
895
+        if ($share->getPermissions() !== $originalShare->getPermissions()) {
896
+            if ($this->userManager->userExists($share->getShareOwner())) {
897
+                $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
898
+            } else {
899
+                $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
900
+            }
901
+            \OC_Hook::emit(Share::class, 'post_update_permissions', [
902
+                'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
903
+                'itemSource' => $share->getNode()->getId(),
904
+                'shareType' => $share->getShareType(),
905
+                'shareWith' => $share->getSharedWith(),
906
+                'uidOwner' => $share->getSharedBy(),
907
+                'permissions' => $share->getPermissions(),
908
+                'attributes' => $share->getAttributes() !== null ? $share->getAttributes()->toArray() : null,
909
+                'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
910
+            ]);
911
+        }
912
+
913
+        return $share;
914
+    }
915
+
916
+    /**
917
+     * Accept a share.
918
+     *
919
+     * @param IShare $share
920
+     * @param string $recipientId
921
+     * @return IShare The share object
922
+     * @throws \InvalidArgumentException Thrown if the provider does not implement `IShareProviderSupportsAccept`
923
+     * @since 9.0.0
924
+     */
925
+    public function acceptShare(IShare $share, string $recipientId): IShare {
926
+        [$providerId,] = $this->splitFullId($share->getFullId());
927
+        $provider = $this->factory->getProvider($providerId);
928
+
929
+        if (!($provider instanceof IShareProviderSupportsAccept)) {
930
+            throw new \InvalidArgumentException($this->l->t('Share provider does not support accepting'));
931
+        }
932
+        /** @var IShareProvider&IShareProviderSupportsAccept $provider */
933
+        $provider->acceptShare($share, $recipientId);
934
+
935
+        $event = new ShareAcceptedEvent($share);
936
+        $this->dispatcher->dispatchTyped($event);
937
+
938
+        return $share;
939
+    }
940
+
941
+    /**
942
+     * Updates the password of the given share if it is not the same as the
943
+     * password of the original share.
944
+     *
945
+     * @param IShare $share the share to update its password.
946
+     * @param IShare $originalShare the original share to compare its
947
+     *                              password with.
948
+     * @return boolean whether the password was updated or not.
949
+     */
950
+    private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare) {
951
+        $passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword()) &&
952
+            (($share->getPassword() !== null && $originalShare->getPassword() === null) ||
953
+                ($share->getPassword() === null && $originalShare->getPassword() !== null) ||
954
+                ($share->getPassword() !== null && $originalShare->getPassword() !== null &&
955
+                    !$this->hasher->verify($share->getPassword(), $originalShare->getPassword())));
956
+
957
+        // Password updated.
958
+        if ($passwordsAreDifferent) {
959
+            // Verify the password
960
+            $this->verifyPassword($share->getPassword());
961
+
962
+            // If a password is set. Hash it!
963
+            if (!empty($share->getPassword())) {
964
+                $share->setPassword($this->hasher->hash($share->getPassword()));
965
+                if ($share->getShareType() === IShare::TYPE_EMAIL) {
966
+                    // Shares shared by email have temporary passwords
967
+                    $this->setSharePasswordExpirationTime($share);
968
+                }
969
+
970
+                return true;
971
+            } else {
972
+                // Empty string and null are seen as NOT password protected
973
+                $share->setPassword(null);
974
+                if ($share->getShareType() === IShare::TYPE_EMAIL) {
975
+                    $share->setPasswordExpirationTime(null);
976
+                }
977
+                return true;
978
+            }
979
+        } else {
980
+            // Reset the password to the original one, as it is either the same
981
+            // as the "new" password or a hashed version of it.
982
+            $share->setPassword($originalShare->getPassword());
983
+        }
984
+
985
+        return false;
986
+    }
987
+
988
+    /**
989
+     * Set the share's password expiration time
990
+     */
991
+    private function setSharePasswordExpirationTime(IShare $share): void {
992
+        if (!$this->config->getSystemValueBool('sharing.enable_mail_link_password_expiration', false)) {
993
+            // Sets password expiration date to NULL
994
+            $share->setPasswordExpirationTime();
995
+            return;
996
+        }
997
+        // Sets password expiration date
998
+        $expirationTime = null;
999
+        $now = new \DateTime();
1000
+        $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
1001
+        $expirationTime = $now->add(new \DateInterval('PT' . $expirationInterval . 'S'));
1002
+        $share->setPasswordExpirationTime($expirationTime);
1003
+    }
1004
+
1005
+
1006
+    /**
1007
+     * Delete all the children of this share
1008
+     * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
1009
+     *
1010
+     * @param IShare $share
1011
+     * @return IShare[] List of deleted shares
1012
+     */
1013
+    protected function deleteChildren(IShare $share) {
1014
+        $deletedShares = [];
1015
+
1016
+        $provider = $this->factory->getProviderForType($share->getShareType());
1017
+
1018
+        foreach ($provider->getChildren($share) as $child) {
1019
+            $this->dispatcher->dispatchTyped(new BeforeShareDeletedEvent($child));
1020
+
1021
+            $deletedChildren = $this->deleteChildren($child);
1022
+            $deletedShares = array_merge($deletedShares, $deletedChildren);
1023
+
1024
+            $provider->delete($child);
1025
+            $this->dispatcher->dispatchTyped(new ShareDeletedEvent($child));
1026
+            $deletedShares[] = $child;
1027
+        }
1028
+
1029
+        return $deletedShares;
1030
+    }
1031
+
1032
+    /** Promote re-shares into direct shares so that target user keeps access */
1033
+    protected function promoteReshares(IShare $share): void {
1034
+        try {
1035
+            $node = $share->getNode();
1036
+        } catch (NotFoundException) {
1037
+            /* Skip if node not found */
1038
+            return;
1039
+        }
1040
+
1041
+        $userIds = [];
1042
+
1043
+        if ($share->getShareType() === IShare::TYPE_USER) {
1044
+            $userIds[] = $share->getSharedWith();
1045
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1046
+            $group = $this->groupManager->get($share->getSharedWith());
1047
+            $users = $group?->getUsers() ?? [];
1048
+
1049
+            foreach ($users as $user) {
1050
+                /* Skip share owner */
1051
+                if ($user->getUID() === $share->getShareOwner() || $user->getUID() === $share->getSharedBy()) {
1052
+                    continue;
1053
+                }
1054
+                $userIds[] = $user->getUID();
1055
+            }
1056
+        } else {
1057
+            /* We only support user and group shares */
1058
+            return;
1059
+        }
1060
+
1061
+        $reshareRecords = [];
1062
+        $shareTypes = [
1063
+            IShare::TYPE_GROUP,
1064
+            IShare::TYPE_USER,
1065
+            IShare::TYPE_LINK,
1066
+            IShare::TYPE_REMOTE,
1067
+            IShare::TYPE_EMAIL,
1068
+        ];
1069
+
1070
+        foreach ($userIds as $userId) {
1071
+            foreach ($shareTypes as $shareType) {
1072
+                try {
1073
+                    $provider = $this->factory->getProviderForType($shareType);
1074
+                } catch (ProviderException $e) {
1075
+                    continue;
1076
+                }
1077
+
1078
+                if ($node instanceof Folder) {
1079
+                    /* We need to get all shares by this user to get subshares */
1080
+                    $shares = $provider->getSharesBy($userId, $shareType, null, false, -1, 0);
1081
+
1082
+                    foreach ($shares as $share) {
1083
+                        try {
1084
+                            $path = $share->getNode()->getPath();
1085
+                        } catch (NotFoundException) {
1086
+                            /* Ignore share of non-existing node */
1087
+                            continue;
1088
+                        }
1089
+                        if ($node->getRelativePath($path) !== null) {
1090
+                            /* If relative path is not null it means the shared node is the same or in a subfolder */
1091
+                            $reshareRecords[] = $share;
1092
+                        }
1093
+                    }
1094
+                } else {
1095
+                    $shares = $provider->getSharesBy($userId, $shareType, $node, false, -1, 0);
1096
+                    foreach ($shares as $child) {
1097
+                        $reshareRecords[] = $child;
1098
+                    }
1099
+                }
1100
+            }
1101
+        }
1102
+
1103
+        foreach ($reshareRecords as $child) {
1104
+            try {
1105
+                /* Check if the share is still valid (means the resharer still has access to the file through another mean) */
1106
+                $this->generalCreateChecks($child);
1107
+            } catch (GenericShareException $e) {
1108
+                /* The check is invalid, promote it to a direct share from the sharer of parent share */
1109
+                $this->logger->debug('Promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1110
+                try {
1111
+                    $child->setSharedBy($share->getSharedBy());
1112
+                    $this->updateShare($child);
1113
+                } catch (GenericShareException|\InvalidArgumentException $e) {
1114
+                    $this->logger->warning('Failed to promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1115
+                }
1116
+            }
1117
+        }
1118
+    }
1119
+
1120
+    /**
1121
+     * Delete a share
1122
+     *
1123
+     * @param IShare $share
1124
+     * @throws ShareNotFound
1125
+     * @throws \InvalidArgumentException
1126
+     */
1127
+    public function deleteShare(IShare $share) {
1128
+        try {
1129
+            $share->getFullId();
1130
+        } catch (\UnexpectedValueException $e) {
1131
+            throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
1132
+        }
1133
+
1134
+        $this->dispatcher->dispatchTyped(new BeforeShareDeletedEvent($share));
1135
+
1136
+        // Get all children and delete them as well
1137
+        $this->deleteChildren($share);
1138
+
1139
+        // Do the actual delete
1140
+        $provider = $this->factory->getProviderForType($share->getShareType());
1141
+        $provider->delete($share);
1142
+
1143
+        $this->dispatcher->dispatchTyped(new ShareDeletedEvent($share));
1144
+
1145
+        // Promote reshares of the deleted share
1146
+        $this->promoteReshares($share);
1147
+    }
1148
+
1149
+
1150
+    /**
1151
+     * Unshare a file as the recipient.
1152
+     * This can be different from a regular delete for example when one of
1153
+     * the users in a groups deletes that share. But the provider should
1154
+     * handle this.
1155
+     *
1156
+     * @param IShare $share
1157
+     * @param string $recipientId
1158
+     */
1159
+    public function deleteFromSelf(IShare $share, $recipientId) {
1160
+        [$providerId,] = $this->splitFullId($share->getFullId());
1161
+        $provider = $this->factory->getProvider($providerId);
1162
+
1163
+        $provider->deleteFromSelf($share, $recipientId);
1164
+        $event = new ShareDeletedFromSelfEvent($share);
1165
+        $this->dispatcher->dispatchTyped($event);
1166
+    }
1167
+
1168
+    public function restoreShare(IShare $share, string $recipientId): IShare {
1169
+        [$providerId,] = $this->splitFullId($share->getFullId());
1170
+        $provider = $this->factory->getProvider($providerId);
1171
+
1172
+        return $provider->restore($share, $recipientId);
1173
+    }
1174
+
1175
+    /**
1176
+     * @inheritdoc
1177
+     */
1178
+    public function moveShare(IShare $share, $recipientId) {
1179
+        if ($share->getShareType() === IShare::TYPE_LINK
1180
+            || $share->getShareType() === IShare::TYPE_EMAIL) {
1181
+            throw new \InvalidArgumentException($this->l->t('Cannot change target of link share'));
1182
+        }
1183
+
1184
+        if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) {
1185
+            throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1186
+        }
1187
+
1188
+        if ($share->getShareType() === IShare::TYPE_GROUP) {
1189
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
1190
+            if (is_null($sharedWith)) {
1191
+                throw new \InvalidArgumentException($this->l->t('Group "%s" does not exist', [$share->getSharedWith()]));
1192
+            }
1193
+            $recipient = $this->userManager->get($recipientId);
1194
+            if (!$sharedWith->inGroup($recipient)) {
1195
+                throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1196
+            }
1197
+        }
1198
+
1199
+        [$providerId,] = $this->splitFullId($share->getFullId());
1200
+        $provider = $this->factory->getProvider($providerId);
1201
+
1202
+        return $provider->move($share, $recipientId);
1203
+    }
1204
+
1205
+    public function getSharesInFolder($userId, Folder $node, $reshares = false, $shallow = true) {
1206
+        $providers = $this->factory->getAllProviders();
1207
+        if (!$shallow) {
1208
+            throw new \Exception('non-shallow getSharesInFolder is no longer supported');
1209
+        }
1210
+
1211
+        $isOwnerless = $node->getMountPoint() instanceof IShareOwnerlessMount;
1212
+
1213
+        $shares = [];
1214
+        foreach ($providers as $provider) {
1215
+            if ($isOwnerless) {
1216
+                foreach ($node->getDirectoryListing() as $childNode) {
1217
+                    $data = $provider->getSharesByPath($childNode);
1218
+                    $fid = $childNode->getId();
1219
+                    $shares[$fid] ??= [];
1220
+                    $shares[$fid] = array_merge($shares[$fid], $data);
1221
+                }
1222
+            } else {
1223
+                foreach ($provider->getSharesInFolder($userId, $node, $reshares) as $fid => $data) {
1224
+                    $shares[$fid] ??= [];
1225
+                    $shares[$fid] = array_merge($shares[$fid], $data);
1226
+                }
1227
+            }
1228
+        }
1229
+
1230
+        return $shares;
1231
+    }
1232
+
1233
+    /**
1234
+     * @inheritdoc
1235
+     */
1236
+    public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, bool $onlyValid = true) {
1237
+        if ($path !== null &&
1238
+            !($path instanceof \OCP\Files\File) &&
1239
+            !($path instanceof \OCP\Files\Folder)) {
1240
+            throw new \InvalidArgumentException($this->l->t('Invalid path'));
1241
+        }
1242
+
1243
+        try {
1244
+            $provider = $this->factory->getProviderForType($shareType);
1245
+        } catch (ProviderException $e) {
1246
+            return [];
1247
+        }
1248
+
1249
+        if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1250
+            $shares = array_filter($provider->getSharesByPath($path), static fn (IShare $share) => $share->getShareType() === $shareType);
1251
+        } else {
1252
+            $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1253
+        }
1254
+
1255
+        /*
1256 1256
 		 * Work around so we don't return expired shares but still follow
1257 1257
 		 * proper pagination.
1258 1258
 		 */
1259 1259
 
1260
-		$shares2 = [];
1261
-
1262
-		while (true) {
1263
-			$added = 0;
1264
-			foreach ($shares as $share) {
1265
-				if ($onlyValid) {
1266
-					try {
1267
-						$this->checkShare($share);
1268
-					} catch (ShareNotFound $e) {
1269
-						// Ignore since this basically means the share is deleted
1270
-						continue;
1271
-					}
1272
-				}
1273
-
1274
-				$added++;
1275
-				$shares2[] = $share;
1276
-
1277
-				if (count($shares2) === $limit) {
1278
-					break;
1279
-				}
1280
-			}
1281
-
1282
-			// If we did not fetch more shares than the limit then there are no more shares
1283
-			if (count($shares) < $limit) {
1284
-				break;
1285
-			}
1286
-
1287
-			if (count($shares2) === $limit) {
1288
-				break;
1289
-			}
1290
-
1291
-			// If there was no limit on the select we are done
1292
-			if ($limit === -1) {
1293
-				break;
1294
-			}
1295
-
1296
-			$offset += $added;
1297
-
1298
-			// Fetch again $limit shares
1299
-			if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1300
-				// We already fetched all shares, so end here
1301
-				$shares = [];
1302
-			} else {
1303
-				$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1304
-			}
1305
-
1306
-			// No more shares means we are done
1307
-			if (empty($shares)) {
1308
-				break;
1309
-			}
1310
-		}
1311
-
1312
-		$shares = $shares2;
1313
-
1314
-		return $shares;
1315
-	}
1316
-
1317
-	/**
1318
-	 * @inheritdoc
1319
-	 */
1320
-	public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1321
-		try {
1322
-			$provider = $this->factory->getProviderForType($shareType);
1323
-		} catch (ProviderException $e) {
1324
-			return [];
1325
-		}
1326
-
1327
-		$shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1328
-
1329
-		// remove all shares which are already expired
1330
-		foreach ($shares as $key => $share) {
1331
-			try {
1332
-				$this->checkShare($share);
1333
-			} catch (ShareNotFound $e) {
1334
-				unset($shares[$key]);
1335
-			}
1336
-		}
1337
-
1338
-		return $shares;
1339
-	}
1340
-
1341
-	/**
1342
-	 * @inheritdoc
1343
-	 */
1344
-	public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1345
-		$shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1346
-
1347
-		// Only get deleted shares
1348
-		$shares = array_filter($shares, function (IShare $share) {
1349
-			return $share->getPermissions() === 0;
1350
-		});
1351
-
1352
-		// Only get shares where the owner still exists
1353
-		$shares = array_filter($shares, function (IShare $share) {
1354
-			return $this->userManager->userExists($share->getShareOwner());
1355
-		});
1356
-
1357
-		return $shares;
1358
-	}
1359
-
1360
-	/**
1361
-	 * @inheritdoc
1362
-	 */
1363
-	public function getShareById($id, $recipient = null, bool $onlyValid = true) {
1364
-		if ($id === null) {
1365
-			throw new ShareNotFound();
1366
-		}
1367
-
1368
-		[$providerId, $id] = $this->splitFullId($id);
1369
-
1370
-		try {
1371
-			$provider = $this->factory->getProvider($providerId);
1372
-		} catch (ProviderException $e) {
1373
-			throw new ShareNotFound();
1374
-		}
1375
-
1376
-		$share = $provider->getShareById($id, $recipient);
1377
-
1378
-		if ($onlyValid) {
1379
-			$this->checkShare($share);
1380
-		}
1381
-
1382
-		return $share;
1383
-	}
1384
-
1385
-	/**
1386
-	 * Get all the shares for a given path
1387
-	 *
1388
-	 * @param \OCP\Files\Node $path
1389
-	 * @param int $page
1390
-	 * @param int $perPage
1391
-	 *
1392
-	 * @return Share[]
1393
-	 */
1394
-	public function getSharesByPath(\OCP\Files\Node $path, $page = 0, $perPage = 50) {
1395
-		return [];
1396
-	}
1397
-
1398
-	/**
1399
-	 * Get the share by token possible with password
1400
-	 *
1401
-	 * @param string $token
1402
-	 * @return IShare
1403
-	 *
1404
-	 * @throws ShareNotFound
1405
-	 */
1406
-	public function getShareByToken($token) {
1407
-		// tokens cannot be valid local user names
1408
-		if ($this->userManager->userExists($token)) {
1409
-			throw new ShareNotFound();
1410
-		}
1411
-		$share = null;
1412
-		try {
1413
-			if ($this->shareApiAllowLinks()) {
1414
-				$provider = $this->factory->getProviderForType(IShare::TYPE_LINK);
1415
-				$share = $provider->getShareByToken($token);
1416
-			}
1417
-		} catch (ProviderException $e) {
1418
-		} catch (ShareNotFound $e) {
1419
-		}
1420
-
1421
-
1422
-		// If it is not a link share try to fetch a federated share by token
1423
-		if ($share === null) {
1424
-			try {
1425
-				$provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE);
1426
-				$share = $provider->getShareByToken($token);
1427
-			} catch (ProviderException $e) {
1428
-			} catch (ShareNotFound $e) {
1429
-			}
1430
-		}
1431
-
1432
-		// If it is not a link share try to fetch a mail share by token
1433
-		if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
1434
-			try {
1435
-				$provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL);
1436
-				$share = $provider->getShareByToken($token);
1437
-			} catch (ProviderException $e) {
1438
-			} catch (ShareNotFound $e) {
1439
-			}
1440
-		}
1441
-
1442
-		if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
1443
-			try {
1444
-				$provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE);
1445
-				$share = $provider->getShareByToken($token);
1446
-			} catch (ProviderException $e) {
1447
-			} catch (ShareNotFound $e) {
1448
-			}
1449
-		}
1450
-
1451
-		if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) {
1452
-			try {
1453
-				$provider = $this->factory->getProviderForType(IShare::TYPE_ROOM);
1454
-				$share = $provider->getShareByToken($token);
1455
-			} catch (ProviderException $e) {
1456
-			} catch (ShareNotFound $e) {
1457
-			}
1458
-		}
1459
-
1460
-		if ($share === null) {
1461
-			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1462
-		}
1463
-
1464
-		$this->checkShare($share);
1465
-
1466
-		/*
1260
+        $shares2 = [];
1261
+
1262
+        while (true) {
1263
+            $added = 0;
1264
+            foreach ($shares as $share) {
1265
+                if ($onlyValid) {
1266
+                    try {
1267
+                        $this->checkShare($share);
1268
+                    } catch (ShareNotFound $e) {
1269
+                        // Ignore since this basically means the share is deleted
1270
+                        continue;
1271
+                    }
1272
+                }
1273
+
1274
+                $added++;
1275
+                $shares2[] = $share;
1276
+
1277
+                if (count($shares2) === $limit) {
1278
+                    break;
1279
+                }
1280
+            }
1281
+
1282
+            // If we did not fetch more shares than the limit then there are no more shares
1283
+            if (count($shares) < $limit) {
1284
+                break;
1285
+            }
1286
+
1287
+            if (count($shares2) === $limit) {
1288
+                break;
1289
+            }
1290
+
1291
+            // If there was no limit on the select we are done
1292
+            if ($limit === -1) {
1293
+                break;
1294
+            }
1295
+
1296
+            $offset += $added;
1297
+
1298
+            // Fetch again $limit shares
1299
+            if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1300
+                // We already fetched all shares, so end here
1301
+                $shares = [];
1302
+            } else {
1303
+                $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1304
+            }
1305
+
1306
+            // No more shares means we are done
1307
+            if (empty($shares)) {
1308
+                break;
1309
+            }
1310
+        }
1311
+
1312
+        $shares = $shares2;
1313
+
1314
+        return $shares;
1315
+    }
1316
+
1317
+    /**
1318
+     * @inheritdoc
1319
+     */
1320
+    public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1321
+        try {
1322
+            $provider = $this->factory->getProviderForType($shareType);
1323
+        } catch (ProviderException $e) {
1324
+            return [];
1325
+        }
1326
+
1327
+        $shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1328
+
1329
+        // remove all shares which are already expired
1330
+        foreach ($shares as $key => $share) {
1331
+            try {
1332
+                $this->checkShare($share);
1333
+            } catch (ShareNotFound $e) {
1334
+                unset($shares[$key]);
1335
+            }
1336
+        }
1337
+
1338
+        return $shares;
1339
+    }
1340
+
1341
+    /**
1342
+     * @inheritdoc
1343
+     */
1344
+    public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
1345
+        $shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1346
+
1347
+        // Only get deleted shares
1348
+        $shares = array_filter($shares, function (IShare $share) {
1349
+            return $share->getPermissions() === 0;
1350
+        });
1351
+
1352
+        // Only get shares where the owner still exists
1353
+        $shares = array_filter($shares, function (IShare $share) {
1354
+            return $this->userManager->userExists($share->getShareOwner());
1355
+        });
1356
+
1357
+        return $shares;
1358
+    }
1359
+
1360
+    /**
1361
+     * @inheritdoc
1362
+     */
1363
+    public function getShareById($id, $recipient = null, bool $onlyValid = true) {
1364
+        if ($id === null) {
1365
+            throw new ShareNotFound();
1366
+        }
1367
+
1368
+        [$providerId, $id] = $this->splitFullId($id);
1369
+
1370
+        try {
1371
+            $provider = $this->factory->getProvider($providerId);
1372
+        } catch (ProviderException $e) {
1373
+            throw new ShareNotFound();
1374
+        }
1375
+
1376
+        $share = $provider->getShareById($id, $recipient);
1377
+
1378
+        if ($onlyValid) {
1379
+            $this->checkShare($share);
1380
+        }
1381
+
1382
+        return $share;
1383
+    }
1384
+
1385
+    /**
1386
+     * Get all the shares for a given path
1387
+     *
1388
+     * @param \OCP\Files\Node $path
1389
+     * @param int $page
1390
+     * @param int $perPage
1391
+     *
1392
+     * @return Share[]
1393
+     */
1394
+    public function getSharesByPath(\OCP\Files\Node $path, $page = 0, $perPage = 50) {
1395
+        return [];
1396
+    }
1397
+
1398
+    /**
1399
+     * Get the share by token possible with password
1400
+     *
1401
+     * @param string $token
1402
+     * @return IShare
1403
+     *
1404
+     * @throws ShareNotFound
1405
+     */
1406
+    public function getShareByToken($token) {
1407
+        // tokens cannot be valid local user names
1408
+        if ($this->userManager->userExists($token)) {
1409
+            throw new ShareNotFound();
1410
+        }
1411
+        $share = null;
1412
+        try {
1413
+            if ($this->shareApiAllowLinks()) {
1414
+                $provider = $this->factory->getProviderForType(IShare::TYPE_LINK);
1415
+                $share = $provider->getShareByToken($token);
1416
+            }
1417
+        } catch (ProviderException $e) {
1418
+        } catch (ShareNotFound $e) {
1419
+        }
1420
+
1421
+
1422
+        // If it is not a link share try to fetch a federated share by token
1423
+        if ($share === null) {
1424
+            try {
1425
+                $provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE);
1426
+                $share = $provider->getShareByToken($token);
1427
+            } catch (ProviderException $e) {
1428
+            } catch (ShareNotFound $e) {
1429
+            }
1430
+        }
1431
+
1432
+        // If it is not a link share try to fetch a mail share by token
1433
+        if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
1434
+            try {
1435
+                $provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL);
1436
+                $share = $provider->getShareByToken($token);
1437
+            } catch (ProviderException $e) {
1438
+            } catch (ShareNotFound $e) {
1439
+            }
1440
+        }
1441
+
1442
+        if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
1443
+            try {
1444
+                $provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE);
1445
+                $share = $provider->getShareByToken($token);
1446
+            } catch (ProviderException $e) {
1447
+            } catch (ShareNotFound $e) {
1448
+            }
1449
+        }
1450
+
1451
+        if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) {
1452
+            try {
1453
+                $provider = $this->factory->getProviderForType(IShare::TYPE_ROOM);
1454
+                $share = $provider->getShareByToken($token);
1455
+            } catch (ProviderException $e) {
1456
+            } catch (ShareNotFound $e) {
1457
+            }
1458
+        }
1459
+
1460
+        if ($share === null) {
1461
+            throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1462
+        }
1463
+
1464
+        $this->checkShare($share);
1465
+
1466
+        /*
1467 1467
 		 * Reduce the permissions for link or email shares if public upload is not enabled
1468 1468
 		 */
1469
-		if (($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL)
1470
-			&& $share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()) {
1471
-			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1472
-		}
1473
-
1474
-		return $share;
1475
-	}
1476
-
1477
-	/**
1478
-	 * Check expire date and disabled owner
1479
-	 *
1480
-	 * @throws ShareNotFound
1481
-	 */
1482
-	protected function checkShare(IShare $share): void {
1483
-		if ($share->isExpired()) {
1484
-			$this->deleteShare($share);
1485
-			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1486
-		}
1487
-		if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
1488
-			$uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
1489
-			foreach ($uids as $uid) {
1490
-				$user = $this->userManager->get($uid);
1491
-				if ($user?->isEnabled() === false) {
1492
-					throw new ShareNotFound($this->l->t('The requested share comes from a disabled user'));
1493
-				}
1494
-			}
1495
-		}
1496
-	}
1497
-
1498
-	/**
1499
-	 * Verify the password of a public share
1500
-	 *
1501
-	 * @param IShare $share
1502
-	 * @param ?string $password
1503
-	 * @return bool
1504
-	 */
1505
-	public function checkPassword(IShare $share, $password) {
1506
-
1507
-		// if there is no password on the share object / passsword is null, there is nothing to check
1508
-		if ($password === null || $share->getPassword() === null) {
1509
-			return false;
1510
-		}
1511
-
1512
-		// Makes sure password hasn't expired
1513
-		$expirationTime = $share->getPasswordExpirationTime();
1514
-		if ($expirationTime !== null && $expirationTime < new \DateTime()) {
1515
-			return false;
1516
-		}
1517
-
1518
-		$newHash = '';
1519
-		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1520
-			return false;
1521
-		}
1522
-
1523
-		if (!empty($newHash)) {
1524
-			$share->setPassword($newHash);
1525
-			$provider = $this->factory->getProviderForType($share->getShareType());
1526
-			$provider->update($share);
1527
-		}
1528
-
1529
-		return true;
1530
-	}
1531
-
1532
-	/**
1533
-	 * @inheritdoc
1534
-	 */
1535
-	public function userDeleted($uid) {
1536
-		$types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
1537
-
1538
-		foreach ($types as $type) {
1539
-			try {
1540
-				$provider = $this->factory->getProviderForType($type);
1541
-			} catch (ProviderException $e) {
1542
-				continue;
1543
-			}
1544
-			$provider->userDeleted($uid, $type);
1545
-		}
1546
-	}
1547
-
1548
-	/**
1549
-	 * @inheritdoc
1550
-	 */
1551
-	public function groupDeleted($gid) {
1552
-		foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1553
-			try {
1554
-				$provider = $this->factory->getProviderForType($type);
1555
-			} catch (ProviderException $e) {
1556
-				continue;
1557
-			}
1558
-			$provider->groupDeleted($gid);
1559
-		}
1560
-
1561
-		$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1562
-		if ($excludedGroups === '') {
1563
-			return;
1564
-		}
1565
-
1566
-		$excludedGroups = json_decode($excludedGroups, true);
1567
-		if (json_last_error() !== JSON_ERROR_NONE) {
1568
-			return;
1569
-		}
1570
-
1571
-		$excludedGroups = array_diff($excludedGroups, [$gid]);
1572
-		$this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
1573
-	}
1574
-
1575
-	/**
1576
-	 * @inheritdoc
1577
-	 */
1578
-	public function userDeletedFromGroup($uid, $gid) {
1579
-		foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1580
-			try {
1581
-				$provider = $this->factory->getProviderForType($type);
1582
-			} catch (ProviderException $e) {
1583
-				continue;
1584
-			}
1585
-			$provider->userDeletedFromGroup($uid, $gid);
1586
-		}
1587
-	}
1588
-
1589
-	/**
1590
-	 * Get access list to a path. This means
1591
-	 * all the users that can access a given path.
1592
-	 *
1593
-	 * Consider:
1594
-	 * -root
1595
-	 * |-folder1 (23)
1596
-	 *  |-folder2 (32)
1597
-	 *   |-fileA (42)
1598
-	 *
1599
-	 * fileA is shared with user1 and user1@server1 and email1@maildomain1
1600
-	 * folder2 is shared with group2 (user4 is a member of group2)
1601
-	 * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
1602
-	 *                        and email2@maildomain2
1603
-	 *
1604
-	 * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
1605
-	 * [
1606
-	 *  users  => [
1607
-	 *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
1608
-	 *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
1609
-	 *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
1610
-	 *  ],
1611
-	 *  remote => [
1612
-	 *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
1613
-	 *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
1614
-	 *  ],
1615
-	 *  public => bool
1616
-	 *  mail => [
1617
-	 *      'email1@maildomain1' => ['node_id' => 42, 'token' => 'aBcDeFg'],
1618
-	 *      'email2@maildomain2' => ['node_id' => 23, 'token' => 'hIjKlMn'],
1619
-	 *  ]
1620
-	 * ]
1621
-	 *
1622
-	 * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
1623
-	 * [
1624
-	 *  users  => ['user1', 'user2', 'user4'],
1625
-	 *  remote => bool,
1626
-	 *  public => bool
1627
-	 *  mail => ['email1@maildomain1', 'email2@maildomain2']
1628
-	 * ]
1629
-	 *
1630
-	 * This is required for encryption/activity
1631
-	 *
1632
-	 * @param \OCP\Files\Node $path
1633
-	 * @param bool $recursive Should we check all parent folders as well
1634
-	 * @param bool $currentAccess Ensure the recipient has access to the file (e.g. did not unshare it)
1635
-	 * @return array
1636
-	 */
1637
-	public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1638
-		$owner = $path->getOwner();
1639
-
1640
-		if ($owner === null) {
1641
-			return [];
1642
-		}
1643
-
1644
-		$owner = $owner->getUID();
1645
-
1646
-		if ($currentAccess) {
1647
-			$al = ['users' => [], 'remote' => [], 'public' => false, 'mail' => []];
1648
-		} else {
1649
-			$al = ['users' => [], 'remote' => false, 'public' => false, 'mail' => []];
1650
-		}
1651
-		if (!$this->userManager->userExists($owner)) {
1652
-			return $al;
1653
-		}
1654
-
1655
-		//Get node for the owner and correct the owner in case of external storage
1656
-		$userFolder = $this->rootFolder->getUserFolder($owner);
1657
-		if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1658
-			$path = $userFolder->getFirstNodeById($path->getId());
1659
-			if ($path === null || $path->getOwner() === null) {
1660
-				return [];
1661
-			}
1662
-			$owner = $path->getOwner()->getUID();
1663
-		}
1664
-
1665
-		$providers = $this->factory->getAllProviders();
1666
-
1667
-		/** @var Node[] $nodes */
1668
-		$nodes = [];
1669
-
1670
-
1671
-		if ($currentAccess) {
1672
-			$ownerPath = $path->getPath();
1673
-			$ownerPath = explode('/', $ownerPath, 4);
1674
-			if (count($ownerPath) < 4) {
1675
-				$ownerPath = '';
1676
-			} else {
1677
-				$ownerPath = $ownerPath[3];
1678
-			}
1679
-			$al['users'][$owner] = [
1680
-				'node_id' => $path->getId(),
1681
-				'node_path' => '/' . $ownerPath,
1682
-			];
1683
-		} else {
1684
-			$al['users'][] = $owner;
1685
-		}
1686
-
1687
-		// Collect all the shares
1688
-		while ($path->getPath() !== $userFolder->getPath()) {
1689
-			$nodes[] = $path;
1690
-			if (!$recursive) {
1691
-				break;
1692
-			}
1693
-			$path = $path->getParent();
1694
-		}
1695
-
1696
-		foreach ($providers as $provider) {
1697
-			$tmp = $provider->getAccessList($nodes, $currentAccess);
1698
-
1699
-			foreach ($tmp as $k => $v) {
1700
-				if (isset($al[$k])) {
1701
-					if (is_array($al[$k])) {
1702
-						if ($currentAccess) {
1703
-							$al[$k] += $v;
1704
-						} else {
1705
-							$al[$k] = array_merge($al[$k], $v);
1706
-							$al[$k] = array_unique($al[$k]);
1707
-							$al[$k] = array_values($al[$k]);
1708
-						}
1709
-					} else {
1710
-						$al[$k] = $al[$k] || $v;
1711
-					}
1712
-				} else {
1713
-					$al[$k] = $v;
1714
-				}
1715
-			}
1716
-		}
1717
-
1718
-		return $al;
1719
-	}
1720
-
1721
-	/**
1722
-	 * Create a new share
1723
-	 *
1724
-	 * @return IShare
1725
-	 */
1726
-	public function newShare() {
1727
-		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1728
-	}
1729
-
1730
-	/**
1731
-	 * Is the share API enabled
1732
-	 *
1733
-	 * @return bool
1734
-	 */
1735
-	public function shareApiEnabled() {
1736
-		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1737
-	}
1738
-
1739
-	/**
1740
-	 * Is public link sharing enabled
1741
-	 *
1742
-	 * @return bool
1743
-	 */
1744
-	public function shareApiAllowLinks() {
1745
-		if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1746
-			return false;
1747
-		}
1748
-
1749
-		$user = $this->userSession->getUser();
1750
-		if ($user) {
1751
-			$excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
1752
-			if ($excludedGroups) {
1753
-				$userGroups = $this->groupManager->getUserGroupIds($user);
1754
-				return !(bool)array_intersect($excludedGroups, $userGroups);
1755
-			}
1756
-		}
1757
-
1758
-		return true;
1759
-	}
1760
-
1761
-	/**
1762
-	 * Is password on public link requires
1763
-	 *
1764
-	 * @param bool Check group membership exclusion
1765
-	 * @return bool
1766
-	 */
1767
-	public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true) {
1768
-		$excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
1769
-		if ($excludedGroups !== '' && $checkGroupMembership) {
1770
-			$excludedGroups = json_decode($excludedGroups);
1771
-			$user = $this->userSession->getUser();
1772
-			if ($user) {
1773
-				$userGroups = $this->groupManager->getUserGroupIds($user);
1774
-				if ((bool)array_intersect($excludedGroups, $userGroups)) {
1775
-					return false;
1776
-				}
1777
-			}
1778
-		}
1779
-		return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1780
-	}
1781
-
1782
-	/**
1783
-	 * Is default link expire date enabled
1784
-	 *
1785
-	 * @return bool
1786
-	 */
1787
-	public function shareApiLinkDefaultExpireDate() {
1788
-		return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1789
-	}
1790
-
1791
-	/**
1792
-	 * Is default link expire date enforced
1793
-	 *`
1794
-	 *
1795
-	 * @return bool
1796
-	 */
1797
-	public function shareApiLinkDefaultExpireDateEnforced() {
1798
-		return $this->shareApiLinkDefaultExpireDate() &&
1799
-			$this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1800
-	}
1801
-
1802
-
1803
-	/**
1804
-	 * Number of default link expire days
1805
-	 *
1806
-	 * @return int
1807
-	 */
1808
-	public function shareApiLinkDefaultExpireDays() {
1809
-		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1810
-	}
1811
-
1812
-	/**
1813
-	 * Is default internal expire date enabled
1814
-	 *
1815
-	 * @return bool
1816
-	 */
1817
-	public function shareApiInternalDefaultExpireDate(): bool {
1818
-		return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
1819
-	}
1820
-
1821
-	/**
1822
-	 * Is default remote expire date enabled
1823
-	 *
1824
-	 * @return bool
1825
-	 */
1826
-	public function shareApiRemoteDefaultExpireDate(): bool {
1827
-		return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
1828
-	}
1829
-
1830
-	/**
1831
-	 * Is default expire date enforced
1832
-	 *
1833
-	 * @return bool
1834
-	 */
1835
-	public function shareApiInternalDefaultExpireDateEnforced(): bool {
1836
-		return $this->shareApiInternalDefaultExpireDate() &&
1837
-			$this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
1838
-	}
1839
-
1840
-	/**
1841
-	 * Is default expire date enforced for remote shares
1842
-	 *
1843
-	 * @return bool
1844
-	 */
1845
-	public function shareApiRemoteDefaultExpireDateEnforced(): bool {
1846
-		return $this->shareApiRemoteDefaultExpireDate() &&
1847
-			$this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
1848
-	}
1849
-
1850
-	/**
1851
-	 * Number of default expire days
1852
-	 *
1853
-	 * @return int
1854
-	 */
1855
-	public function shareApiInternalDefaultExpireDays(): int {
1856
-		return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
1857
-	}
1858
-
1859
-	/**
1860
-	 * Number of default expire days for remote shares
1861
-	 *
1862
-	 * @return int
1863
-	 */
1864
-	public function shareApiRemoteDefaultExpireDays(): int {
1865
-		return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
1866
-	}
1867
-
1868
-	/**
1869
-	 * Allow public upload on link shares
1870
-	 *
1871
-	 * @return bool
1872
-	 */
1873
-	public function shareApiLinkAllowPublicUpload() {
1874
-		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1875
-	}
1876
-
1877
-	/**
1878
-	 * check if user can only share with group members
1879
-	 *
1880
-	 * @return bool
1881
-	 */
1882
-	public function shareWithGroupMembersOnly() {
1883
-		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1884
-	}
1885
-
1886
-	/**
1887
-	 * If shareWithGroupMembersOnly is enabled, return an optional
1888
-	 * list of groups that must be excluded from the principle of
1889
-	 * belonging to the same group.
1890
-	 *
1891
-	 * @return array
1892
-	 */
1893
-	public function shareWithGroupMembersOnlyExcludeGroupsList() {
1894
-		if (!$this->shareWithGroupMembersOnly()) {
1895
-			return [];
1896
-		}
1897
-		$excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
1898
-		return json_decode($excludeGroups, true) ?? [];
1899
-	}
1900
-
1901
-	/**
1902
-	 * Check if users can share with groups
1903
-	 *
1904
-	 * @return bool
1905
-	 */
1906
-	public function allowGroupSharing() {
1907
-		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1908
-	}
1909
-
1910
-	public function allowEnumeration(): bool {
1911
-		return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
1912
-	}
1913
-
1914
-	public function limitEnumerationToGroups(): bool {
1915
-		return $this->allowEnumeration() &&
1916
-			$this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
1917
-	}
1918
-
1919
-	public function limitEnumerationToPhone(): bool {
1920
-		return $this->allowEnumeration() &&
1921
-			$this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
1922
-	}
1923
-
1924
-	public function allowEnumerationFullMatch(): bool {
1925
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
1926
-	}
1927
-
1928
-	public function matchEmail(): bool {
1929
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
1930
-	}
1931
-
1932
-	public function ignoreSecondDisplayName(): bool {
1933
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
1934
-	}
1935
-
1936
-	public function allowCustomTokens(): bool {
1937
-		return $this->appConfig->getValueBool('core', 'shareapi_allow_custom_tokens', false);
1938
-	}
1939
-
1940
-	public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool {
1941
-		if ($this->allowEnumerationFullMatch()) {
1942
-			return true;
1943
-		}
1944
-
1945
-		if (!$this->allowEnumeration()) {
1946
-			return false;
1947
-		}
1948
-
1949
-		if (!$this->limitEnumerationToPhone() && !$this->limitEnumerationToGroups()) {
1950
-			// Enumeration is enabled and not restricted: OK
1951
-			return true;
1952
-		}
1953
-
1954
-		if (!$currentUser instanceof IUser) {
1955
-			// Enumeration restrictions require an account
1956
-			return false;
1957
-		}
1958
-
1959
-		// Enumeration is limited to phone match
1960
-		if ($this->limitEnumerationToPhone() && $this->knownUserService->isKnownToUser($currentUser->getUID(), $targetUser->getUID())) {
1961
-			return true;
1962
-		}
1963
-
1964
-		// Enumeration is limited to groups
1965
-		if ($this->limitEnumerationToGroups()) {
1966
-			$currentUserGroupIds = $this->groupManager->getUserGroupIds($currentUser);
1967
-			$targetUserGroupIds = $this->groupManager->getUserGroupIds($targetUser);
1968
-			if (!empty(array_intersect($currentUserGroupIds, $targetUserGroupIds))) {
1969
-				return true;
1970
-			}
1971
-		}
1972
-
1973
-		return false;
1974
-	}
1975
-
1976
-	/**
1977
-	 * Check if sharing is disabled for the current user
1978
-	 */
1979
-	public function sharingDisabledForUser(?string $userId): bool {
1980
-		return $this->shareDisableChecker->sharingDisabledForUser($userId);
1981
-	}
1982
-
1983
-	/**
1984
-	 * @inheritdoc
1985
-	 */
1986
-	public function outgoingServer2ServerSharesAllowed() {
1987
-		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1988
-	}
1989
-
1990
-	/**
1991
-	 * @inheritdoc
1992
-	 */
1993
-	public function outgoingServer2ServerGroupSharesAllowed() {
1994
-		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1995
-	}
1996
-
1997
-	/**
1998
-	 * @inheritdoc
1999
-	 */
2000
-	public function shareProviderExists($shareType) {
2001
-		try {
2002
-			$this->factory->getProviderForType($shareType);
2003
-		} catch (ProviderException $e) {
2004
-			return false;
2005
-		}
2006
-
2007
-		return true;
2008
-	}
2009
-
2010
-	public function registerShareProvider(string $shareProviderClass): void {
2011
-		$this->factory->registerProvider($shareProviderClass);
2012
-	}
2013
-
2014
-	public function getAllShares(): iterable {
2015
-		$providers = $this->factory->getAllProviders();
2016
-
2017
-		foreach ($providers as $provider) {
2018
-			yield from $provider->getAllShares();
2019
-		}
2020
-	}
2021
-
2022
-	public function generateToken(): string {
2023
-		// Initial token length
2024
-		$tokenLength = \OC\Share\Helper::getTokenLength();
2025
-
2026
-		do {
2027
-			$tokenExists = false;
2028
-
2029
-			for ($i = 0; $i <= 2; $i++) {
2030
-				// Generate a new token
2031
-				$token = $this->secureRandom->generate(
2032
-					$tokenLength,
2033
-					ISecureRandom::CHAR_HUMAN_READABLE,
2034
-				);
2035
-
2036
-				try {
2037
-					// Try to fetch a share with the generated token
2038
-					$this->getShareByToken($token);
2039
-					$tokenExists = true; // Token exists, we need to try again
2040
-				} catch (ShareNotFound $e) {
2041
-					// Token is unique, exit the loop
2042
-					$tokenExists = false;
2043
-					break;
2044
-				}
2045
-			}
2046
-
2047
-			// If we've reached the maximum attempts and the token still exists, increase the token length
2048
-			if ($tokenExists) {
2049
-				$tokenLength++;
2050
-
2051
-				// Check if the token length exceeds the maximum allowed length
2052
-				if ($tokenLength > \OC\Share\Constants::MAX_TOKEN_LENGTH) {
2053
-					throw new ShareTokenException('Unable to generate a unique share token. Maximum token length exceeded.');
2054
-				}
2055
-			}
2056
-		} while ($tokenExists);
2057
-
2058
-		return $token;
2059
-	}
1469
+        if (($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL)
1470
+            && $share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()) {
1471
+            $share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1472
+        }
1473
+
1474
+        return $share;
1475
+    }
1476
+
1477
+    /**
1478
+     * Check expire date and disabled owner
1479
+     *
1480
+     * @throws ShareNotFound
1481
+     */
1482
+    protected function checkShare(IShare $share): void {
1483
+        if ($share->isExpired()) {
1484
+            $this->deleteShare($share);
1485
+            throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1486
+        }
1487
+        if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
1488
+            $uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
1489
+            foreach ($uids as $uid) {
1490
+                $user = $this->userManager->get($uid);
1491
+                if ($user?->isEnabled() === false) {
1492
+                    throw new ShareNotFound($this->l->t('The requested share comes from a disabled user'));
1493
+                }
1494
+            }
1495
+        }
1496
+    }
1497
+
1498
+    /**
1499
+     * Verify the password of a public share
1500
+     *
1501
+     * @param IShare $share
1502
+     * @param ?string $password
1503
+     * @return bool
1504
+     */
1505
+    public function checkPassword(IShare $share, $password) {
1506
+
1507
+        // if there is no password on the share object / passsword is null, there is nothing to check
1508
+        if ($password === null || $share->getPassword() === null) {
1509
+            return false;
1510
+        }
1511
+
1512
+        // Makes sure password hasn't expired
1513
+        $expirationTime = $share->getPasswordExpirationTime();
1514
+        if ($expirationTime !== null && $expirationTime < new \DateTime()) {
1515
+            return false;
1516
+        }
1517
+
1518
+        $newHash = '';
1519
+        if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1520
+            return false;
1521
+        }
1522
+
1523
+        if (!empty($newHash)) {
1524
+            $share->setPassword($newHash);
1525
+            $provider = $this->factory->getProviderForType($share->getShareType());
1526
+            $provider->update($share);
1527
+        }
1528
+
1529
+        return true;
1530
+    }
1531
+
1532
+    /**
1533
+     * @inheritdoc
1534
+     */
1535
+    public function userDeleted($uid) {
1536
+        $types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
1537
+
1538
+        foreach ($types as $type) {
1539
+            try {
1540
+                $provider = $this->factory->getProviderForType($type);
1541
+            } catch (ProviderException $e) {
1542
+                continue;
1543
+            }
1544
+            $provider->userDeleted($uid, $type);
1545
+        }
1546
+    }
1547
+
1548
+    /**
1549
+     * @inheritdoc
1550
+     */
1551
+    public function groupDeleted($gid) {
1552
+        foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1553
+            try {
1554
+                $provider = $this->factory->getProviderForType($type);
1555
+            } catch (ProviderException $e) {
1556
+                continue;
1557
+            }
1558
+            $provider->groupDeleted($gid);
1559
+        }
1560
+
1561
+        $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1562
+        if ($excludedGroups === '') {
1563
+            return;
1564
+        }
1565
+
1566
+        $excludedGroups = json_decode($excludedGroups, true);
1567
+        if (json_last_error() !== JSON_ERROR_NONE) {
1568
+            return;
1569
+        }
1570
+
1571
+        $excludedGroups = array_diff($excludedGroups, [$gid]);
1572
+        $this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
1573
+    }
1574
+
1575
+    /**
1576
+     * @inheritdoc
1577
+     */
1578
+    public function userDeletedFromGroup($uid, $gid) {
1579
+        foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1580
+            try {
1581
+                $provider = $this->factory->getProviderForType($type);
1582
+            } catch (ProviderException $e) {
1583
+                continue;
1584
+            }
1585
+            $provider->userDeletedFromGroup($uid, $gid);
1586
+        }
1587
+    }
1588
+
1589
+    /**
1590
+     * Get access list to a path. This means
1591
+     * all the users that can access a given path.
1592
+     *
1593
+     * Consider:
1594
+     * -root
1595
+     * |-folder1 (23)
1596
+     *  |-folder2 (32)
1597
+     *   |-fileA (42)
1598
+     *
1599
+     * fileA is shared with user1 and user1@server1 and email1@maildomain1
1600
+     * folder2 is shared with group2 (user4 is a member of group2)
1601
+     * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
1602
+     *                        and email2@maildomain2
1603
+     *
1604
+     * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
1605
+     * [
1606
+     *  users  => [
1607
+     *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
1608
+     *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
1609
+     *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
1610
+     *  ],
1611
+     *  remote => [
1612
+     *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
1613
+     *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
1614
+     *  ],
1615
+     *  public => bool
1616
+     *  mail => [
1617
+     *      'email1@maildomain1' => ['node_id' => 42, 'token' => 'aBcDeFg'],
1618
+     *      'email2@maildomain2' => ['node_id' => 23, 'token' => 'hIjKlMn'],
1619
+     *  ]
1620
+     * ]
1621
+     *
1622
+     * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
1623
+     * [
1624
+     *  users  => ['user1', 'user2', 'user4'],
1625
+     *  remote => bool,
1626
+     *  public => bool
1627
+     *  mail => ['email1@maildomain1', 'email2@maildomain2']
1628
+     * ]
1629
+     *
1630
+     * This is required for encryption/activity
1631
+     *
1632
+     * @param \OCP\Files\Node $path
1633
+     * @param bool $recursive Should we check all parent folders as well
1634
+     * @param bool $currentAccess Ensure the recipient has access to the file (e.g. did not unshare it)
1635
+     * @return array
1636
+     */
1637
+    public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
1638
+        $owner = $path->getOwner();
1639
+
1640
+        if ($owner === null) {
1641
+            return [];
1642
+        }
1643
+
1644
+        $owner = $owner->getUID();
1645
+
1646
+        if ($currentAccess) {
1647
+            $al = ['users' => [], 'remote' => [], 'public' => false, 'mail' => []];
1648
+        } else {
1649
+            $al = ['users' => [], 'remote' => false, 'public' => false, 'mail' => []];
1650
+        }
1651
+        if (!$this->userManager->userExists($owner)) {
1652
+            return $al;
1653
+        }
1654
+
1655
+        //Get node for the owner and correct the owner in case of external storage
1656
+        $userFolder = $this->rootFolder->getUserFolder($owner);
1657
+        if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1658
+            $path = $userFolder->getFirstNodeById($path->getId());
1659
+            if ($path === null || $path->getOwner() === null) {
1660
+                return [];
1661
+            }
1662
+            $owner = $path->getOwner()->getUID();
1663
+        }
1664
+
1665
+        $providers = $this->factory->getAllProviders();
1666
+
1667
+        /** @var Node[] $nodes */
1668
+        $nodes = [];
1669
+
1670
+
1671
+        if ($currentAccess) {
1672
+            $ownerPath = $path->getPath();
1673
+            $ownerPath = explode('/', $ownerPath, 4);
1674
+            if (count($ownerPath) < 4) {
1675
+                $ownerPath = '';
1676
+            } else {
1677
+                $ownerPath = $ownerPath[3];
1678
+            }
1679
+            $al['users'][$owner] = [
1680
+                'node_id' => $path->getId(),
1681
+                'node_path' => '/' . $ownerPath,
1682
+            ];
1683
+        } else {
1684
+            $al['users'][] = $owner;
1685
+        }
1686
+
1687
+        // Collect all the shares
1688
+        while ($path->getPath() !== $userFolder->getPath()) {
1689
+            $nodes[] = $path;
1690
+            if (!$recursive) {
1691
+                break;
1692
+            }
1693
+            $path = $path->getParent();
1694
+        }
1695
+
1696
+        foreach ($providers as $provider) {
1697
+            $tmp = $provider->getAccessList($nodes, $currentAccess);
1698
+
1699
+            foreach ($tmp as $k => $v) {
1700
+                if (isset($al[$k])) {
1701
+                    if (is_array($al[$k])) {
1702
+                        if ($currentAccess) {
1703
+                            $al[$k] += $v;
1704
+                        } else {
1705
+                            $al[$k] = array_merge($al[$k], $v);
1706
+                            $al[$k] = array_unique($al[$k]);
1707
+                            $al[$k] = array_values($al[$k]);
1708
+                        }
1709
+                    } else {
1710
+                        $al[$k] = $al[$k] || $v;
1711
+                    }
1712
+                } else {
1713
+                    $al[$k] = $v;
1714
+                }
1715
+            }
1716
+        }
1717
+
1718
+        return $al;
1719
+    }
1720
+
1721
+    /**
1722
+     * Create a new share
1723
+     *
1724
+     * @return IShare
1725
+     */
1726
+    public function newShare() {
1727
+        return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1728
+    }
1729
+
1730
+    /**
1731
+     * Is the share API enabled
1732
+     *
1733
+     * @return bool
1734
+     */
1735
+    public function shareApiEnabled() {
1736
+        return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1737
+    }
1738
+
1739
+    /**
1740
+     * Is public link sharing enabled
1741
+     *
1742
+     * @return bool
1743
+     */
1744
+    public function shareApiAllowLinks() {
1745
+        if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1746
+            return false;
1747
+        }
1748
+
1749
+        $user = $this->userSession->getUser();
1750
+        if ($user) {
1751
+            $excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
1752
+            if ($excludedGroups) {
1753
+                $userGroups = $this->groupManager->getUserGroupIds($user);
1754
+                return !(bool)array_intersect($excludedGroups, $userGroups);
1755
+            }
1756
+        }
1757
+
1758
+        return true;
1759
+    }
1760
+
1761
+    /**
1762
+     * Is password on public link requires
1763
+     *
1764
+     * @param bool Check group membership exclusion
1765
+     * @return bool
1766
+     */
1767
+    public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true) {
1768
+        $excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
1769
+        if ($excludedGroups !== '' && $checkGroupMembership) {
1770
+            $excludedGroups = json_decode($excludedGroups);
1771
+            $user = $this->userSession->getUser();
1772
+            if ($user) {
1773
+                $userGroups = $this->groupManager->getUserGroupIds($user);
1774
+                if ((bool)array_intersect($excludedGroups, $userGroups)) {
1775
+                    return false;
1776
+                }
1777
+            }
1778
+        }
1779
+        return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
1780
+    }
1781
+
1782
+    /**
1783
+     * Is default link expire date enabled
1784
+     *
1785
+     * @return bool
1786
+     */
1787
+    public function shareApiLinkDefaultExpireDate() {
1788
+        return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
1789
+    }
1790
+
1791
+    /**
1792
+     * Is default link expire date enforced
1793
+     *`
1794
+     *
1795
+     * @return bool
1796
+     */
1797
+    public function shareApiLinkDefaultExpireDateEnforced() {
1798
+        return $this->shareApiLinkDefaultExpireDate() &&
1799
+            $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
1800
+    }
1801
+
1802
+
1803
+    /**
1804
+     * Number of default link expire days
1805
+     *
1806
+     * @return int
1807
+     */
1808
+    public function shareApiLinkDefaultExpireDays() {
1809
+        return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1810
+    }
1811
+
1812
+    /**
1813
+     * Is default internal expire date enabled
1814
+     *
1815
+     * @return bool
1816
+     */
1817
+    public function shareApiInternalDefaultExpireDate(): bool {
1818
+        return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
1819
+    }
1820
+
1821
+    /**
1822
+     * Is default remote expire date enabled
1823
+     *
1824
+     * @return bool
1825
+     */
1826
+    public function shareApiRemoteDefaultExpireDate(): bool {
1827
+        return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
1828
+    }
1829
+
1830
+    /**
1831
+     * Is default expire date enforced
1832
+     *
1833
+     * @return bool
1834
+     */
1835
+    public function shareApiInternalDefaultExpireDateEnforced(): bool {
1836
+        return $this->shareApiInternalDefaultExpireDate() &&
1837
+            $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
1838
+    }
1839
+
1840
+    /**
1841
+     * Is default expire date enforced for remote shares
1842
+     *
1843
+     * @return bool
1844
+     */
1845
+    public function shareApiRemoteDefaultExpireDateEnforced(): bool {
1846
+        return $this->shareApiRemoteDefaultExpireDate() &&
1847
+            $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
1848
+    }
1849
+
1850
+    /**
1851
+     * Number of default expire days
1852
+     *
1853
+     * @return int
1854
+     */
1855
+    public function shareApiInternalDefaultExpireDays(): int {
1856
+        return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
1857
+    }
1858
+
1859
+    /**
1860
+     * Number of default expire days for remote shares
1861
+     *
1862
+     * @return int
1863
+     */
1864
+    public function shareApiRemoteDefaultExpireDays(): int {
1865
+        return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
1866
+    }
1867
+
1868
+    /**
1869
+     * Allow public upload on link shares
1870
+     *
1871
+     * @return bool
1872
+     */
1873
+    public function shareApiLinkAllowPublicUpload() {
1874
+        return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1875
+    }
1876
+
1877
+    /**
1878
+     * check if user can only share with group members
1879
+     *
1880
+     * @return bool
1881
+     */
1882
+    public function shareWithGroupMembersOnly() {
1883
+        return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1884
+    }
1885
+
1886
+    /**
1887
+     * If shareWithGroupMembersOnly is enabled, return an optional
1888
+     * list of groups that must be excluded from the principle of
1889
+     * belonging to the same group.
1890
+     *
1891
+     * @return array
1892
+     */
1893
+    public function shareWithGroupMembersOnlyExcludeGroupsList() {
1894
+        if (!$this->shareWithGroupMembersOnly()) {
1895
+            return [];
1896
+        }
1897
+        $excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
1898
+        return json_decode($excludeGroups, true) ?? [];
1899
+    }
1900
+
1901
+    /**
1902
+     * Check if users can share with groups
1903
+     *
1904
+     * @return bool
1905
+     */
1906
+    public function allowGroupSharing() {
1907
+        return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1908
+    }
1909
+
1910
+    public function allowEnumeration(): bool {
1911
+        return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
1912
+    }
1913
+
1914
+    public function limitEnumerationToGroups(): bool {
1915
+        return $this->allowEnumeration() &&
1916
+            $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
1917
+    }
1918
+
1919
+    public function limitEnumerationToPhone(): bool {
1920
+        return $this->allowEnumeration() &&
1921
+            $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
1922
+    }
1923
+
1924
+    public function allowEnumerationFullMatch(): bool {
1925
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
1926
+    }
1927
+
1928
+    public function matchEmail(): bool {
1929
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
1930
+    }
1931
+
1932
+    public function ignoreSecondDisplayName(): bool {
1933
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
1934
+    }
1935
+
1936
+    public function allowCustomTokens(): bool {
1937
+        return $this->appConfig->getValueBool('core', 'shareapi_allow_custom_tokens', false);
1938
+    }
1939
+
1940
+    public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool {
1941
+        if ($this->allowEnumerationFullMatch()) {
1942
+            return true;
1943
+        }
1944
+
1945
+        if (!$this->allowEnumeration()) {
1946
+            return false;
1947
+        }
1948
+
1949
+        if (!$this->limitEnumerationToPhone() && !$this->limitEnumerationToGroups()) {
1950
+            // Enumeration is enabled and not restricted: OK
1951
+            return true;
1952
+        }
1953
+
1954
+        if (!$currentUser instanceof IUser) {
1955
+            // Enumeration restrictions require an account
1956
+            return false;
1957
+        }
1958
+
1959
+        // Enumeration is limited to phone match
1960
+        if ($this->limitEnumerationToPhone() && $this->knownUserService->isKnownToUser($currentUser->getUID(), $targetUser->getUID())) {
1961
+            return true;
1962
+        }
1963
+
1964
+        // Enumeration is limited to groups
1965
+        if ($this->limitEnumerationToGroups()) {
1966
+            $currentUserGroupIds = $this->groupManager->getUserGroupIds($currentUser);
1967
+            $targetUserGroupIds = $this->groupManager->getUserGroupIds($targetUser);
1968
+            if (!empty(array_intersect($currentUserGroupIds, $targetUserGroupIds))) {
1969
+                return true;
1970
+            }
1971
+        }
1972
+
1973
+        return false;
1974
+    }
1975
+
1976
+    /**
1977
+     * Check if sharing is disabled for the current user
1978
+     */
1979
+    public function sharingDisabledForUser(?string $userId): bool {
1980
+        return $this->shareDisableChecker->sharingDisabledForUser($userId);
1981
+    }
1982
+
1983
+    /**
1984
+     * @inheritdoc
1985
+     */
1986
+    public function outgoingServer2ServerSharesAllowed() {
1987
+        return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1988
+    }
1989
+
1990
+    /**
1991
+     * @inheritdoc
1992
+     */
1993
+    public function outgoingServer2ServerGroupSharesAllowed() {
1994
+        return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1995
+    }
1996
+
1997
+    /**
1998
+     * @inheritdoc
1999
+     */
2000
+    public function shareProviderExists($shareType) {
2001
+        try {
2002
+            $this->factory->getProviderForType($shareType);
2003
+        } catch (ProviderException $e) {
2004
+            return false;
2005
+        }
2006
+
2007
+        return true;
2008
+    }
2009
+
2010
+    public function registerShareProvider(string $shareProviderClass): void {
2011
+        $this->factory->registerProvider($shareProviderClass);
2012
+    }
2013
+
2014
+    public function getAllShares(): iterable {
2015
+        $providers = $this->factory->getAllProviders();
2016
+
2017
+        foreach ($providers as $provider) {
2018
+            yield from $provider->getAllShares();
2019
+        }
2020
+    }
2021
+
2022
+    public function generateToken(): string {
2023
+        // Initial token length
2024
+        $tokenLength = \OC\Share\Helper::getTokenLength();
2025
+
2026
+        do {
2027
+            $tokenExists = false;
2028
+
2029
+            for ($i = 0; $i <= 2; $i++) {
2030
+                // Generate a new token
2031
+                $token = $this->secureRandom->generate(
2032
+                    $tokenLength,
2033
+                    ISecureRandom::CHAR_HUMAN_READABLE,
2034
+                );
2035
+
2036
+                try {
2037
+                    // Try to fetch a share with the generated token
2038
+                    $this->getShareByToken($token);
2039
+                    $tokenExists = true; // Token exists, we need to try again
2040
+                } catch (ShareNotFound $e) {
2041
+                    // Token is unique, exit the loop
2042
+                    $tokenExists = false;
2043
+                    break;
2044
+                }
2045
+            }
2046
+
2047
+            // If we've reached the maximum attempts and the token still exists, increase the token length
2048
+            if ($tokenExists) {
2049
+                $tokenLength++;
2050
+
2051
+                // Check if the token length exceeds the maximum allowed length
2052
+                if ($tokenLength > \OC\Share\Constants::MAX_TOKEN_LENGTH) {
2053
+                    throw new ShareTokenException('Unable to generate a unique share token. Maximum token length exceeded.');
2054
+                }
2055
+            }
2056
+        } while ($tokenExists);
2057
+
2058
+        return $token;
2059
+    }
2060 2060
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/Controller/ShareAPIController.php 2 patches
Indentation   +2149 added lines, -2149 removed lines patch added patch discarded remove patch
@@ -71,2156 +71,2156 @@
 block discarded – undo
71 71
  */
72 72
 class ShareAPIController extends OCSController {
73 73
 
74
-	private ?Node $lockedNode = null;
75
-
76
-	/**
77
-	 * Share20OCS constructor.
78
-	 */
79
-	public function __construct(
80
-		string $appName,
81
-		IRequest $request,
82
-		private IManager $shareManager,
83
-		private IGroupManager $groupManager,
84
-		private IUserManager $userManager,
85
-		private IRootFolder $rootFolder,
86
-		private IURLGenerator $urlGenerator,
87
-		private IL10N $l,
88
-		private IConfig $config,
89
-		private IAppManager $appManager,
90
-		private ContainerInterface $serverContainer,
91
-		private IUserStatusManager $userStatusManager,
92
-		private IPreview $previewManager,
93
-		private IDateTimeZone $dateTimeZone,
94
-		private LoggerInterface $logger,
95
-		private IProviderFactory $factory,
96
-		private IMailer $mailer,
97
-		private ?string $userId = null,
98
-	) {
99
-		parent::__construct($appName, $request);
100
-	}
101
-
102
-	/**
103
-	 * Convert an IShare to an array for OCS output
104
-	 *
105
-	 * @param IShare $share
106
-	 * @param Node|null $recipientNode
107
-	 * @return Files_SharingShare
108
-	 * @throws NotFoundException In case the node can't be resolved.
109
-	 *
110
-	 * @suppress PhanUndeclaredClassMethod
111
-	 */
112
-	protected function formatShare(IShare $share, ?Node $recipientNode = null): array {
113
-		$sharedBy = $this->userManager->get($share->getSharedBy());
114
-		$shareOwner = $this->userManager->get($share->getShareOwner());
115
-
116
-		$isOwnShare = false;
117
-		if ($shareOwner !== null) {
118
-			$isOwnShare = $shareOwner->getUID() === $this->userId;
119
-		}
120
-
121
-		$result = [
122
-			'id' => $share->getId(),
123
-			'share_type' => $share->getShareType(),
124
-			'uid_owner' => $share->getSharedBy(),
125
-			'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
126
-			// recipient permissions
127
-			'permissions' => $share->getPermissions(),
128
-			// current user permissions on this share
129
-			'can_edit' => $this->canEditShare($share),
130
-			'can_delete' => $this->canDeleteShare($share),
131
-			'stime' => $share->getShareTime()->getTimestamp(),
132
-			'parent' => null,
133
-			'expiration' => null,
134
-			'token' => null,
135
-			'uid_file_owner' => $share->getShareOwner(),
136
-			'note' => $share->getNote(),
137
-			'label' => $share->getLabel(),
138
-			'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
139
-		];
140
-
141
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
142
-		if ($recipientNode) {
143
-			$node = $recipientNode;
144
-		} else {
145
-			$node = $userFolder->getFirstNodeById($share->getNodeId());
146
-			if (!$node) {
147
-				// fallback to guessing the path
148
-				$node = $userFolder->get($share->getTarget());
149
-				if ($node === null || $share->getTarget() === '') {
150
-					throw new NotFoundException();
151
-				}
152
-			}
153
-		}
154
-
155
-		$result['path'] = $userFolder->getRelativePath($node->getPath());
156
-		if ($node instanceof Folder) {
157
-			$result['item_type'] = 'folder';
158
-		} else {
159
-			$result['item_type'] = 'file';
160
-		}
161
-
162
-		// Get the original node permission if the share owner is the current user
163
-		if ($isOwnShare) {
164
-			$result['item_permissions'] = $node->getPermissions();
165
-		}
166
-
167
-		// If we're on the recipient side, the node permissions
168
-		// are bound to the share permissions. So we need to
169
-		// adjust the permissions to the share permissions if necessary.
170
-		if (!$isOwnShare) {
171
-			$result['item_permissions'] = $share->getPermissions();
172
-
173
-			// For some reason, single files share are forbidden to have the delete permission
174
-			// since we have custom methods to check those, let's adjust straight away.
175
-			// DAV permissions does not have that issue though.
176
-			if ($this->canDeleteShare($share) || $this->canDeleteShareFromSelf($share)) {
177
-				$result['item_permissions'] |= Constants::PERMISSION_DELETE;
178
-			}
179
-			if ($this->canEditShare($share)) {
180
-				$result['item_permissions'] |= Constants::PERMISSION_UPDATE;
181
-			}
182
-		}
183
-
184
-		// See MOUNT_ROOT_PROPERTYNAME dav property
185
-		$result['is-mount-root'] = $node->getInternalPath() === '';
186
-		$result['mount-type'] = $node->getMountPoint()->getMountType();
187
-
188
-		$result['mimetype'] = $node->getMimetype();
189
-		$result['has_preview'] = $this->previewManager->isAvailable($node);
190
-		$result['storage_id'] = $node->getStorage()->getId();
191
-		$result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
192
-		$result['item_source'] = $node->getId();
193
-		$result['file_source'] = $node->getId();
194
-		$result['file_parent'] = $node->getParent()->getId();
195
-		$result['file_target'] = $share->getTarget();
196
-		$result['item_size'] = $node->getSize();
197
-		$result['item_mtime'] = $node->getMTime();
198
-
199
-		$expiration = $share->getExpirationDate();
200
-		if ($expiration !== null) {
201
-			$expiration->setTimezone($this->dateTimeZone->getTimeZone());
202
-			$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
203
-		}
204
-
205
-		if ($share->getShareType() === IShare::TYPE_USER) {
206
-			$sharedWith = $this->userManager->get($share->getSharedWith());
207
-			$result['share_with'] = $share->getSharedWith();
208
-			$result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
209
-			$result['share_with_displayname_unique'] = $sharedWith !== null ? (
210
-				!empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
211
-			) : $share->getSharedWith();
212
-
213
-			$userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
214
-			$userStatus = array_shift($userStatuses);
215
-			if ($userStatus) {
216
-				$result['status'] = [
217
-					'status' => $userStatus->getStatus(),
218
-					'message' => $userStatus->getMessage(),
219
-					'icon' => $userStatus->getIcon(),
220
-					'clearAt' => $userStatus->getClearAt()
221
-						? (int)$userStatus->getClearAt()->format('U')
222
-						: null,
223
-				];
224
-			}
225
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
226
-			$group = $this->groupManager->get($share->getSharedWith());
227
-			$result['share_with'] = $share->getSharedWith();
228
-			$result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
229
-		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
230
-
231
-			// "share_with" and "share_with_displayname" for passwords of link
232
-			// shares was deprecated in Nextcloud 15, use "password" instead.
233
-			$result['share_with'] = $share->getPassword();
234
-			$result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
235
-
236
-			$result['password'] = $share->getPassword();
237
-
238
-			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
239
-
240
-			$result['token'] = $share->getToken();
241
-			$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
242
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
243
-			$result['share_with'] = $share->getSharedWith();
244
-			$result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith());
245
-			$result['token'] = $share->getToken();
246
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
247
-			$result['share_with'] = $share->getSharedWith();
248
-			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
249
-			$result['token'] = $share->getToken();
250
-		} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
251
-			$result['share_with'] = $share->getSharedWith();
252
-			$result['password'] = $share->getPassword();
253
-			$result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
254
-			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
255
-			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
256
-			$result['token'] = $share->getToken();
257
-		} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
258
-			// getSharedWith() returns either "name (type, owner)" or
259
-			// "name (type, owner) [id]", depending on the Teams app version.
260
-			$hasCircleId = (substr($share->getSharedWith(), -1) === ']');
261
-
262
-			$result['share_with_displayname'] = $share->getSharedWithDisplayName();
263
-			if (empty($result['share_with_displayname'])) {
264
-				$displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
265
-				$result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
266
-			}
267
-
268
-			$result['share_with_avatar'] = $share->getSharedWithAvatar();
269
-
270
-			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
271
-			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
272
-			if ($shareWithLength === false) {
273
-				$result['share_with'] = substr($share->getSharedWith(), $shareWithStart);
274
-			} else {
275
-				$result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
276
-			}
277
-		} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
278
-			$result['share_with'] = $share->getSharedWith();
279
-			$result['share_with_displayname'] = '';
280
-
281
-			try {
282
-				/** @var array{share_with_displayname: string, share_with_link: string, share_with?: string, token?: string} $roomShare */
283
-				$roomShare = $this->getRoomShareHelper()->formatShare($share);
284
-				$result = array_merge($result, $roomShare);
285
-			} catch (ContainerExceptionInterface $e) {
286
-			}
287
-		} elseif ($share->getShareType() === IShare::TYPE_DECK) {
288
-			$result['share_with'] = $share->getSharedWith();
289
-			$result['share_with_displayname'] = '';
290
-
291
-			try {
292
-				/** @var array{share_with: string, share_with_displayname: string, share_with_link: string} $deckShare */
293
-				$deckShare = $this->getDeckShareHelper()->formatShare($share);
294
-				$result = array_merge($result, $deckShare);
295
-			} catch (ContainerExceptionInterface $e) {
296
-			}
297
-		} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
298
-			$result['share_with'] = $share->getSharedWith();
299
-			$result['share_with_displayname'] = '';
300
-
301
-			try {
302
-				/** @var array{share_with: string, share_with_displayname: string, token: string} $scienceMeshShare */
303
-				$scienceMeshShare = $this->getSciencemeshShareHelper()->formatShare($share);
304
-				$result = array_merge($result, $scienceMeshShare);
305
-			} catch (ContainerExceptionInterface $e) {
306
-			}
307
-		}
308
-
309
-
310
-		$result['mail_send'] = $share->getMailSend() ? 1 : 0;
311
-		$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
312
-
313
-		$result['attributes'] = null;
314
-		if ($attributes = $share->getAttributes()) {
315
-			$result['attributes'] = (string)\json_encode($attributes->toArray());
316
-		}
317
-
318
-		return $result;
319
-	}
320
-
321
-	/**
322
-	 * Check if one of the users address books knows the exact property, if
323
-	 * not we return the full name.
324
-	 *
325
-	 * @param string $query
326
-	 * @param string $property
327
-	 * @return string
328
-	 */
329
-	private function getDisplayNameFromAddressBook(string $query, string $property): string {
330
-		// FIXME: If we inject the contacts manager it gets initialized before any address books are registered
331
-		try {
332
-			$result = Server::get(\OCP\Contacts\IManager::class)->search($query, [$property], [
333
-				'limit' => 1,
334
-				'enumeration' => false,
335
-				'strict_search' => true,
336
-			]);
337
-		} catch (Exception $e) {
338
-			$this->logger->error(
339
-				$e->getMessage(),
340
-				['exception' => $e]
341
-			);
342
-			return $query;
343
-		}
344
-
345
-		foreach ($result as $r) {
346
-			foreach ($r[$property] as $value) {
347
-				if ($value === $query && $r['FN']) {
348
-					return $r['FN'];
349
-				}
350
-			}
351
-		}
352
-
353
-		return $query;
354
-	}
355
-
356
-
357
-	/**
358
-	 * @param list<Files_SharingShare> $shares
359
-	 * @param array<string, string>|null $updatedDisplayName
360
-	 *
361
-	 * @return list<Files_SharingShare>
362
-	 */
363
-	private function fixMissingDisplayName(array $shares, ?array $updatedDisplayName = null): array {
364
-		$userIds = $updated = [];
365
-		foreach ($shares as $share) {
366
-			// share is federated and share have no display name yet
367
-			if ($share['share_type'] === IShare::TYPE_REMOTE
368
-				&& ($share['share_with'] ?? '') !== ''
369
-				&& ($share['share_with_displayname'] ?? '') === '') {
370
-				$userIds[] = $userId = $share['share_with'];
371
-
372
-				if ($updatedDisplayName !== null && array_key_exists($userId, $updatedDisplayName)) {
373
-					$share['share_with_displayname'] = $updatedDisplayName[$userId];
374
-				}
375
-			}
376
-
377
-			// prepping userIds with displayName to be updated
378
-			$updated[] = $share;
379
-		}
380
-
381
-		// if $updatedDisplayName is not null, it means we should have already fixed displayNames of the shares
382
-		if ($updatedDisplayName !== null) {
383
-			return $updated;
384
-		}
385
-
386
-		// get displayName for the generated list of userId with no displayName
387
-		$displayNames = $this->retrieveFederatedDisplayName($userIds);
388
-
389
-		// if no displayName are updated, we exit
390
-		if (empty($displayNames)) {
391
-			return $updated;
392
-		}
393
-
394
-		// let's fix missing display name and returns all shares
395
-		return $this->fixMissingDisplayName($shares, $displayNames);
396
-	}
397
-
398
-
399
-	/**
400
-	 * get displayName of a list of userIds from the lookup-server; through the globalsiteselector app.
401
-	 * returns an array with userIds as keys and displayName as values.
402
-	 *
403
-	 * @param array $userIds
404
-	 * @param bool $cacheOnly - do not reach LUS, get data from cache.
405
-	 *
406
-	 * @return array
407
-	 * @throws ContainerExceptionInterface
408
-	 */
409
-	private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array {
410
-		// check if gss is enabled and available
411
-		if (count($userIds) === 0
412
-			|| !$this->appManager->isEnabledForAnyone('globalsiteselector')
413
-			|| !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
414
-			return [];
415
-		}
416
-
417
-		try {
418
-			$slaveService = Server::get(SlaveService::class);
419
-		} catch (\Throwable $e) {
420
-			$this->logger->error(
421
-				$e->getMessage(),
422
-				['exception' => $e]
423
-			);
424
-			return [];
425
-		}
426
-
427
-		return $slaveService->getUsersDisplayName($userIds, $cacheOnly);
428
-	}
429
-
430
-
431
-	/**
432
-	 * retrieve displayName from cache if available (should be used on federated shares)
433
-	 * if not available in cache/lus, try for get from address-book, else returns empty string.
434
-	 *
435
-	 * @param string $userId
436
-	 * @param bool $cacheOnly if true will not reach the lus but will only get data from cache
437
-	 *
438
-	 * @return string
439
-	 */
440
-	private function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = true): string {
441
-		$details = $this->retrieveFederatedDisplayName([$userId], $cacheOnly);
442
-		if (array_key_exists($userId, $details)) {
443
-			return $details[$userId];
444
-		}
445
-
446
-		$displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD');
447
-		return ($displayName === $userId) ? '' : $displayName;
448
-	}
449
-
450
-
451
-
452
-	/**
453
-	 * Get a specific share by id
454
-	 *
455
-	 * @param string $id ID of the share
456
-	 * @param bool $include_tags Include tags in the share
457
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
458
-	 * @throws OCSNotFoundException Share not found
459
-	 *
460
-	 * 200: Share returned
461
-	 */
462
-	#[NoAdminRequired]
463
-	public function getShare(string $id, bool $include_tags = false): DataResponse {
464
-		try {
465
-			$share = $this->getShareById($id);
466
-		} catch (ShareNotFound $e) {
467
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
468
-		}
469
-
470
-		try {
471
-			if ($this->canAccessShare($share)) {
472
-				$share = $this->formatShare($share);
473
-
474
-				if ($include_tags) {
475
-					$share = Helper::populateTags([$share], Server::get(ITagManager::class));
476
-				} else {
477
-					$share = [$share];
478
-				}
479
-
480
-				return new DataResponse($share);
481
-			}
482
-		} catch (NotFoundException $e) {
483
-			// Fall through
484
-		}
485
-
486
-		throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
487
-	}
488
-
489
-	/**
490
-	 * Delete a share
491
-	 *
492
-	 * @param string $id ID of the share
493
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
494
-	 * @throws OCSNotFoundException Share not found
495
-	 * @throws OCSForbiddenException Missing permissions to delete the share
496
-	 *
497
-	 * 200: Share deleted successfully
498
-	 */
499
-	#[NoAdminRequired]
500
-	public function deleteShare(string $id): DataResponse {
501
-		try {
502
-			$share = $this->getShareById($id);
503
-		} catch (ShareNotFound $e) {
504
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
505
-		}
506
-
507
-		try {
508
-			$this->lock($share->getNode());
509
-		} catch (LockedException $e) {
510
-			throw new OCSNotFoundException($this->l->t('Could not delete share'));
511
-		}
512
-
513
-		if (!$this->canAccessShare($share)) {
514
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
515
-		}
516
-
517
-		// if it's a group share or a room share
518
-		// we don't delete the share, but only the
519
-		// mount point. Allowing it to be restored
520
-		// from the deleted shares
521
-		if ($this->canDeleteShareFromSelf($share)) {
522
-			$this->shareManager->deleteFromSelf($share, $this->userId);
523
-		} else {
524
-			if (!$this->canDeleteShare($share)) {
525
-				throw new OCSForbiddenException($this->l->t('Could not delete share'));
526
-			}
527
-
528
-			$this->shareManager->deleteShare($share);
529
-		}
530
-
531
-		return new DataResponse();
532
-	}
533
-
534
-	/**
535
-	 * Create a share
536
-	 *
537
-	 * @param string|null $path Path of the share
538
-	 * @param int|null $permissions Permissions for the share
539
-	 * @param int $shareType Type of the share
540
-	 * @param ?string $shareWith The entity this should be shared with
541
-	 * @param 'true'|'false'|null $publicUpload If public uploading is allowed (deprecated)
542
-	 * @param string $password Password for the share
543
-	 * @param string|null $sendPasswordByTalk Send the password for the share over Talk
544
-	 * @param ?string $expireDate The expiry date of the share in the user's timezone at 00:00.
545
-	 *                            If $expireDate is not supplied or set to `null`, the system default will be used.
546
-	 * @param string $note Note for the share
547
-	 * @param string $label Label for the share (only used in link and email)
548
-	 * @param string|null $attributes Additional attributes for the share
549
-	 * @param 'false'|'true'|null $sendMail Send a mail to the recipient
550
-	 *
551
-	 * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
552
-	 * @throws OCSBadRequestException Unknown share type
553
-	 * @throws OCSException
554
-	 * @throws OCSForbiddenException Creating the share is not allowed
555
-	 * @throws OCSNotFoundException Creating the share failed
556
-	 * @suppress PhanUndeclaredClassMethod
557
-	 *
558
-	 * 200: Share created
559
-	 */
560
-	#[NoAdminRequired]
561
-	public function createShare(
562
-		?string $path = null,
563
-		?int $permissions = null,
564
-		int $shareType = -1,
565
-		?string $shareWith = null,
566
-		?string $publicUpload = null,
567
-		string $password = '',
568
-		?string $sendPasswordByTalk = null,
569
-		?string $expireDate = null,
570
-		string $note = '',
571
-		string $label = '',
572
-		?string $attributes = null,
573
-		?string $sendMail = null,
574
-	): DataResponse {
575
-		assert($this->userId !== null);
576
-
577
-		$share = $this->shareManager->newShare();
578
-		$hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
579
-
580
-		// Verify path
581
-		if ($path === null) {
582
-			throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
583
-		}
584
-
585
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
586
-		try {
587
-			/** @var \OC\Files\Node\Node $node */
588
-			$node = $userFolder->get($path);
589
-		} catch (NotFoundException $e) {
590
-			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
591
-		}
592
-
593
-		// a user can have access to a file through different paths, with differing permissions
594
-		// combine all permissions to determine if the user can share this file
595
-		$nodes = $userFolder->getById($node->getId());
596
-		foreach ($nodes as $nodeById) {
597
-			/** @var \OC\Files\FileInfo $fileInfo */
598
-			$fileInfo = $node->getFileInfo();
599
-			$fileInfo['permissions'] |= $nodeById->getPermissions();
600
-		}
601
-
602
-		$share->setNode($node);
603
-
604
-		try {
605
-			$this->lock($share->getNode());
606
-		} catch (LockedException $e) {
607
-			throw new OCSNotFoundException($this->l->t('Could not create share'));
608
-		}
609
-
610
-		// Set permissions
611
-		if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_EMAIL) {
612
-			$permissions = $this->getLinkSharePermissions($permissions, $hasPublicUpload);
613
-			$this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
614
-		} else {
615
-			// Use default permissions only for non-link shares to keep legacy behavior
616
-			if ($permissions === null) {
617
-				$permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
618
-			}
619
-			// Non-link shares always require read permissions (link shares could be file drop)
620
-			$permissions |= Constants::PERMISSION_READ;
621
-		}
622
-
623
-		// For legacy reasons the API allows to pass PERMISSIONS_ALL even for single file shares (I look at you Talk)
624
-		if ($node instanceof File) {
625
-			// if this is a single file share we remove the DELETE and CREATE permissions
626
-			$permissions = $permissions & ~(Constants::PERMISSION_DELETE | Constants::PERMISSION_CREATE);
627
-		}
628
-
629
-		/**
630
-		 * Hack for https://github.com/owncloud/core/issues/22587
631
-		 * We check the permissions via webdav. But the permissions of the mount point
632
-		 * do not equal the share permissions. Here we fix that for federated mounts.
633
-		 */
634
-		if ($node->getStorage()->instanceOfStorage(Storage::class)) {
635
-			$permissions &= ~($permissions & ~$node->getPermissions());
636
-		}
637
-
638
-		if ($attributes !== null) {
639
-			$share = $this->setShareAttributes($share, $attributes);
640
-		}
641
-
642
-		// Expire date checks
643
-		// Normally, null means no expiration date but we still set the default for backwards compatibility
644
-		// If the client sends an empty string, we set noExpirationDate to true
645
-		if ($expireDate !== null) {
646
-			if ($expireDate !== '') {
647
-				try {
648
-					$expireDateTime = $this->parseDate($expireDate);
649
-					$share->setExpirationDate($expireDateTime);
650
-				} catch (\Exception $e) {
651
-					throw new OCSNotFoundException($e->getMessage(), $e);
652
-				}
653
-			} else {
654
-				// Client sent empty string for expire date.
655
-				// Set noExpirationDate to true so overwrite is prevented.
656
-				$share->setNoExpirationDate(true);
657
-			}
658
-		}
659
-
660
-		$share->setSharedBy($this->userId);
661
-
662
-		// Handle mail send
663
-		if (is_null($sendMail)) {
664
-			$allowSendMail = $this->config->getSystemValueBool('sharing.enable_share_mail', true);
665
-			if ($allowSendMail !== true || $shareType === IShare::TYPE_EMAIL) {
666
-				// Define a default behavior when sendMail is not provided
667
-				// For email shares with a valid recipient, the default is to send the mail
668
-				// For all other share types, the default is to not send the mail
669
-				$allowSendMail = ($shareType === IShare::TYPE_EMAIL && $shareWith !== null && $shareWith !== '');
670
-			}
671
-			$share->setMailSend($allowSendMail);
672
-		} else {
673
-			$share->setMailSend($sendMail === 'true');
674
-		}
675
-
676
-		if ($shareType === IShare::TYPE_USER) {
677
-			// Valid user is required to share
678
-			if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
679
-				throw new OCSNotFoundException($this->l->t('Please specify a valid account to share with'));
680
-			}
681
-			$share->setSharedWith($shareWith);
682
-			$share->setPermissions($permissions);
683
-		} elseif ($shareType === IShare::TYPE_GROUP) {
684
-			if (!$this->shareManager->allowGroupSharing()) {
685
-				throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
686
-			}
687
-
688
-			// Valid group is required to share
689
-			if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
690
-				throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
691
-			}
692
-			$share->setSharedWith($shareWith);
693
-			$share->setPermissions($permissions);
694
-		} elseif ($shareType === IShare::TYPE_LINK
695
-			|| $shareType === IShare::TYPE_EMAIL) {
696
-
697
-			// Can we even share links?
698
-			if (!$this->shareManager->shareApiAllowLinks()) {
699
-				throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
700
-			}
701
-
702
-			$this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
703
-			$share->setPermissions($permissions);
704
-
705
-			// Set password
706
-			if ($password !== '') {
707
-				$share->setPassword($password);
708
-			}
709
-
710
-			// Only share by mail have a recipient
711
-			if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
712
-				// If sending a mail have been requested, validate the mail address
713
-				if ($share->getMailSend() && !$this->mailer->validateMailAddress($shareWith)) {
714
-					throw new OCSNotFoundException($this->l->t('Please specify a valid email address'));
715
-				}
716
-				$share->setSharedWith($shareWith);
717
-			}
718
-
719
-			// If we have a label, use it
720
-			if ($label !== '') {
721
-				if (strlen($label) > 255) {
722
-					throw new OCSBadRequestException('Maximum label length is 255');
723
-				}
724
-				$share->setLabel($label);
725
-			}
726
-
727
-			if ($sendPasswordByTalk === 'true') {
728
-				if (!$this->appManager->isEnabledForUser('spreed')) {
729
-					throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$node->getPath()]));
730
-				}
731
-
732
-				$share->setSendPasswordByTalk(true);
733
-			}
734
-		} elseif ($shareType === IShare::TYPE_REMOTE) {
735
-			if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
736
-				throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
737
-			}
738
-
739
-			if ($shareWith === null) {
740
-				throw new OCSNotFoundException($this->l->t('Please specify a valid federated account ID'));
741
-			}
742
-
743
-			$share->setSharedWith($shareWith);
744
-			$share->setPermissions($permissions);
745
-			$share->setSharedWithDisplayName($this->getCachedFederatedDisplayName($shareWith, false));
746
-		} elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
747
-			if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
748
-				throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
749
-			}
750
-
751
-			if ($shareWith === null) {
752
-				throw new OCSNotFoundException($this->l->t('Please specify a valid federated group ID'));
753
-			}
754
-
755
-			$share->setSharedWith($shareWith);
756
-			$share->setPermissions($permissions);
757
-		} elseif ($shareType === IShare::TYPE_CIRCLE) {
758
-			if (!Server::get(IAppManager::class)->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
759
-				throw new OCSNotFoundException($this->l->t('You cannot share to a Team if the app is not enabled'));
760
-			}
761
-
762
-			$circle = Circles::detailsCircle($shareWith);
763
-
764
-			// Valid team is required to share
765
-			if ($circle === null) {
766
-				throw new OCSNotFoundException($this->l->t('Please specify a valid team'));
767
-			}
768
-			$share->setSharedWith($shareWith);
769
-			$share->setPermissions($permissions);
770
-		} elseif ($shareType === IShare::TYPE_ROOM) {
771
-			try {
772
-				$this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
773
-			} catch (ContainerExceptionInterface $e) {
774
-				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
775
-			}
776
-		} elseif ($shareType === IShare::TYPE_DECK) {
777
-			try {
778
-				$this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
779
-			} catch (ContainerExceptionInterface $e) {
780
-				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
781
-			}
782
-		} elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
783
-			try {
784
-				$this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
785
-			} catch (ContainerExceptionInterface $e) {
786
-				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support ScienceMesh shares', [$node->getPath()]));
787
-			}
788
-		} else {
789
-			throw new OCSBadRequestException($this->l->t('Unknown share type'));
790
-		}
791
-
792
-		$share->setShareType($shareType);
793
-		$this->checkInheritedAttributes($share);
794
-
795
-		if ($note !== '') {
796
-			$share->setNote($note);
797
-		}
798
-
799
-		try {
800
-			$share = $this->shareManager->createShare($share);
801
-		} catch (HintException $e) {
802
-			$code = $e->getCode() === 0 ? 403 : $e->getCode();
803
-			throw new OCSException($e->getHint(), $code);
804
-		} catch (GenericShareException|\InvalidArgumentException $e) {
805
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
806
-			throw new OCSForbiddenException($e->getMessage(), $e);
807
-		} catch (\Exception $e) {
808
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
809
-			throw new OCSForbiddenException('Failed to create share.', $e);
810
-		}
811
-
812
-		$output = $this->formatShare($share);
813
-
814
-		return new DataResponse($output);
815
-	}
816
-
817
-	/**
818
-	 * @param null|Node $node
819
-	 * @param boolean $includeTags
820
-	 *
821
-	 * @return list<Files_SharingShare>
822
-	 */
823
-	private function getSharedWithMe($node, bool $includeTags): array {
824
-		$userShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_USER, $node, -1, 0);
825
-		$groupShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_GROUP, $node, -1, 0);
826
-		$circleShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_CIRCLE, $node, -1, 0);
827
-		$roomShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_ROOM, $node, -1, 0);
828
-		$deckShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_DECK, $node, -1, 0);
829
-		$sciencemeshShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_SCIENCEMESH, $node, -1, 0);
830
-
831
-		$shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares);
832
-
833
-		$filteredShares = array_filter($shares, function (IShare $share) {
834
-			return $share->getShareOwner() !== $this->userId;
835
-		});
836
-
837
-		$formatted = [];
838
-		foreach ($filteredShares as $share) {
839
-			if ($this->canAccessShare($share)) {
840
-				try {
841
-					$formatted[] = $this->formatShare($share);
842
-				} catch (NotFoundException $e) {
843
-					// Ignore this share
844
-				}
845
-			}
846
-		}
847
-
848
-		if ($includeTags) {
849
-			$formatted = Helper::populateTags($formatted, Server::get(ITagManager::class));
850
-		}
851
-
852
-		return $formatted;
853
-	}
854
-
855
-	/**
856
-	 * @param Node $folder
857
-	 *
858
-	 * @return list<Files_SharingShare>
859
-	 * @throws OCSBadRequestException
860
-	 * @throws NotFoundException
861
-	 */
862
-	private function getSharesInDir(Node $folder): array {
863
-		if (!($folder instanceof Folder)) {
864
-			throw new OCSBadRequestException($this->l->t('Not a directory'));
865
-		}
866
-
867
-		$nodes = $folder->getDirectoryListing();
868
-
869
-		/** @var IShare[] $shares */
870
-		$shares = array_reduce($nodes, function ($carry, $node) {
871
-			$carry = array_merge($carry, $this->getAllShares($node, true));
872
-			return $carry;
873
-		}, []);
874
-
875
-		// filter out duplicate shares
876
-		$known = [];
877
-
878
-		$formatted = $miniFormatted = [];
879
-		$resharingRight = false;
880
-		$known = [];
881
-		foreach ($shares as $share) {
882
-			if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->userId) {
883
-				continue;
884
-			}
885
-
886
-			try {
887
-				$format = $this->formatShare($share);
888
-
889
-				$known[] = $share->getId();
890
-				$formatted[] = $format;
891
-				if ($share->getSharedBy() === $this->userId) {
892
-					$miniFormatted[] = $format;
893
-				}
894
-				if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $folder)) {
895
-					$resharingRight = true;
896
-				}
897
-			} catch (\Exception $e) {
898
-				//Ignore this share
899
-			}
900
-		}
901
-
902
-		if (!$resharingRight) {
903
-			$formatted = $miniFormatted;
904
-		}
905
-
906
-		return $formatted;
907
-	}
908
-
909
-	/**
910
-	 * Get shares of the current user
911
-	 *
912
-	 * @param string $shared_with_me Only get shares with the current user
913
-	 * @param string $reshares Only get shares by the current user and reshares
914
-	 * @param string $subfiles Only get all shares in a folder
915
-	 * @param string $path Get shares for a specific path
916
-	 * @param string $include_tags Include tags in the share
917
-	 *
918
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
919
-	 * @throws OCSNotFoundException The folder was not found or is inaccessible
920
-	 *
921
-	 * 200: Shares returned
922
-	 */
923
-	#[NoAdminRequired]
924
-	public function getShares(
925
-		string $shared_with_me = 'false',
926
-		string $reshares = 'false',
927
-		string $subfiles = 'false',
928
-		string $path = '',
929
-		string $include_tags = 'false',
930
-	): DataResponse {
931
-		$node = null;
932
-		if ($path !== '') {
933
-			$userFolder = $this->rootFolder->getUserFolder($this->userId);
934
-			try {
935
-				$node = $userFolder->get($path);
936
-				$this->lock($node);
937
-			} catch (NotFoundException $e) {
938
-				throw new OCSNotFoundException(
939
-					$this->l->t('Wrong path, file/folder does not exist')
940
-				);
941
-			} catch (LockedException $e) {
942
-				throw new OCSNotFoundException($this->l->t('Could not lock node'));
943
-			}
944
-		}
945
-
946
-		$shares = $this->getFormattedShares(
947
-			$this->userId,
948
-			$node,
949
-			($shared_with_me === 'true'),
950
-			($reshares === 'true'),
951
-			($subfiles === 'true'),
952
-			($include_tags === 'true')
953
-		);
954
-
955
-		return new DataResponse($shares);
956
-	}
957
-
958
-	private function getLinkSharePermissions(?int $permissions, ?bool $legacyPublicUpload): int {
959
-		$permissions = $permissions ?? Constants::PERMISSION_READ;
960
-
961
-		// Legacy option handling
962
-		if ($legacyPublicUpload !== null) {
963
-			$permissions = $legacyPublicUpload
964
-				? (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
965
-				: Constants::PERMISSION_READ;
966
-		}
967
-
968
-		// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
969
-		if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
970
-			&& $this->shareManager->outgoingServer2ServerSharesAllowed()) {
971
-			$permissions |= Constants::PERMISSION_SHARE;
972
-		}
973
-
974
-		return $permissions;
975
-	}
976
-
977
-	/**
978
-	 * Helper to check for legacy "publicUpload" handling.
979
-	 * If the value is set to `true` or `false` then true or false are returned.
980
-	 * Otherwise null is returned to indicate that the option was not (or wrong) set.
981
-	 *
982
-	 * @param null|string $legacyPublicUpload The value of `publicUpload`
983
-	 */
984
-	private function getLegacyPublicUpload(?string $legacyPublicUpload): ?bool {
985
-		if ($legacyPublicUpload === 'true') {
986
-			return true;
987
-		} elseif ($legacyPublicUpload === 'false') {
988
-			return false;
989
-		}
990
-		// Not set at all
991
-		return null;
992
-	}
993
-
994
-	/**
995
-	 * For link and email shares validate that only allowed combinations are set.
996
-	 *
997
-	 * @throw OCSBadRequestException If permission combination is invalid.
998
-	 * @throw OCSForbiddenException If public upload was forbidden by the administrator.
999
-	 */
1000
-	private function validateLinkSharePermissions(Node $node, int $permissions, ?bool $legacyPublicUpload): void {
1001
-		if ($legacyPublicUpload && ($node instanceof File)) {
1002
-			throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1003
-		}
1004
-
1005
-		// We need at least READ or CREATE (file drop)
1006
-		if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1007
-			&& !$this->hasPermission($permissions, Constants::PERMISSION_CREATE)) {
1008
-			throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
1009
-		}
1010
-
1011
-		// UPDATE and DELETE require a READ permission
1012
-		if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1013
-			&& ($this->hasPermission($permissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($permissions, Constants::PERMISSION_DELETE))) {
1014
-			throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
1015
-		}
1016
-
1017
-		// Check if public uploading was disabled
1018
-		if ($this->hasPermission($permissions, Constants::PERMISSION_CREATE)
1019
-			&& !$this->shareManager->shareApiLinkAllowPublicUpload()) {
1020
-			throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1021
-		}
1022
-	}
1023
-
1024
-	/**
1025
-	 * @param string $viewer
1026
-	 * @param Node $node
1027
-	 * @param bool $sharedWithMe
1028
-	 * @param bool $reShares
1029
-	 * @param bool $subFiles
1030
-	 * @param bool $includeTags
1031
-	 *
1032
-	 * @return list<Files_SharingShare>
1033
-	 * @throws NotFoundException
1034
-	 * @throws OCSBadRequestException
1035
-	 */
1036
-	private function getFormattedShares(
1037
-		string $viewer,
1038
-		$node = null,
1039
-		bool $sharedWithMe = false,
1040
-		bool $reShares = false,
1041
-		bool $subFiles = false,
1042
-		bool $includeTags = false,
1043
-	): array {
1044
-		if ($sharedWithMe) {
1045
-			return $this->getSharedWithMe($node, $includeTags);
1046
-		}
1047
-
1048
-		if ($subFiles) {
1049
-			return $this->getSharesInDir($node);
1050
-		}
1051
-
1052
-		$shares = $this->getSharesFromNode($viewer, $node, $reShares);
1053
-
1054
-		$known = $formatted = $miniFormatted = [];
1055
-		$resharingRight = false;
1056
-		foreach ($shares as $share) {
1057
-			try {
1058
-				$share->getNode();
1059
-			} catch (NotFoundException $e) {
1060
-				/*
74
+    private ?Node $lockedNode = null;
75
+
76
+    /**
77
+     * Share20OCS constructor.
78
+     */
79
+    public function __construct(
80
+        string $appName,
81
+        IRequest $request,
82
+        private IManager $shareManager,
83
+        private IGroupManager $groupManager,
84
+        private IUserManager $userManager,
85
+        private IRootFolder $rootFolder,
86
+        private IURLGenerator $urlGenerator,
87
+        private IL10N $l,
88
+        private IConfig $config,
89
+        private IAppManager $appManager,
90
+        private ContainerInterface $serverContainer,
91
+        private IUserStatusManager $userStatusManager,
92
+        private IPreview $previewManager,
93
+        private IDateTimeZone $dateTimeZone,
94
+        private LoggerInterface $logger,
95
+        private IProviderFactory $factory,
96
+        private IMailer $mailer,
97
+        private ?string $userId = null,
98
+    ) {
99
+        parent::__construct($appName, $request);
100
+    }
101
+
102
+    /**
103
+     * Convert an IShare to an array for OCS output
104
+     *
105
+     * @param IShare $share
106
+     * @param Node|null $recipientNode
107
+     * @return Files_SharingShare
108
+     * @throws NotFoundException In case the node can't be resolved.
109
+     *
110
+     * @suppress PhanUndeclaredClassMethod
111
+     */
112
+    protected function formatShare(IShare $share, ?Node $recipientNode = null): array {
113
+        $sharedBy = $this->userManager->get($share->getSharedBy());
114
+        $shareOwner = $this->userManager->get($share->getShareOwner());
115
+
116
+        $isOwnShare = false;
117
+        if ($shareOwner !== null) {
118
+            $isOwnShare = $shareOwner->getUID() === $this->userId;
119
+        }
120
+
121
+        $result = [
122
+            'id' => $share->getId(),
123
+            'share_type' => $share->getShareType(),
124
+            'uid_owner' => $share->getSharedBy(),
125
+            'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
126
+            // recipient permissions
127
+            'permissions' => $share->getPermissions(),
128
+            // current user permissions on this share
129
+            'can_edit' => $this->canEditShare($share),
130
+            'can_delete' => $this->canDeleteShare($share),
131
+            'stime' => $share->getShareTime()->getTimestamp(),
132
+            'parent' => null,
133
+            'expiration' => null,
134
+            'token' => null,
135
+            'uid_file_owner' => $share->getShareOwner(),
136
+            'note' => $share->getNote(),
137
+            'label' => $share->getLabel(),
138
+            'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
139
+        ];
140
+
141
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
142
+        if ($recipientNode) {
143
+            $node = $recipientNode;
144
+        } else {
145
+            $node = $userFolder->getFirstNodeById($share->getNodeId());
146
+            if (!$node) {
147
+                // fallback to guessing the path
148
+                $node = $userFolder->get($share->getTarget());
149
+                if ($node === null || $share->getTarget() === '') {
150
+                    throw new NotFoundException();
151
+                }
152
+            }
153
+        }
154
+
155
+        $result['path'] = $userFolder->getRelativePath($node->getPath());
156
+        if ($node instanceof Folder) {
157
+            $result['item_type'] = 'folder';
158
+        } else {
159
+            $result['item_type'] = 'file';
160
+        }
161
+
162
+        // Get the original node permission if the share owner is the current user
163
+        if ($isOwnShare) {
164
+            $result['item_permissions'] = $node->getPermissions();
165
+        }
166
+
167
+        // If we're on the recipient side, the node permissions
168
+        // are bound to the share permissions. So we need to
169
+        // adjust the permissions to the share permissions if necessary.
170
+        if (!$isOwnShare) {
171
+            $result['item_permissions'] = $share->getPermissions();
172
+
173
+            // For some reason, single files share are forbidden to have the delete permission
174
+            // since we have custom methods to check those, let's adjust straight away.
175
+            // DAV permissions does not have that issue though.
176
+            if ($this->canDeleteShare($share) || $this->canDeleteShareFromSelf($share)) {
177
+                $result['item_permissions'] |= Constants::PERMISSION_DELETE;
178
+            }
179
+            if ($this->canEditShare($share)) {
180
+                $result['item_permissions'] |= Constants::PERMISSION_UPDATE;
181
+            }
182
+        }
183
+
184
+        // See MOUNT_ROOT_PROPERTYNAME dav property
185
+        $result['is-mount-root'] = $node->getInternalPath() === '';
186
+        $result['mount-type'] = $node->getMountPoint()->getMountType();
187
+
188
+        $result['mimetype'] = $node->getMimetype();
189
+        $result['has_preview'] = $this->previewManager->isAvailable($node);
190
+        $result['storage_id'] = $node->getStorage()->getId();
191
+        $result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
192
+        $result['item_source'] = $node->getId();
193
+        $result['file_source'] = $node->getId();
194
+        $result['file_parent'] = $node->getParent()->getId();
195
+        $result['file_target'] = $share->getTarget();
196
+        $result['item_size'] = $node->getSize();
197
+        $result['item_mtime'] = $node->getMTime();
198
+
199
+        $expiration = $share->getExpirationDate();
200
+        if ($expiration !== null) {
201
+            $expiration->setTimezone($this->dateTimeZone->getTimeZone());
202
+            $result['expiration'] = $expiration->format('Y-m-d 00:00:00');
203
+        }
204
+
205
+        if ($share->getShareType() === IShare::TYPE_USER) {
206
+            $sharedWith = $this->userManager->get($share->getSharedWith());
207
+            $result['share_with'] = $share->getSharedWith();
208
+            $result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
209
+            $result['share_with_displayname_unique'] = $sharedWith !== null ? (
210
+                !empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
211
+            ) : $share->getSharedWith();
212
+
213
+            $userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
214
+            $userStatus = array_shift($userStatuses);
215
+            if ($userStatus) {
216
+                $result['status'] = [
217
+                    'status' => $userStatus->getStatus(),
218
+                    'message' => $userStatus->getMessage(),
219
+                    'icon' => $userStatus->getIcon(),
220
+                    'clearAt' => $userStatus->getClearAt()
221
+                        ? (int)$userStatus->getClearAt()->format('U')
222
+                        : null,
223
+                ];
224
+            }
225
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
226
+            $group = $this->groupManager->get($share->getSharedWith());
227
+            $result['share_with'] = $share->getSharedWith();
228
+            $result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
229
+        } elseif ($share->getShareType() === IShare::TYPE_LINK) {
230
+
231
+            // "share_with" and "share_with_displayname" for passwords of link
232
+            // shares was deprecated in Nextcloud 15, use "password" instead.
233
+            $result['share_with'] = $share->getPassword();
234
+            $result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
235
+
236
+            $result['password'] = $share->getPassword();
237
+
238
+            $result['send_password_by_talk'] = $share->getSendPasswordByTalk();
239
+
240
+            $result['token'] = $share->getToken();
241
+            $result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
242
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
243
+            $result['share_with'] = $share->getSharedWith();
244
+            $result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith());
245
+            $result['token'] = $share->getToken();
246
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
247
+            $result['share_with'] = $share->getSharedWith();
248
+            $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
249
+            $result['token'] = $share->getToken();
250
+        } elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
251
+            $result['share_with'] = $share->getSharedWith();
252
+            $result['password'] = $share->getPassword();
253
+            $result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
254
+            $result['send_password_by_talk'] = $share->getSendPasswordByTalk();
255
+            $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
256
+            $result['token'] = $share->getToken();
257
+        } elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
258
+            // getSharedWith() returns either "name (type, owner)" or
259
+            // "name (type, owner) [id]", depending on the Teams app version.
260
+            $hasCircleId = (substr($share->getSharedWith(), -1) === ']');
261
+
262
+            $result['share_with_displayname'] = $share->getSharedWithDisplayName();
263
+            if (empty($result['share_with_displayname'])) {
264
+                $displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
265
+                $result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
266
+            }
267
+
268
+            $result['share_with_avatar'] = $share->getSharedWithAvatar();
269
+
270
+            $shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
271
+            $shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
272
+            if ($shareWithLength === false) {
273
+                $result['share_with'] = substr($share->getSharedWith(), $shareWithStart);
274
+            } else {
275
+                $result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
276
+            }
277
+        } elseif ($share->getShareType() === IShare::TYPE_ROOM) {
278
+            $result['share_with'] = $share->getSharedWith();
279
+            $result['share_with_displayname'] = '';
280
+
281
+            try {
282
+                /** @var array{share_with_displayname: string, share_with_link: string, share_with?: string, token?: string} $roomShare */
283
+                $roomShare = $this->getRoomShareHelper()->formatShare($share);
284
+                $result = array_merge($result, $roomShare);
285
+            } catch (ContainerExceptionInterface $e) {
286
+            }
287
+        } elseif ($share->getShareType() === IShare::TYPE_DECK) {
288
+            $result['share_with'] = $share->getSharedWith();
289
+            $result['share_with_displayname'] = '';
290
+
291
+            try {
292
+                /** @var array{share_with: string, share_with_displayname: string, share_with_link: string} $deckShare */
293
+                $deckShare = $this->getDeckShareHelper()->formatShare($share);
294
+                $result = array_merge($result, $deckShare);
295
+            } catch (ContainerExceptionInterface $e) {
296
+            }
297
+        } elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
298
+            $result['share_with'] = $share->getSharedWith();
299
+            $result['share_with_displayname'] = '';
300
+
301
+            try {
302
+                /** @var array{share_with: string, share_with_displayname: string, token: string} $scienceMeshShare */
303
+                $scienceMeshShare = $this->getSciencemeshShareHelper()->formatShare($share);
304
+                $result = array_merge($result, $scienceMeshShare);
305
+            } catch (ContainerExceptionInterface $e) {
306
+            }
307
+        }
308
+
309
+
310
+        $result['mail_send'] = $share->getMailSend() ? 1 : 0;
311
+        $result['hide_download'] = $share->getHideDownload() ? 1 : 0;
312
+
313
+        $result['attributes'] = null;
314
+        if ($attributes = $share->getAttributes()) {
315
+            $result['attributes'] = (string)\json_encode($attributes->toArray());
316
+        }
317
+
318
+        return $result;
319
+    }
320
+
321
+    /**
322
+     * Check if one of the users address books knows the exact property, if
323
+     * not we return the full name.
324
+     *
325
+     * @param string $query
326
+     * @param string $property
327
+     * @return string
328
+     */
329
+    private function getDisplayNameFromAddressBook(string $query, string $property): string {
330
+        // FIXME: If we inject the contacts manager it gets initialized before any address books are registered
331
+        try {
332
+            $result = Server::get(\OCP\Contacts\IManager::class)->search($query, [$property], [
333
+                'limit' => 1,
334
+                'enumeration' => false,
335
+                'strict_search' => true,
336
+            ]);
337
+        } catch (Exception $e) {
338
+            $this->logger->error(
339
+                $e->getMessage(),
340
+                ['exception' => $e]
341
+            );
342
+            return $query;
343
+        }
344
+
345
+        foreach ($result as $r) {
346
+            foreach ($r[$property] as $value) {
347
+                if ($value === $query && $r['FN']) {
348
+                    return $r['FN'];
349
+                }
350
+            }
351
+        }
352
+
353
+        return $query;
354
+    }
355
+
356
+
357
+    /**
358
+     * @param list<Files_SharingShare> $shares
359
+     * @param array<string, string>|null $updatedDisplayName
360
+     *
361
+     * @return list<Files_SharingShare>
362
+     */
363
+    private function fixMissingDisplayName(array $shares, ?array $updatedDisplayName = null): array {
364
+        $userIds = $updated = [];
365
+        foreach ($shares as $share) {
366
+            // share is federated and share have no display name yet
367
+            if ($share['share_type'] === IShare::TYPE_REMOTE
368
+                && ($share['share_with'] ?? '') !== ''
369
+                && ($share['share_with_displayname'] ?? '') === '') {
370
+                $userIds[] = $userId = $share['share_with'];
371
+
372
+                if ($updatedDisplayName !== null && array_key_exists($userId, $updatedDisplayName)) {
373
+                    $share['share_with_displayname'] = $updatedDisplayName[$userId];
374
+                }
375
+            }
376
+
377
+            // prepping userIds with displayName to be updated
378
+            $updated[] = $share;
379
+        }
380
+
381
+        // if $updatedDisplayName is not null, it means we should have already fixed displayNames of the shares
382
+        if ($updatedDisplayName !== null) {
383
+            return $updated;
384
+        }
385
+
386
+        // get displayName for the generated list of userId with no displayName
387
+        $displayNames = $this->retrieveFederatedDisplayName($userIds);
388
+
389
+        // if no displayName are updated, we exit
390
+        if (empty($displayNames)) {
391
+            return $updated;
392
+        }
393
+
394
+        // let's fix missing display name and returns all shares
395
+        return $this->fixMissingDisplayName($shares, $displayNames);
396
+    }
397
+
398
+
399
+    /**
400
+     * get displayName of a list of userIds from the lookup-server; through the globalsiteselector app.
401
+     * returns an array with userIds as keys and displayName as values.
402
+     *
403
+     * @param array $userIds
404
+     * @param bool $cacheOnly - do not reach LUS, get data from cache.
405
+     *
406
+     * @return array
407
+     * @throws ContainerExceptionInterface
408
+     */
409
+    private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array {
410
+        // check if gss is enabled and available
411
+        if (count($userIds) === 0
412
+            || !$this->appManager->isEnabledForAnyone('globalsiteselector')
413
+            || !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
414
+            return [];
415
+        }
416
+
417
+        try {
418
+            $slaveService = Server::get(SlaveService::class);
419
+        } catch (\Throwable $e) {
420
+            $this->logger->error(
421
+                $e->getMessage(),
422
+                ['exception' => $e]
423
+            );
424
+            return [];
425
+        }
426
+
427
+        return $slaveService->getUsersDisplayName($userIds, $cacheOnly);
428
+    }
429
+
430
+
431
+    /**
432
+     * retrieve displayName from cache if available (should be used on federated shares)
433
+     * if not available in cache/lus, try for get from address-book, else returns empty string.
434
+     *
435
+     * @param string $userId
436
+     * @param bool $cacheOnly if true will not reach the lus but will only get data from cache
437
+     *
438
+     * @return string
439
+     */
440
+    private function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = true): string {
441
+        $details = $this->retrieveFederatedDisplayName([$userId], $cacheOnly);
442
+        if (array_key_exists($userId, $details)) {
443
+            return $details[$userId];
444
+        }
445
+
446
+        $displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD');
447
+        return ($displayName === $userId) ? '' : $displayName;
448
+    }
449
+
450
+
451
+
452
+    /**
453
+     * Get a specific share by id
454
+     *
455
+     * @param string $id ID of the share
456
+     * @param bool $include_tags Include tags in the share
457
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
458
+     * @throws OCSNotFoundException Share not found
459
+     *
460
+     * 200: Share returned
461
+     */
462
+    #[NoAdminRequired]
463
+    public function getShare(string $id, bool $include_tags = false): DataResponse {
464
+        try {
465
+            $share = $this->getShareById($id);
466
+        } catch (ShareNotFound $e) {
467
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
468
+        }
469
+
470
+        try {
471
+            if ($this->canAccessShare($share)) {
472
+                $share = $this->formatShare($share);
473
+
474
+                if ($include_tags) {
475
+                    $share = Helper::populateTags([$share], Server::get(ITagManager::class));
476
+                } else {
477
+                    $share = [$share];
478
+                }
479
+
480
+                return new DataResponse($share);
481
+            }
482
+        } catch (NotFoundException $e) {
483
+            // Fall through
484
+        }
485
+
486
+        throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
487
+    }
488
+
489
+    /**
490
+     * Delete a share
491
+     *
492
+     * @param string $id ID of the share
493
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
494
+     * @throws OCSNotFoundException Share not found
495
+     * @throws OCSForbiddenException Missing permissions to delete the share
496
+     *
497
+     * 200: Share deleted successfully
498
+     */
499
+    #[NoAdminRequired]
500
+    public function deleteShare(string $id): DataResponse {
501
+        try {
502
+            $share = $this->getShareById($id);
503
+        } catch (ShareNotFound $e) {
504
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
505
+        }
506
+
507
+        try {
508
+            $this->lock($share->getNode());
509
+        } catch (LockedException $e) {
510
+            throw new OCSNotFoundException($this->l->t('Could not delete share'));
511
+        }
512
+
513
+        if (!$this->canAccessShare($share)) {
514
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
515
+        }
516
+
517
+        // if it's a group share or a room share
518
+        // we don't delete the share, but only the
519
+        // mount point. Allowing it to be restored
520
+        // from the deleted shares
521
+        if ($this->canDeleteShareFromSelf($share)) {
522
+            $this->shareManager->deleteFromSelf($share, $this->userId);
523
+        } else {
524
+            if (!$this->canDeleteShare($share)) {
525
+                throw new OCSForbiddenException($this->l->t('Could not delete share'));
526
+            }
527
+
528
+            $this->shareManager->deleteShare($share);
529
+        }
530
+
531
+        return new DataResponse();
532
+    }
533
+
534
+    /**
535
+     * Create a share
536
+     *
537
+     * @param string|null $path Path of the share
538
+     * @param int|null $permissions Permissions for the share
539
+     * @param int $shareType Type of the share
540
+     * @param ?string $shareWith The entity this should be shared with
541
+     * @param 'true'|'false'|null $publicUpload If public uploading is allowed (deprecated)
542
+     * @param string $password Password for the share
543
+     * @param string|null $sendPasswordByTalk Send the password for the share over Talk
544
+     * @param ?string $expireDate The expiry date of the share in the user's timezone at 00:00.
545
+     *                            If $expireDate is not supplied or set to `null`, the system default will be used.
546
+     * @param string $note Note for the share
547
+     * @param string $label Label for the share (only used in link and email)
548
+     * @param string|null $attributes Additional attributes for the share
549
+     * @param 'false'|'true'|null $sendMail Send a mail to the recipient
550
+     *
551
+     * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
552
+     * @throws OCSBadRequestException Unknown share type
553
+     * @throws OCSException
554
+     * @throws OCSForbiddenException Creating the share is not allowed
555
+     * @throws OCSNotFoundException Creating the share failed
556
+     * @suppress PhanUndeclaredClassMethod
557
+     *
558
+     * 200: Share created
559
+     */
560
+    #[NoAdminRequired]
561
+    public function createShare(
562
+        ?string $path = null,
563
+        ?int $permissions = null,
564
+        int $shareType = -1,
565
+        ?string $shareWith = null,
566
+        ?string $publicUpload = null,
567
+        string $password = '',
568
+        ?string $sendPasswordByTalk = null,
569
+        ?string $expireDate = null,
570
+        string $note = '',
571
+        string $label = '',
572
+        ?string $attributes = null,
573
+        ?string $sendMail = null,
574
+    ): DataResponse {
575
+        assert($this->userId !== null);
576
+
577
+        $share = $this->shareManager->newShare();
578
+        $hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
579
+
580
+        // Verify path
581
+        if ($path === null) {
582
+            throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
583
+        }
584
+
585
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
586
+        try {
587
+            /** @var \OC\Files\Node\Node $node */
588
+            $node = $userFolder->get($path);
589
+        } catch (NotFoundException $e) {
590
+            throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
591
+        }
592
+
593
+        // a user can have access to a file through different paths, with differing permissions
594
+        // combine all permissions to determine if the user can share this file
595
+        $nodes = $userFolder->getById($node->getId());
596
+        foreach ($nodes as $nodeById) {
597
+            /** @var \OC\Files\FileInfo $fileInfo */
598
+            $fileInfo = $node->getFileInfo();
599
+            $fileInfo['permissions'] |= $nodeById->getPermissions();
600
+        }
601
+
602
+        $share->setNode($node);
603
+
604
+        try {
605
+            $this->lock($share->getNode());
606
+        } catch (LockedException $e) {
607
+            throw new OCSNotFoundException($this->l->t('Could not create share'));
608
+        }
609
+
610
+        // Set permissions
611
+        if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_EMAIL) {
612
+            $permissions = $this->getLinkSharePermissions($permissions, $hasPublicUpload);
613
+            $this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
614
+        } else {
615
+            // Use default permissions only for non-link shares to keep legacy behavior
616
+            if ($permissions === null) {
617
+                $permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
618
+            }
619
+            // Non-link shares always require read permissions (link shares could be file drop)
620
+            $permissions |= Constants::PERMISSION_READ;
621
+        }
622
+
623
+        // For legacy reasons the API allows to pass PERMISSIONS_ALL even for single file shares (I look at you Talk)
624
+        if ($node instanceof File) {
625
+            // if this is a single file share we remove the DELETE and CREATE permissions
626
+            $permissions = $permissions & ~(Constants::PERMISSION_DELETE | Constants::PERMISSION_CREATE);
627
+        }
628
+
629
+        /**
630
+         * Hack for https://github.com/owncloud/core/issues/22587
631
+         * We check the permissions via webdav. But the permissions of the mount point
632
+         * do not equal the share permissions. Here we fix that for federated mounts.
633
+         */
634
+        if ($node->getStorage()->instanceOfStorage(Storage::class)) {
635
+            $permissions &= ~($permissions & ~$node->getPermissions());
636
+        }
637
+
638
+        if ($attributes !== null) {
639
+            $share = $this->setShareAttributes($share, $attributes);
640
+        }
641
+
642
+        // Expire date checks
643
+        // Normally, null means no expiration date but we still set the default for backwards compatibility
644
+        // If the client sends an empty string, we set noExpirationDate to true
645
+        if ($expireDate !== null) {
646
+            if ($expireDate !== '') {
647
+                try {
648
+                    $expireDateTime = $this->parseDate($expireDate);
649
+                    $share->setExpirationDate($expireDateTime);
650
+                } catch (\Exception $e) {
651
+                    throw new OCSNotFoundException($e->getMessage(), $e);
652
+                }
653
+            } else {
654
+                // Client sent empty string for expire date.
655
+                // Set noExpirationDate to true so overwrite is prevented.
656
+                $share->setNoExpirationDate(true);
657
+            }
658
+        }
659
+
660
+        $share->setSharedBy($this->userId);
661
+
662
+        // Handle mail send
663
+        if (is_null($sendMail)) {
664
+            $allowSendMail = $this->config->getSystemValueBool('sharing.enable_share_mail', true);
665
+            if ($allowSendMail !== true || $shareType === IShare::TYPE_EMAIL) {
666
+                // Define a default behavior when sendMail is not provided
667
+                // For email shares with a valid recipient, the default is to send the mail
668
+                // For all other share types, the default is to not send the mail
669
+                $allowSendMail = ($shareType === IShare::TYPE_EMAIL && $shareWith !== null && $shareWith !== '');
670
+            }
671
+            $share->setMailSend($allowSendMail);
672
+        } else {
673
+            $share->setMailSend($sendMail === 'true');
674
+        }
675
+
676
+        if ($shareType === IShare::TYPE_USER) {
677
+            // Valid user is required to share
678
+            if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
679
+                throw new OCSNotFoundException($this->l->t('Please specify a valid account to share with'));
680
+            }
681
+            $share->setSharedWith($shareWith);
682
+            $share->setPermissions($permissions);
683
+        } elseif ($shareType === IShare::TYPE_GROUP) {
684
+            if (!$this->shareManager->allowGroupSharing()) {
685
+                throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
686
+            }
687
+
688
+            // Valid group is required to share
689
+            if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
690
+                throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
691
+            }
692
+            $share->setSharedWith($shareWith);
693
+            $share->setPermissions($permissions);
694
+        } elseif ($shareType === IShare::TYPE_LINK
695
+            || $shareType === IShare::TYPE_EMAIL) {
696
+
697
+            // Can we even share links?
698
+            if (!$this->shareManager->shareApiAllowLinks()) {
699
+                throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
700
+            }
701
+
702
+            $this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
703
+            $share->setPermissions($permissions);
704
+
705
+            // Set password
706
+            if ($password !== '') {
707
+                $share->setPassword($password);
708
+            }
709
+
710
+            // Only share by mail have a recipient
711
+            if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
712
+                // If sending a mail have been requested, validate the mail address
713
+                if ($share->getMailSend() && !$this->mailer->validateMailAddress($shareWith)) {
714
+                    throw new OCSNotFoundException($this->l->t('Please specify a valid email address'));
715
+                }
716
+                $share->setSharedWith($shareWith);
717
+            }
718
+
719
+            // If we have a label, use it
720
+            if ($label !== '') {
721
+                if (strlen($label) > 255) {
722
+                    throw new OCSBadRequestException('Maximum label length is 255');
723
+                }
724
+                $share->setLabel($label);
725
+            }
726
+
727
+            if ($sendPasswordByTalk === 'true') {
728
+                if (!$this->appManager->isEnabledForUser('spreed')) {
729
+                    throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$node->getPath()]));
730
+                }
731
+
732
+                $share->setSendPasswordByTalk(true);
733
+            }
734
+        } elseif ($shareType === IShare::TYPE_REMOTE) {
735
+            if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
736
+                throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
737
+            }
738
+
739
+            if ($shareWith === null) {
740
+                throw new OCSNotFoundException($this->l->t('Please specify a valid federated account ID'));
741
+            }
742
+
743
+            $share->setSharedWith($shareWith);
744
+            $share->setPermissions($permissions);
745
+            $share->setSharedWithDisplayName($this->getCachedFederatedDisplayName($shareWith, false));
746
+        } elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
747
+            if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
748
+                throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
749
+            }
750
+
751
+            if ($shareWith === null) {
752
+                throw new OCSNotFoundException($this->l->t('Please specify a valid federated group ID'));
753
+            }
754
+
755
+            $share->setSharedWith($shareWith);
756
+            $share->setPermissions($permissions);
757
+        } elseif ($shareType === IShare::TYPE_CIRCLE) {
758
+            if (!Server::get(IAppManager::class)->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
759
+                throw new OCSNotFoundException($this->l->t('You cannot share to a Team if the app is not enabled'));
760
+            }
761
+
762
+            $circle = Circles::detailsCircle($shareWith);
763
+
764
+            // Valid team is required to share
765
+            if ($circle === null) {
766
+                throw new OCSNotFoundException($this->l->t('Please specify a valid team'));
767
+            }
768
+            $share->setSharedWith($shareWith);
769
+            $share->setPermissions($permissions);
770
+        } elseif ($shareType === IShare::TYPE_ROOM) {
771
+            try {
772
+                $this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
773
+            } catch (ContainerExceptionInterface $e) {
774
+                throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
775
+            }
776
+        } elseif ($shareType === IShare::TYPE_DECK) {
777
+            try {
778
+                $this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
779
+            } catch (ContainerExceptionInterface $e) {
780
+                throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
781
+            }
782
+        } elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
783
+            try {
784
+                $this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
785
+            } catch (ContainerExceptionInterface $e) {
786
+                throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support ScienceMesh shares', [$node->getPath()]));
787
+            }
788
+        } else {
789
+            throw new OCSBadRequestException($this->l->t('Unknown share type'));
790
+        }
791
+
792
+        $share->setShareType($shareType);
793
+        $this->checkInheritedAttributes($share);
794
+
795
+        if ($note !== '') {
796
+            $share->setNote($note);
797
+        }
798
+
799
+        try {
800
+            $share = $this->shareManager->createShare($share);
801
+        } catch (HintException $e) {
802
+            $code = $e->getCode() === 0 ? 403 : $e->getCode();
803
+            throw new OCSException($e->getHint(), $code);
804
+        } catch (GenericShareException|\InvalidArgumentException $e) {
805
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
806
+            throw new OCSForbiddenException($e->getMessage(), $e);
807
+        } catch (\Exception $e) {
808
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
809
+            throw new OCSForbiddenException('Failed to create share.', $e);
810
+        }
811
+
812
+        $output = $this->formatShare($share);
813
+
814
+        return new DataResponse($output);
815
+    }
816
+
817
+    /**
818
+     * @param null|Node $node
819
+     * @param boolean $includeTags
820
+     *
821
+     * @return list<Files_SharingShare>
822
+     */
823
+    private function getSharedWithMe($node, bool $includeTags): array {
824
+        $userShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_USER, $node, -1, 0);
825
+        $groupShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_GROUP, $node, -1, 0);
826
+        $circleShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_CIRCLE, $node, -1, 0);
827
+        $roomShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_ROOM, $node, -1, 0);
828
+        $deckShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_DECK, $node, -1, 0);
829
+        $sciencemeshShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_SCIENCEMESH, $node, -1, 0);
830
+
831
+        $shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares);
832
+
833
+        $filteredShares = array_filter($shares, function (IShare $share) {
834
+            return $share->getShareOwner() !== $this->userId;
835
+        });
836
+
837
+        $formatted = [];
838
+        foreach ($filteredShares as $share) {
839
+            if ($this->canAccessShare($share)) {
840
+                try {
841
+                    $formatted[] = $this->formatShare($share);
842
+                } catch (NotFoundException $e) {
843
+                    // Ignore this share
844
+                }
845
+            }
846
+        }
847
+
848
+        if ($includeTags) {
849
+            $formatted = Helper::populateTags($formatted, Server::get(ITagManager::class));
850
+        }
851
+
852
+        return $formatted;
853
+    }
854
+
855
+    /**
856
+     * @param Node $folder
857
+     *
858
+     * @return list<Files_SharingShare>
859
+     * @throws OCSBadRequestException
860
+     * @throws NotFoundException
861
+     */
862
+    private function getSharesInDir(Node $folder): array {
863
+        if (!($folder instanceof Folder)) {
864
+            throw new OCSBadRequestException($this->l->t('Not a directory'));
865
+        }
866
+
867
+        $nodes = $folder->getDirectoryListing();
868
+
869
+        /** @var IShare[] $shares */
870
+        $shares = array_reduce($nodes, function ($carry, $node) {
871
+            $carry = array_merge($carry, $this->getAllShares($node, true));
872
+            return $carry;
873
+        }, []);
874
+
875
+        // filter out duplicate shares
876
+        $known = [];
877
+
878
+        $formatted = $miniFormatted = [];
879
+        $resharingRight = false;
880
+        $known = [];
881
+        foreach ($shares as $share) {
882
+            if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->userId) {
883
+                continue;
884
+            }
885
+
886
+            try {
887
+                $format = $this->formatShare($share);
888
+
889
+                $known[] = $share->getId();
890
+                $formatted[] = $format;
891
+                if ($share->getSharedBy() === $this->userId) {
892
+                    $miniFormatted[] = $format;
893
+                }
894
+                if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $folder)) {
895
+                    $resharingRight = true;
896
+                }
897
+            } catch (\Exception $e) {
898
+                //Ignore this share
899
+            }
900
+        }
901
+
902
+        if (!$resharingRight) {
903
+            $formatted = $miniFormatted;
904
+        }
905
+
906
+        return $formatted;
907
+    }
908
+
909
+    /**
910
+     * Get shares of the current user
911
+     *
912
+     * @param string $shared_with_me Only get shares with the current user
913
+     * @param string $reshares Only get shares by the current user and reshares
914
+     * @param string $subfiles Only get all shares in a folder
915
+     * @param string $path Get shares for a specific path
916
+     * @param string $include_tags Include tags in the share
917
+     *
918
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
919
+     * @throws OCSNotFoundException The folder was not found or is inaccessible
920
+     *
921
+     * 200: Shares returned
922
+     */
923
+    #[NoAdminRequired]
924
+    public function getShares(
925
+        string $shared_with_me = 'false',
926
+        string $reshares = 'false',
927
+        string $subfiles = 'false',
928
+        string $path = '',
929
+        string $include_tags = 'false',
930
+    ): DataResponse {
931
+        $node = null;
932
+        if ($path !== '') {
933
+            $userFolder = $this->rootFolder->getUserFolder($this->userId);
934
+            try {
935
+                $node = $userFolder->get($path);
936
+                $this->lock($node);
937
+            } catch (NotFoundException $e) {
938
+                throw new OCSNotFoundException(
939
+                    $this->l->t('Wrong path, file/folder does not exist')
940
+                );
941
+            } catch (LockedException $e) {
942
+                throw new OCSNotFoundException($this->l->t('Could not lock node'));
943
+            }
944
+        }
945
+
946
+        $shares = $this->getFormattedShares(
947
+            $this->userId,
948
+            $node,
949
+            ($shared_with_me === 'true'),
950
+            ($reshares === 'true'),
951
+            ($subfiles === 'true'),
952
+            ($include_tags === 'true')
953
+        );
954
+
955
+        return new DataResponse($shares);
956
+    }
957
+
958
+    private function getLinkSharePermissions(?int $permissions, ?bool $legacyPublicUpload): int {
959
+        $permissions = $permissions ?? Constants::PERMISSION_READ;
960
+
961
+        // Legacy option handling
962
+        if ($legacyPublicUpload !== null) {
963
+            $permissions = $legacyPublicUpload
964
+                ? (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
965
+                : Constants::PERMISSION_READ;
966
+        }
967
+
968
+        // TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
969
+        if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
970
+            && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
971
+            $permissions |= Constants::PERMISSION_SHARE;
972
+        }
973
+
974
+        return $permissions;
975
+    }
976
+
977
+    /**
978
+     * Helper to check for legacy "publicUpload" handling.
979
+     * If the value is set to `true` or `false` then true or false are returned.
980
+     * Otherwise null is returned to indicate that the option was not (or wrong) set.
981
+     *
982
+     * @param null|string $legacyPublicUpload The value of `publicUpload`
983
+     */
984
+    private function getLegacyPublicUpload(?string $legacyPublicUpload): ?bool {
985
+        if ($legacyPublicUpload === 'true') {
986
+            return true;
987
+        } elseif ($legacyPublicUpload === 'false') {
988
+            return false;
989
+        }
990
+        // Not set at all
991
+        return null;
992
+    }
993
+
994
+    /**
995
+     * For link and email shares validate that only allowed combinations are set.
996
+     *
997
+     * @throw OCSBadRequestException If permission combination is invalid.
998
+     * @throw OCSForbiddenException If public upload was forbidden by the administrator.
999
+     */
1000
+    private function validateLinkSharePermissions(Node $node, int $permissions, ?bool $legacyPublicUpload): void {
1001
+        if ($legacyPublicUpload && ($node instanceof File)) {
1002
+            throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1003
+        }
1004
+
1005
+        // We need at least READ or CREATE (file drop)
1006
+        if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1007
+            && !$this->hasPermission($permissions, Constants::PERMISSION_CREATE)) {
1008
+            throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
1009
+        }
1010
+
1011
+        // UPDATE and DELETE require a READ permission
1012
+        if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1013
+            && ($this->hasPermission($permissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($permissions, Constants::PERMISSION_DELETE))) {
1014
+            throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
1015
+        }
1016
+
1017
+        // Check if public uploading was disabled
1018
+        if ($this->hasPermission($permissions, Constants::PERMISSION_CREATE)
1019
+            && !$this->shareManager->shareApiLinkAllowPublicUpload()) {
1020
+            throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1021
+        }
1022
+    }
1023
+
1024
+    /**
1025
+     * @param string $viewer
1026
+     * @param Node $node
1027
+     * @param bool $sharedWithMe
1028
+     * @param bool $reShares
1029
+     * @param bool $subFiles
1030
+     * @param bool $includeTags
1031
+     *
1032
+     * @return list<Files_SharingShare>
1033
+     * @throws NotFoundException
1034
+     * @throws OCSBadRequestException
1035
+     */
1036
+    private function getFormattedShares(
1037
+        string $viewer,
1038
+        $node = null,
1039
+        bool $sharedWithMe = false,
1040
+        bool $reShares = false,
1041
+        bool $subFiles = false,
1042
+        bool $includeTags = false,
1043
+    ): array {
1044
+        if ($sharedWithMe) {
1045
+            return $this->getSharedWithMe($node, $includeTags);
1046
+        }
1047
+
1048
+        if ($subFiles) {
1049
+            return $this->getSharesInDir($node);
1050
+        }
1051
+
1052
+        $shares = $this->getSharesFromNode($viewer, $node, $reShares);
1053
+
1054
+        $known = $formatted = $miniFormatted = [];
1055
+        $resharingRight = false;
1056
+        foreach ($shares as $share) {
1057
+            try {
1058
+                $share->getNode();
1059
+            } catch (NotFoundException $e) {
1060
+                /*
1061 1061
 				 * Ignore shares where we can't get the node
1062 1062
 				 * For example deleted shares
1063 1063
 				 */
1064
-				continue;
1065
-			}
1066
-
1067
-			if (in_array($share->getId(), $known)
1068
-				|| ($share->getSharedWith() === $this->userId && $share->getShareType() === IShare::TYPE_USER)) {
1069
-				continue;
1070
-			}
1071
-
1072
-			$known[] = $share->getId();
1073
-			try {
1074
-				/** @var IShare $share */
1075
-				$format = $this->formatShare($share, $node);
1076
-				$formatted[] = $format;
1077
-
1078
-				// let's also build a list of shares created
1079
-				// by the current user only, in case
1080
-				// there is no resharing rights
1081
-				if ($share->getSharedBy() === $this->userId) {
1082
-					$miniFormatted[] = $format;
1083
-				}
1084
-
1085
-				// check if one of those share is shared with me
1086
-				// and if I have resharing rights on it
1087
-				if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $node)) {
1088
-					$resharingRight = true;
1089
-				}
1090
-			} catch (InvalidPathException|NotFoundException $e) {
1091
-			}
1092
-		}
1093
-
1094
-		if (!$resharingRight) {
1095
-			$formatted = $miniFormatted;
1096
-		}
1097
-
1098
-		// fix eventual missing display name from federated shares
1099
-		$formatted = $this->fixMissingDisplayName($formatted);
1100
-
1101
-		if ($includeTags) {
1102
-			$formatted =
1103
-				Helper::populateTags($formatted, Server::get(ITagManager::class));
1104
-		}
1105
-
1106
-		return $formatted;
1107
-	}
1108
-
1109
-
1110
-	/**
1111
-	 * Get all shares relative to a file, including parent folders shares rights
1112
-	 *
1113
-	 * @param string $path Path all shares will be relative to
1114
-	 *
1115
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1116
-	 * @throws InvalidPathException
1117
-	 * @throws NotFoundException
1118
-	 * @throws OCSNotFoundException The given path is invalid
1119
-	 * @throws SharingRightsException
1120
-	 *
1121
-	 * 200: Shares returned
1122
-	 */
1123
-	#[NoAdminRequired]
1124
-	public function getInheritedShares(string $path): DataResponse {
1125
-		// get Node from (string) path.
1126
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1127
-		try {
1128
-			$node = $userFolder->get($path);
1129
-			$this->lock($node);
1130
-		} catch (NotFoundException $e) {
1131
-			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
1132
-		} catch (LockedException $e) {
1133
-			throw new OCSNotFoundException($this->l->t('Could not lock path'));
1134
-		}
1135
-
1136
-		if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
1137
-			throw new SharingRightsException($this->l->t('no sharing rights on this item'));
1138
-		}
1139
-
1140
-		// The current top parent we have access to
1141
-		$parent = $node;
1142
-
1143
-		// initiate real owner.
1144
-		$owner = $node->getOwner()
1145
-			->getUID();
1146
-		if (!$this->userManager->userExists($owner)) {
1147
-			return new DataResponse([]);
1148
-		}
1149
-
1150
-		// get node based on the owner, fix owner in case of external storage
1151
-		$userFolder = $this->rootFolder->getUserFolder($owner);
1152
-		if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) {
1153
-			$owner = $node->getOwner()
1154
-				->getUID();
1155
-			$userFolder = $this->rootFolder->getUserFolder($owner);
1156
-			$node = $userFolder->getFirstNodeById($node->getId());
1157
-		}
1158
-		$basePath = $userFolder->getPath();
1159
-
1160
-		// generate node list for each parent folders
1161
-		/** @var Node[] $nodes */
1162
-		$nodes = [];
1163
-		while (true) {
1164
-			$node = $node->getParent();
1165
-			if ($node->getPath() === $basePath) {
1166
-				break;
1167
-			}
1168
-			$nodes[] = $node;
1169
-		}
1170
-
1171
-		// The user that is requesting this list
1172
-		$currentUserFolder = $this->rootFolder->getUserFolder($this->userId);
1173
-
1174
-		// for each nodes, retrieve shares.
1175
-		$shares = [];
1176
-
1177
-		foreach ($nodes as $node) {
1178
-			$getShares = $this->getFormattedShares($owner, $node, false, true);
1179
-
1180
-			$currentUserNode = $currentUserFolder->getFirstNodeById($node->getId());
1181
-			if ($currentUserNode) {
1182
-				$parent = $currentUserNode;
1183
-			}
1184
-
1185
-			$subPath = $currentUserFolder->getRelativePath($parent->getPath());
1186
-			foreach ($getShares as &$share) {
1187
-				$share['via_fileid'] = $parent->getId();
1188
-				$share['via_path'] = $subPath;
1189
-			}
1190
-			$this->mergeFormattedShares($shares, $getShares);
1191
-		}
1192
-
1193
-		return new DataResponse(array_values($shares));
1194
-	}
1195
-
1196
-	/**
1197
-	 * Check whether a set of permissions contains the permissions to check.
1198
-	 */
1199
-	private function hasPermission(int $permissionsSet, int $permissionsToCheck): bool {
1200
-		return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
1201
-	}
1202
-
1203
-	/**
1204
-	 * Update a share
1205
-	 *
1206
-	 * @param string $id ID of the share
1207
-	 * @param int|null $permissions New permissions
1208
-	 * @param string|null $password New password
1209
-	 * @param string|null $sendPasswordByTalk New condition if the password should be send over Talk
1210
-	 * @param string|null $publicUpload New condition if public uploading is allowed
1211
-	 * @param string|null $expireDate New expiry date
1212
-	 * @param string|null $note New note
1213
-	 * @param string|null $label New label
1214
-	 * @param string|null $hideDownload New condition if the download should be hidden
1215
-	 * @param string|null $attributes New additional attributes
1216
-	 * @param string|null $sendMail if the share should be send by mail.
1217
-	 *                              Considering the share already exists, no mail will be send after the share is updated.
1218
-	 *                              You will have to use the sendMail action to send the mail.
1219
-	 * @param string|null $shareWith New recipient for email shares
1220
-	 * @param string|null $token New token
1221
-	 * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
1222
-	 * @throws OCSBadRequestException Share could not be updated because the requested changes are invalid
1223
-	 * @throws OCSForbiddenException Missing permissions to update the share
1224
-	 * @throws OCSNotFoundException Share not found
1225
-	 *
1226
-	 * 200: Share updated successfully
1227
-	 */
1228
-	#[NoAdminRequired]
1229
-	public function updateShare(
1230
-		string $id,
1231
-		?int $permissions = null,
1232
-		?string $password = null,
1233
-		?string $sendPasswordByTalk = null,
1234
-		?string $publicUpload = null,
1235
-		?string $expireDate = null,
1236
-		?string $note = null,
1237
-		?string $label = null,
1238
-		?string $hideDownload = null,
1239
-		?string $attributes = null,
1240
-		?string $sendMail = null,
1241
-		?string $token = null,
1242
-	): DataResponse {
1243
-		try {
1244
-			$share = $this->getShareById($id);
1245
-		} catch (ShareNotFound $e) {
1246
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1247
-		}
1248
-
1249
-		$this->lock($share->getNode());
1250
-
1251
-		if (!$this->canAccessShare($share, false)) {
1252
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1253
-		}
1254
-
1255
-		if (!$this->canEditShare($share)) {
1256
-			throw new OCSForbiddenException($this->l->t('You are not allowed to edit incoming shares'));
1257
-		}
1258
-
1259
-		if (
1260
-			$permissions === null &&
1261
-			$password === null &&
1262
-			$sendPasswordByTalk === null &&
1263
-			$publicUpload === null &&
1264
-			$expireDate === null &&
1265
-			$note === null &&
1266
-			$label === null &&
1267
-			$hideDownload === null &&
1268
-			$attributes === null &&
1269
-			$sendMail === null &&
1270
-			$token === null
1271
-		) {
1272
-			throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
1273
-		}
1274
-
1275
-		if ($note !== null) {
1276
-			$share->setNote($note);
1277
-		}
1278
-
1279
-		if ($attributes !== null) {
1280
-			$share = $this->setShareAttributes($share, $attributes);
1281
-		}
1282
-
1283
-		// Handle mail send
1284
-		if ($sendMail === 'true' || $sendMail === 'false') {
1285
-			$share->setMailSend($sendMail === 'true');
1286
-		}
1287
-
1288
-		/**
1289
-		 * expiration date, password and publicUpload only make sense for link shares
1290
-		 */
1291
-		if ($share->getShareType() === IShare::TYPE_LINK
1292
-			|| $share->getShareType() === IShare::TYPE_EMAIL) {
1293
-
1294
-			// Update hide download state
1295
-			$attributes = $share->getAttributes() ?? $share->newAttributes();
1296
-			if ($hideDownload === 'true') {
1297
-				$share->setHideDownload(true);
1298
-				$attributes->setAttribute('permissions', 'download', false);
1299
-			} elseif ($hideDownload === 'false') {
1300
-				$share->setHideDownload(false);
1301
-				$attributes->setAttribute('permissions', 'download', true);
1302
-			}
1303
-			$share->setAttributes($attributes);
1304
-
1305
-
1306
-			// If either manual permissions are specified or publicUpload
1307
-			// then we need to also update the permissions of the share
1308
-			if ($permissions !== null || $publicUpload !== null) {
1309
-				$hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
1310
-				$permissions = $this->getLinkSharePermissions($permissions ?? Constants::PERMISSION_READ, $hasPublicUpload);
1311
-				$this->validateLinkSharePermissions($share->getNode(), $permissions, $hasPublicUpload);
1312
-				$share->setPermissions($permissions);
1313
-			}
1314
-
1315
-			if ($password === '') {
1316
-				$share->setPassword(null);
1317
-			} elseif ($password !== null) {
1318
-				$share->setPassword($password);
1319
-			}
1320
-
1321
-			if ($label !== null) {
1322
-				if (strlen($label) > 255) {
1323
-					throw new OCSBadRequestException('Maximum label length is 255');
1324
-				}
1325
-				$share->setLabel($label);
1326
-			}
1327
-
1328
-			if ($sendPasswordByTalk === 'true') {
1329
-				if (!$this->appManager->isEnabledForUser('spreed')) {
1330
-					throw new OCSForbiddenException($this->l->t('"Sending the password by Nextcloud Talk" for sharing a file or folder failed because Nextcloud Talk is not enabled.'));
1331
-				}
1332
-
1333
-				$share->setSendPasswordByTalk(true);
1334
-			} elseif ($sendPasswordByTalk !== null) {
1335
-				$share->setSendPasswordByTalk(false);
1336
-			}
1337
-
1338
-			if ($token !== null) {
1339
-				if (!$this->shareManager->allowCustomTokens()) {
1340
-					throw new OCSForbiddenException($this->l->t('Custom share link tokens have been disabled by the administrator'));
1341
-				}
1342
-				if (!$this->validateToken($token)) {
1343
-					throw new OCSBadRequestException($this->l->t('Tokens must contain at least 1 character and may only contain letters, numbers, or a hyphen'));
1344
-				}
1345
-				$share->setToken($token);
1346
-			}
1347
-		}
1348
-
1349
-		// NOT A LINK SHARE
1350
-		else {
1351
-			if ($permissions !== null) {
1352
-				$share->setPermissions($permissions);
1353
-			}
1354
-		}
1355
-
1356
-		if ($expireDate === '') {
1357
-			$share->setExpirationDate(null);
1358
-		} elseif ($expireDate !== null) {
1359
-			try {
1360
-				$expireDateTime = $this->parseDate($expireDate);
1361
-				$share->setExpirationDate($expireDateTime);
1362
-			} catch (\Exception $e) {
1363
-				throw new OCSBadRequestException($e->getMessage(), $e);
1364
-			}
1365
-		}
1366
-
1367
-		try {
1368
-			$this->checkInheritedAttributes($share);
1369
-			$share = $this->shareManager->updateShare($share);
1370
-		} catch (HintException $e) {
1371
-			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1372
-			throw new OCSException($e->getHint(), (int)$code);
1373
-		} catch (\Exception $e) {
1374
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
1375
-			throw new OCSBadRequestException('Failed to update share.', $e);
1376
-		}
1377
-
1378
-		return new DataResponse($this->formatShare($share));
1379
-	}
1380
-
1381
-	private function validateToken(string $token): bool {
1382
-		if (mb_strlen($token) === 0) {
1383
-			return false;
1384
-		}
1385
-		if (!preg_match('/^[a-z0-9-]+$/i', $token)) {
1386
-			return false;
1387
-		}
1388
-		return true;
1389
-	}
1390
-
1391
-	/**
1392
-	 * Get all shares that are still pending
1393
-	 *
1394
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1395
-	 *
1396
-	 * 200: Pending shares returned
1397
-	 */
1398
-	#[NoAdminRequired]
1399
-	public function pendingShares(): DataResponse {
1400
-		$pendingShares = [];
1401
-
1402
-		$shareTypes = [
1403
-			IShare::TYPE_USER,
1404
-			IShare::TYPE_GROUP
1405
-		];
1406
-
1407
-		foreach ($shareTypes as $shareType) {
1408
-			$shares = $this->shareManager->getSharedWith($this->userId, $shareType, null, -1, 0);
1409
-
1410
-			foreach ($shares as $share) {
1411
-				if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
1412
-					$pendingShares[] = $share;
1413
-				}
1414
-			}
1415
-		}
1416
-
1417
-		$result = array_values(array_filter(array_map(function (IShare $share) {
1418
-			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1419
-			$node = $userFolder->getFirstNodeById($share->getNodeId());
1420
-			if (!$node) {
1421
-				// fallback to guessing the path
1422
-				$node = $userFolder->get($share->getTarget());
1423
-				if ($node === null || $share->getTarget() === '') {
1424
-					return null;
1425
-				}
1426
-			}
1427
-
1428
-			try {
1429
-				$formattedShare = $this->formatShare($share, $node);
1430
-				$formattedShare['path'] = '/' . $share->getNode()->getName();
1431
-				$formattedShare['permissions'] = 0;
1432
-				return $formattedShare;
1433
-			} catch (NotFoundException $e) {
1434
-				return null;
1435
-			}
1436
-		}, $pendingShares), function ($entry) {
1437
-			return $entry !== null;
1438
-		}));
1439
-
1440
-		return new DataResponse($result);
1441
-	}
1442
-
1443
-	/**
1444
-	 * Accept a share
1445
-	 *
1446
-	 * @param string $id ID of the share
1447
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
1448
-	 * @throws OCSNotFoundException Share not found
1449
-	 * @throws OCSException
1450
-	 * @throws OCSBadRequestException Share could not be accepted
1451
-	 *
1452
-	 * 200: Share accepted successfully
1453
-	 */
1454
-	#[NoAdminRequired]
1455
-	public function acceptShare(string $id): DataResponse {
1456
-		try {
1457
-			$share = $this->getShareById($id);
1458
-		} catch (ShareNotFound $e) {
1459
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1460
-		}
1461
-
1462
-		if (!$this->canAccessShare($share)) {
1463
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1464
-		}
1465
-
1466
-		try {
1467
-			$this->shareManager->acceptShare($share, $this->userId);
1468
-		} catch (HintException $e) {
1469
-			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1470
-			throw new OCSException($e->getHint(), (int)$code);
1471
-		} catch (\Exception $e) {
1472
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
1473
-			throw new OCSBadRequestException('Failed to accept share.', $e);
1474
-		}
1475
-
1476
-		return new DataResponse();
1477
-	}
1478
-
1479
-	/**
1480
-	 * Does the user have read permission on the share
1481
-	 *
1482
-	 * @param IShare $share the share to check
1483
-	 * @param boolean $checkGroups check groups as well?
1484
-	 * @return boolean
1485
-	 * @throws NotFoundException
1486
-	 *
1487
-	 * @suppress PhanUndeclaredClassMethod
1488
-	 */
1489
-	protected function canAccessShare(IShare $share, bool $checkGroups = true): bool {
1490
-		// A file with permissions 0 can't be accessed by us. So Don't show it
1491
-		if ($share->getPermissions() === 0) {
1492
-			return false;
1493
-		}
1494
-
1495
-		// Owner of the file and the sharer of the file can always get share
1496
-		if ($share->getShareOwner() === $this->userId
1497
-			|| $share->getSharedBy() === $this->userId) {
1498
-			return true;
1499
-		}
1500
-
1501
-		// If the share is shared with you, you can access it!
1502
-		if ($share->getShareType() === IShare::TYPE_USER
1503
-			&& $share->getSharedWith() === $this->userId) {
1504
-			return true;
1505
-		}
1506
-
1507
-		// Have reshare rights on the shared file/folder ?
1508
-		// Does the currentUser have access to the shared file?
1509
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1510
-		$file = $userFolder->getFirstNodeById($share->getNodeId());
1511
-		if ($file && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1512
-			return true;
1513
-		}
1514
-
1515
-		// If in the recipient group, you can see the share
1516
-		if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
1517
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
1518
-			$user = $this->userManager->get($this->userId);
1519
-			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1520
-				return true;
1521
-			}
1522
-		}
1523
-
1524
-		if ($share->getShareType() === IShare::TYPE_CIRCLE) {
1525
-			// TODO: have a sanity check like above?
1526
-			return true;
1527
-		}
1528
-
1529
-		if ($share->getShareType() === IShare::TYPE_ROOM) {
1530
-			try {
1531
-				return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1532
-			} catch (ContainerExceptionInterface $e) {
1533
-				return false;
1534
-			}
1535
-		}
1536
-
1537
-		if ($share->getShareType() === IShare::TYPE_DECK) {
1538
-			try {
1539
-				return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1540
-			} catch (ContainerExceptionInterface $e) {
1541
-				return false;
1542
-			}
1543
-		}
1544
-
1545
-		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1546
-			try {
1547
-				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1548
-			} catch (ContainerExceptionInterface $e) {
1549
-				return false;
1550
-			}
1551
-		}
1552
-
1553
-		return false;
1554
-	}
1555
-
1556
-	/**
1557
-	 * Does the user have edit permission on the share
1558
-	 *
1559
-	 * @param IShare $share the share to check
1560
-	 * @return boolean
1561
-	 */
1562
-	protected function canEditShare(IShare $share): bool {
1563
-		// A file with permissions 0 can't be accessed by us. So Don't show it
1564
-		if ($share->getPermissions() === 0) {
1565
-			return false;
1566
-		}
1567
-
1568
-		// The owner of the file and the creator of the share
1569
-		// can always edit the share
1570
-		if ($share->getShareOwner() === $this->userId ||
1571
-			$share->getSharedBy() === $this->userId
1572
-		) {
1573
-			return true;
1574
-		}
1575
-
1576
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1577
-		$file = $userFolder->getFirstNodeById($share->getNodeId());
1578
-		if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1579
-			return true;
1580
-		}
1581
-
1582
-		//! we do NOT support some kind of `admin` in groups.
1583
-		//! You cannot edit shares shared to a group you're
1584
-		//! a member of if you're not the share owner or the file owner!
1585
-
1586
-		return false;
1587
-	}
1588
-
1589
-	/**
1590
-	 * Does the user have delete permission on the share
1591
-	 *
1592
-	 * @param IShare $share the share to check
1593
-	 * @return boolean
1594
-	 */
1595
-	protected function canDeleteShare(IShare $share): bool {
1596
-		// A file with permissions 0 can't be accessed by us. So Don't show it
1597
-		if ($share->getPermissions() === 0) {
1598
-			return false;
1599
-		}
1600
-
1601
-		// if the user is the recipient, i can unshare
1602
-		// the share with self
1603
-		if ($share->getShareType() === IShare::TYPE_USER &&
1604
-			$share->getSharedWith() === $this->userId
1605
-		) {
1606
-			return true;
1607
-		}
1608
-
1609
-		// The owner of the file and the creator of the share
1610
-		// can always delete the share
1611
-		if ($share->getShareOwner() === $this->userId ||
1612
-			$share->getSharedBy() === $this->userId
1613
-		) {
1614
-			return true;
1615
-		}
1616
-
1617
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1618
-		$file = $userFolder->getFirstNodeById($share->getNodeId());
1619
-		if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1620
-			return true;
1621
-		}
1622
-
1623
-		return false;
1624
-	}
1625
-
1626
-	/**
1627
-	 * Does the user have delete permission on the share
1628
-	 * This differs from the canDeleteShare function as it only
1629
-	 * remove the share for the current user. It does NOT
1630
-	 * completely delete the share but only the mount point.
1631
-	 * It can then be restored from the deleted shares section.
1632
-	 *
1633
-	 * @param IShare $share the share to check
1634
-	 * @return boolean
1635
-	 *
1636
-	 * @suppress PhanUndeclaredClassMethod
1637
-	 */
1638
-	protected function canDeleteShareFromSelf(IShare $share): bool {
1639
-		if ($share->getShareType() !== IShare::TYPE_GROUP &&
1640
-			$share->getShareType() !== IShare::TYPE_ROOM &&
1641
-			$share->getShareType() !== IShare::TYPE_DECK &&
1642
-			$share->getShareType() !== IShare::TYPE_SCIENCEMESH
1643
-		) {
1644
-			return false;
1645
-		}
1646
-
1647
-		if ($share->getShareOwner() === $this->userId ||
1648
-			$share->getSharedBy() === $this->userId
1649
-		) {
1650
-			// Delete the whole share, not just for self
1651
-			return false;
1652
-		}
1653
-
1654
-		// If in the recipient group, you can delete the share from self
1655
-		if ($share->getShareType() === IShare::TYPE_GROUP) {
1656
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
1657
-			$user = $this->userManager->get($this->userId);
1658
-			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1659
-				return true;
1660
-			}
1661
-		}
1662
-
1663
-		if ($share->getShareType() === IShare::TYPE_ROOM) {
1664
-			try {
1665
-				return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1666
-			} catch (ContainerExceptionInterface $e) {
1667
-				return false;
1668
-			}
1669
-		}
1670
-
1671
-		if ($share->getShareType() === IShare::TYPE_DECK) {
1672
-			try {
1673
-				return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1674
-			} catch (ContainerExceptionInterface $e) {
1675
-				return false;
1676
-			}
1677
-		}
1678
-
1679
-		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1680
-			try {
1681
-				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1682
-			} catch (ContainerExceptionInterface $e) {
1683
-				return false;
1684
-			}
1685
-		}
1686
-
1687
-		return false;
1688
-	}
1689
-
1690
-	/**
1691
-	 * Make sure that the passed date is valid ISO 8601
1692
-	 * So YYYY-MM-DD
1693
-	 * If not throw an exception
1694
-	 *
1695
-	 * @param string $expireDate
1696
-	 *
1697
-	 * @throws \Exception
1698
-	 * @return \DateTime
1699
-	 */
1700
-	private function parseDate(string $expireDate): \DateTime {
1701
-		try {
1702
-			$date = new \DateTime(trim($expireDate, '"'), $this->dateTimeZone->getTimeZone());
1703
-			// Make sure it expires at midnight in owner timezone
1704
-			$date->setTime(0, 0, 0);
1705
-		} catch (\Exception $e) {
1706
-			throw new \Exception($this->l->t('Invalid date. Format must be YYYY-MM-DD'));
1707
-		}
1708
-
1709
-		return $date;
1710
-	}
1711
-
1712
-	/**
1713
-	 * Since we have multiple providers but the OCS Share API v1 does
1714
-	 * not support this we need to check all backends.
1715
-	 *
1716
-	 * @param string $id
1717
-	 * @return IShare
1718
-	 * @throws ShareNotFound
1719
-	 */
1720
-	private function getShareById(string $id): IShare {
1721
-		$share = null;
1722
-
1723
-		// First check if it is an internal share.
1724
-		try {
1725
-			$share = $this->shareManager->getShareById('ocinternal:' . $id, $this->userId);
1726
-			return $share;
1727
-		} catch (ShareNotFound $e) {
1728
-			// Do nothing, just try the other share type
1729
-		}
1730
-
1731
-
1732
-		try {
1733
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1734
-				$share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->userId);
1735
-				return $share;
1736
-			}
1737
-		} catch (ShareNotFound $e) {
1738
-			// Do nothing, just try the other share type
1739
-		}
1740
-
1741
-		try {
1742
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1743
-				$share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->userId);
1744
-				return $share;
1745
-			}
1746
-		} catch (ShareNotFound $e) {
1747
-			// Do nothing, just try the other share type
1748
-		}
1749
-
1750
-		try {
1751
-			$share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->userId);
1752
-			return $share;
1753
-		} catch (ShareNotFound $e) {
1754
-			// Do nothing, just try the other share type
1755
-		}
1756
-
1757
-		try {
1758
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1759
-				$share = $this->shareManager->getShareById('deck:' . $id, $this->userId);
1760
-				return $share;
1761
-			}
1762
-		} catch (ShareNotFound $e) {
1763
-			// Do nothing, just try the other share type
1764
-		}
1765
-
1766
-		try {
1767
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
1768
-				$share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->userId);
1769
-				return $share;
1770
-			}
1771
-		} catch (ShareNotFound $e) {
1772
-			// Do nothing, just try the other share type
1773
-		}
1774
-
1775
-		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1776
-			throw new ShareNotFound();
1777
-		}
1778
-		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->userId);
1779
-
1780
-		return $share;
1781
-	}
1782
-
1783
-	/**
1784
-	 * Lock a Node
1785
-	 *
1786
-	 * @param Node $node
1787
-	 * @throws LockedException
1788
-	 */
1789
-	private function lock(Node $node) {
1790
-		$node->lock(ILockingProvider::LOCK_SHARED);
1791
-		$this->lockedNode = $node;
1792
-	}
1793
-
1794
-	/**
1795
-	 * Cleanup the remaining locks
1796
-	 * @throws LockedException
1797
-	 */
1798
-	public function cleanup() {
1799
-		if ($this->lockedNode !== null) {
1800
-			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1801
-		}
1802
-	}
1803
-
1804
-	/**
1805
-	 * Returns the helper of ShareAPIController for room shares.
1806
-	 *
1807
-	 * If the Talk application is not enabled or the helper is not available
1808
-	 * a ContainerExceptionInterface is thrown instead.
1809
-	 *
1810
-	 * @return \OCA\Talk\Share\Helper\ShareAPIController
1811
-	 * @throws ContainerExceptionInterface
1812
-	 */
1813
-	private function getRoomShareHelper() {
1814
-		if (!$this->appManager->isEnabledForUser('spreed')) {
1815
-			throw new QueryException();
1816
-		}
1817
-
1818
-		return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
1819
-	}
1820
-
1821
-	/**
1822
-	 * Returns the helper of ShareAPIHelper for deck shares.
1823
-	 *
1824
-	 * If the Deck application is not enabled or the helper is not available
1825
-	 * a ContainerExceptionInterface is thrown instead.
1826
-	 *
1827
-	 * @return \OCA\Deck\Sharing\ShareAPIHelper
1828
-	 * @throws ContainerExceptionInterface
1829
-	 */
1830
-	private function getDeckShareHelper() {
1831
-		if (!$this->appManager->isEnabledForUser('deck')) {
1832
-			throw new QueryException();
1833
-		}
1834
-
1835
-		return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
1836
-	}
1837
-
1838
-	/**
1839
-	 * Returns the helper of ShareAPIHelper for sciencemesh shares.
1840
-	 *
1841
-	 * If the sciencemesh application is not enabled or the helper is not available
1842
-	 * a ContainerExceptionInterface is thrown instead.
1843
-	 *
1844
-	 * @return \OCA\Deck\Sharing\ShareAPIHelper
1845
-	 * @throws ContainerExceptionInterface
1846
-	 */
1847
-	private function getSciencemeshShareHelper() {
1848
-		if (!$this->appManager->isEnabledForUser('sciencemesh')) {
1849
-			throw new QueryException();
1850
-		}
1851
-
1852
-		return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
1853
-	}
1854
-
1855
-	/**
1856
-	 * @param string $viewer
1857
-	 * @param Node $node
1858
-	 * @param bool $reShares
1859
-	 *
1860
-	 * @return IShare[]
1861
-	 */
1862
-	private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
1863
-		$providers = [
1864
-			IShare::TYPE_USER,
1865
-			IShare::TYPE_GROUP,
1866
-			IShare::TYPE_LINK,
1867
-			IShare::TYPE_EMAIL,
1868
-			IShare::TYPE_CIRCLE,
1869
-			IShare::TYPE_ROOM,
1870
-			IShare::TYPE_DECK,
1871
-			IShare::TYPE_SCIENCEMESH
1872
-		];
1873
-
1874
-		// Should we assume that the (currentUser) viewer is the owner of the node !?
1875
-		$shares = [];
1876
-		foreach ($providers as $provider) {
1877
-			if (!$this->shareManager->shareProviderExists($provider)) {
1878
-				continue;
1879
-			}
1880
-
1881
-			$providerShares =
1882
-				$this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
1883
-			$shares = array_merge($shares, $providerShares);
1884
-		}
1885
-
1886
-		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1887
-			$federatedShares = $this->shareManager->getSharesBy(
1888
-				$this->userId, IShare::TYPE_REMOTE, $node, $reShares, -1, 0
1889
-			);
1890
-			$shares = array_merge($shares, $federatedShares);
1891
-		}
1892
-
1893
-		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1894
-			$federatedShares = $this->shareManager->getSharesBy(
1895
-				$this->userId, IShare::TYPE_REMOTE_GROUP, $node, $reShares, -1, 0
1896
-			);
1897
-			$shares = array_merge($shares, $federatedShares);
1898
-		}
1899
-
1900
-		return $shares;
1901
-	}
1902
-
1903
-
1904
-	/**
1905
-	 * @param Node $node
1906
-	 *
1907
-	 * @throws SharingRightsException
1908
-	 */
1909
-	private function confirmSharingRights(Node $node): void {
1910
-		if (!$this->hasResharingRights($this->userId, $node)) {
1911
-			throw new SharingRightsException($this->l->t('No sharing rights on this item'));
1912
-		}
1913
-	}
1914
-
1915
-
1916
-	/**
1917
-	 * @param string $viewer
1918
-	 * @param Node $node
1919
-	 *
1920
-	 * @return bool
1921
-	 */
1922
-	private function hasResharingRights($viewer, $node): bool {
1923
-		if ($viewer === $node->getOwner()->getUID()) {
1924
-			return true;
1925
-		}
1926
-
1927
-		foreach ([$node, $node->getParent()] as $node) {
1928
-			$shares = $this->getSharesFromNode($viewer, $node, true);
1929
-			foreach ($shares as $share) {
1930
-				try {
1931
-					if ($this->shareProviderResharingRights($viewer, $share, $node)) {
1932
-						return true;
1933
-					}
1934
-				} catch (InvalidPathException|NotFoundException $e) {
1935
-				}
1936
-			}
1937
-		}
1938
-
1939
-		return false;
1940
-	}
1941
-
1942
-
1943
-	/**
1944
-	 * Returns if we can find resharing rights in an IShare object for a specific user.
1945
-	 *
1946
-	 * @suppress PhanUndeclaredClassMethod
1947
-	 *
1948
-	 * @param string $userId
1949
-	 * @param IShare $share
1950
-	 * @param Node $node
1951
-	 *
1952
-	 * @return bool
1953
-	 * @throws NotFoundException
1954
-	 * @throws InvalidPathException
1955
-	 */
1956
-	private function shareProviderResharingRights(string $userId, IShare $share, $node): bool {
1957
-		if ($share->getShareOwner() === $userId) {
1958
-			return true;
1959
-		}
1960
-
1961
-		// we check that current user have parent resharing rights on the current file
1962
-		if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) {
1963
-			return true;
1964
-		}
1965
-
1966
-		if ((Constants::PERMISSION_SHARE & $share->getPermissions()) === 0) {
1967
-			return false;
1968
-		}
1969
-
1970
-		if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $userId) {
1971
-			return true;
1972
-		}
1973
-
1974
-		if ($share->getShareType() === IShare::TYPE_GROUP && $this->groupManager->isInGroup($userId, $share->getSharedWith())) {
1975
-			return true;
1976
-		}
1977
-
1978
-		if ($share->getShareType() === IShare::TYPE_CIRCLE && Server::get(IAppManager::class)->isEnabledForUser('circles')
1979
-			&& class_exists('\OCA\Circles\Api\v1\Circles')) {
1980
-			$hasCircleId = (str_ends_with($share->getSharedWith(), ']'));
1981
-			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
1982
-			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
1983
-			if ($shareWithLength === false) {
1984
-				$sharedWith = substr($share->getSharedWith(), $shareWithStart);
1985
-			} else {
1986
-				$sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
1987
-			}
1988
-			try {
1989
-				$member = Circles::getMember($sharedWith, $userId, 1);
1990
-				if ($member->getLevel() >= 4) {
1991
-					return true;
1992
-				}
1993
-				return false;
1994
-			} catch (ContainerExceptionInterface $e) {
1995
-				return false;
1996
-			}
1997
-		}
1998
-
1999
-		return false;
2000
-	}
2001
-
2002
-	/**
2003
-	 * Get all the shares for the current user
2004
-	 *
2005
-	 * @param Node|null $path
2006
-	 * @param boolean $reshares
2007
-	 * @return IShare[]
2008
-	 */
2009
-	private function getAllShares(?Node $path = null, bool $reshares = false) {
2010
-		// Get all shares
2011
-		$userShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_USER, $path, $reshares, -1, 0);
2012
-		$groupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
2013
-		$linkShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_LINK, $path, $reshares, -1, 0);
2014
-
2015
-		// EMAIL SHARES
2016
-		$mailShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
2017
-
2018
-		// TEAM SHARES
2019
-		$circleShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
2020
-
2021
-		// TALK SHARES
2022
-		$roomShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
2023
-
2024
-		// DECK SHARES
2025
-		$deckShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_DECK, $path, $reshares, -1, 0);
2026
-
2027
-		// SCIENCEMESH SHARES
2028
-		$sciencemeshShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_SCIENCEMESH, $path, $reshares, -1, 0);
2029
-
2030
-		// FEDERATION
2031
-		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
2032
-			$federatedShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
2033
-		} else {
2034
-			$federatedShares = [];
2035
-		}
2036
-		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
2037
-			$federatedGroupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
2038
-		} else {
2039
-			$federatedGroupShares = [];
2040
-		}
2041
-
2042
-		return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares, $federatedShares, $federatedGroupShares);
2043
-	}
2044
-
2045
-
2046
-	/**
2047
-	 * merging already formatted shares.
2048
-	 * We'll make an associative array to easily detect duplicate Ids.
2049
-	 * Keys _needs_ to be removed after all shares are retrieved and merged.
2050
-	 *
2051
-	 * @param array $shares
2052
-	 * @param array $newShares
2053
-	 */
2054
-	private function mergeFormattedShares(array &$shares, array $newShares) {
2055
-		foreach ($newShares as $newShare) {
2056
-			if (!array_key_exists($newShare['id'], $shares)) {
2057
-				$shares[$newShare['id']] = $newShare;
2058
-			}
2059
-		}
2060
-	}
2061
-
2062
-	/**
2063
-	 * @param IShare $share
2064
-	 * @param string|null $attributesString
2065
-	 * @return IShare modified share
2066
-	 */
2067
-	private function setShareAttributes(IShare $share, ?string $attributesString) {
2068
-		$newShareAttributes = null;
2069
-		if ($attributesString !== null) {
2070
-			$newShareAttributes = $this->shareManager->newShare()->newAttributes();
2071
-			$formattedShareAttributes = \json_decode($attributesString, true);
2072
-			if (is_array($formattedShareAttributes)) {
2073
-				foreach ($formattedShareAttributes as $formattedAttr) {
2074
-					$newShareAttributes->setAttribute(
2075
-						$formattedAttr['scope'],
2076
-						$formattedAttr['key'],
2077
-						$formattedAttr['value'],
2078
-					);
2079
-				}
2080
-			} else {
2081
-				throw new OCSBadRequestException($this->l->t('Invalid share attributes provided: "%s"', [$attributesString]));
2082
-			}
2083
-		}
2084
-		$share->setAttributes($newShareAttributes);
2085
-
2086
-		return $share;
2087
-	}
2088
-
2089
-	private function checkInheritedAttributes(IShare $share): void {
2090
-		if (!$share->getSharedBy()) {
2091
-			return; // Probably in a test
2092
-		}
2093
-
2094
-		$canDownload = false;
2095
-		$hideDownload = true;
2096
-
2097
-		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
2098
-		$nodes = $userFolder->getById($share->getNodeId());
2099
-		foreach ($nodes as $node) {
2100
-			// Owner always can download it - so allow it and break
2101
-			if ($node->getOwner()?->getUID() === $share->getSharedBy()) {
2102
-				$canDownload = true;
2103
-				$hideDownload = false;
2104
-				break;
2105
-			}
2106
-
2107
-			if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
2108
-				$storage = $node->getStorage();
2109
-				if ($storage instanceof Wrapper) {
2110
-					$storage = $storage->getInstanceOfStorage(SharedStorage::class);
2111
-					if ($storage === null) {
2112
-						throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
2113
-					}
2114
-				} else {
2115
-					throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
2116
-				}
2117
-
2118
-				/** @var SharedStorage $storage */
2119
-				$originalShare = $storage->getShare();
2120
-				$inheritedAttributes = $originalShare->getAttributes();
2121
-				// hide if hidden and also the current share enforces hide (can only be false if one share is false or user is owner)
2122
-				$hideDownload = $hideDownload && $originalShare->getHideDownload();
2123
-				// allow download if already allowed by previous share or when the current share allows downloading
2124
-				$canDownload = $canDownload || $inheritedAttributes === null || $inheritedAttributes->getAttribute('permissions', 'download') !== false;
2125
-			}
2126
-		}
2127
-
2128
-		if ($hideDownload || !$canDownload) {
2129
-			$share->setHideDownload(true);
2130
-
2131
-			if (!$canDownload) {
2132
-				$attributes = $share->getAttributes() ?? $share->newAttributes();
2133
-				$attributes->setAttribute('permissions', 'download', false);
2134
-				$share->setAttributes($attributes);
2135
-			}
2136
-		}
2137
-	}
2138
-
2139
-	/**
2140
-	 * Send a mail notification again for a share.
2141
-	 * The mail_send option must be enabled for the given share.
2142
-	 * @param string $id the share ID
2143
-	 * @param string $password the password to check against. Necessary for password protected shares.
2144
-	 * @throws OCSNotFoundException Share not found
2145
-	 * @throws OCSForbiddenException You are not allowed to send mail notifications
2146
-	 * @throws OCSBadRequestException Invalid request or wrong password
2147
-	 * @throws OCSException Error while sending mail notification
2148
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
2149
-	 *
2150
-	 * 200: The email notification was sent successfully
2151
-	 */
2152
-	#[NoAdminRequired]
2153
-	#[UserRateLimit(limit: 5, period: 120)]
2154
-	public function sendShareEmail(string $id, $password = ''): DataResponse {
2155
-		try {
2156
-			$share = $this->getShareById($id);
2157
-
2158
-			if (!$this->canAccessShare($share, false)) {
2159
-				throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2160
-			}
2161
-
2162
-			if (!$this->canEditShare($share)) {
2163
-				throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2164
-			}
2165
-
2166
-			// For mail and link shares, the user must be
2167
-			// the owner of the share, not only the file owner.
2168
-			if ($share->getShareType() === IShare::TYPE_EMAIL
2169
-				|| $share->getShareType() === IShare::TYPE_LINK) {
2170
-				if ($share->getSharedBy() !== $this->userId) {
2171
-					throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2172
-				}
2173
-			}
2174
-
2175
-			try {
2176
-				$provider = $this->factory->getProviderForType($share->getShareType());
2177
-				if (!($provider instanceof IShareProviderWithNotification)) {
2178
-					throw new OCSBadRequestException($this->l->t('No mail notification configured for this share type'));
2179
-				}
2180
-
2181
-				// Circumvent the password encrypted data by
2182
-				// setting the password clear. We're not storing
2183
-				// the password clear, it is just a temporary
2184
-				// object manipulation. The password will stay
2185
-				// encrypted in the database.
2186
-				if ($share->getPassword() !== null && $share->getPassword() !== $password) {
2187
-					if (!$this->shareManager->checkPassword($share, $password)) {
2188
-						throw new OCSBadRequestException($this->l->t('Wrong password'));
2189
-					}
2190
-					$share = $share->setPassword($password);
2191
-				}
2192
-
2193
-				$provider->sendMailNotification($share);
2194
-				return new DataResponse();
2195
-			} catch (Exception $e) {
2196
-				$this->logger->error($e->getMessage(), ['exception' => $e]);
2197
-				throw new OCSException($this->l->t('Error while sending mail notification'));
2198
-			}
2199
-
2200
-		} catch (ShareNotFound $e) {
2201
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2202
-		}
2203
-	}
2204
-
2205
-	/**
2206
-	 * Get a unique share token
2207
-	 *
2208
-	 * @throws OCSException Failed to generate a unique token
2209
-	 *
2210
-	 * @return DataResponse<Http::STATUS_OK, array{token: string}, array{}>
2211
-	 *
2212
-	 * 200: Token generated successfully
2213
-	 */
2214
-	#[ApiRoute(verb: 'GET', url: '/api/v1/token')]
2215
-	#[NoAdminRequired]
2216
-	public function generateToken(): DataResponse {
2217
-		try {
2218
-			$token = $this->shareManager->generateToken();
2219
-			return new DataResponse([
2220
-				'token' => $token,
2221
-			]);
2222
-		} catch (ShareTokenException $e) {
2223
-			throw new OCSException($this->l->t('Failed to generate a unique token'));
2224
-		}
2225
-	}
1064
+                continue;
1065
+            }
1066
+
1067
+            if (in_array($share->getId(), $known)
1068
+                || ($share->getSharedWith() === $this->userId && $share->getShareType() === IShare::TYPE_USER)) {
1069
+                continue;
1070
+            }
1071
+
1072
+            $known[] = $share->getId();
1073
+            try {
1074
+                /** @var IShare $share */
1075
+                $format = $this->formatShare($share, $node);
1076
+                $formatted[] = $format;
1077
+
1078
+                // let's also build a list of shares created
1079
+                // by the current user only, in case
1080
+                // there is no resharing rights
1081
+                if ($share->getSharedBy() === $this->userId) {
1082
+                    $miniFormatted[] = $format;
1083
+                }
1084
+
1085
+                // check if one of those share is shared with me
1086
+                // and if I have resharing rights on it
1087
+                if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $node)) {
1088
+                    $resharingRight = true;
1089
+                }
1090
+            } catch (InvalidPathException|NotFoundException $e) {
1091
+            }
1092
+        }
1093
+
1094
+        if (!$resharingRight) {
1095
+            $formatted = $miniFormatted;
1096
+        }
1097
+
1098
+        // fix eventual missing display name from federated shares
1099
+        $formatted = $this->fixMissingDisplayName($formatted);
1100
+
1101
+        if ($includeTags) {
1102
+            $formatted =
1103
+                Helper::populateTags($formatted, Server::get(ITagManager::class));
1104
+        }
1105
+
1106
+        return $formatted;
1107
+    }
1108
+
1109
+
1110
+    /**
1111
+     * Get all shares relative to a file, including parent folders shares rights
1112
+     *
1113
+     * @param string $path Path all shares will be relative to
1114
+     *
1115
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1116
+     * @throws InvalidPathException
1117
+     * @throws NotFoundException
1118
+     * @throws OCSNotFoundException The given path is invalid
1119
+     * @throws SharingRightsException
1120
+     *
1121
+     * 200: Shares returned
1122
+     */
1123
+    #[NoAdminRequired]
1124
+    public function getInheritedShares(string $path): DataResponse {
1125
+        // get Node from (string) path.
1126
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1127
+        try {
1128
+            $node = $userFolder->get($path);
1129
+            $this->lock($node);
1130
+        } catch (NotFoundException $e) {
1131
+            throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
1132
+        } catch (LockedException $e) {
1133
+            throw new OCSNotFoundException($this->l->t('Could not lock path'));
1134
+        }
1135
+
1136
+        if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
1137
+            throw new SharingRightsException($this->l->t('no sharing rights on this item'));
1138
+        }
1139
+
1140
+        // The current top parent we have access to
1141
+        $parent = $node;
1142
+
1143
+        // initiate real owner.
1144
+        $owner = $node->getOwner()
1145
+            ->getUID();
1146
+        if (!$this->userManager->userExists($owner)) {
1147
+            return new DataResponse([]);
1148
+        }
1149
+
1150
+        // get node based on the owner, fix owner in case of external storage
1151
+        $userFolder = $this->rootFolder->getUserFolder($owner);
1152
+        if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) {
1153
+            $owner = $node->getOwner()
1154
+                ->getUID();
1155
+            $userFolder = $this->rootFolder->getUserFolder($owner);
1156
+            $node = $userFolder->getFirstNodeById($node->getId());
1157
+        }
1158
+        $basePath = $userFolder->getPath();
1159
+
1160
+        // generate node list for each parent folders
1161
+        /** @var Node[] $nodes */
1162
+        $nodes = [];
1163
+        while (true) {
1164
+            $node = $node->getParent();
1165
+            if ($node->getPath() === $basePath) {
1166
+                break;
1167
+            }
1168
+            $nodes[] = $node;
1169
+        }
1170
+
1171
+        // The user that is requesting this list
1172
+        $currentUserFolder = $this->rootFolder->getUserFolder($this->userId);
1173
+
1174
+        // for each nodes, retrieve shares.
1175
+        $shares = [];
1176
+
1177
+        foreach ($nodes as $node) {
1178
+            $getShares = $this->getFormattedShares($owner, $node, false, true);
1179
+
1180
+            $currentUserNode = $currentUserFolder->getFirstNodeById($node->getId());
1181
+            if ($currentUserNode) {
1182
+                $parent = $currentUserNode;
1183
+            }
1184
+
1185
+            $subPath = $currentUserFolder->getRelativePath($parent->getPath());
1186
+            foreach ($getShares as &$share) {
1187
+                $share['via_fileid'] = $parent->getId();
1188
+                $share['via_path'] = $subPath;
1189
+            }
1190
+            $this->mergeFormattedShares($shares, $getShares);
1191
+        }
1192
+
1193
+        return new DataResponse(array_values($shares));
1194
+    }
1195
+
1196
+    /**
1197
+     * Check whether a set of permissions contains the permissions to check.
1198
+     */
1199
+    private function hasPermission(int $permissionsSet, int $permissionsToCheck): bool {
1200
+        return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
1201
+    }
1202
+
1203
+    /**
1204
+     * Update a share
1205
+     *
1206
+     * @param string $id ID of the share
1207
+     * @param int|null $permissions New permissions
1208
+     * @param string|null $password New password
1209
+     * @param string|null $sendPasswordByTalk New condition if the password should be send over Talk
1210
+     * @param string|null $publicUpload New condition if public uploading is allowed
1211
+     * @param string|null $expireDate New expiry date
1212
+     * @param string|null $note New note
1213
+     * @param string|null $label New label
1214
+     * @param string|null $hideDownload New condition if the download should be hidden
1215
+     * @param string|null $attributes New additional attributes
1216
+     * @param string|null $sendMail if the share should be send by mail.
1217
+     *                              Considering the share already exists, no mail will be send after the share is updated.
1218
+     *                              You will have to use the sendMail action to send the mail.
1219
+     * @param string|null $shareWith New recipient for email shares
1220
+     * @param string|null $token New token
1221
+     * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
1222
+     * @throws OCSBadRequestException Share could not be updated because the requested changes are invalid
1223
+     * @throws OCSForbiddenException Missing permissions to update the share
1224
+     * @throws OCSNotFoundException Share not found
1225
+     *
1226
+     * 200: Share updated successfully
1227
+     */
1228
+    #[NoAdminRequired]
1229
+    public function updateShare(
1230
+        string $id,
1231
+        ?int $permissions = null,
1232
+        ?string $password = null,
1233
+        ?string $sendPasswordByTalk = null,
1234
+        ?string $publicUpload = null,
1235
+        ?string $expireDate = null,
1236
+        ?string $note = null,
1237
+        ?string $label = null,
1238
+        ?string $hideDownload = null,
1239
+        ?string $attributes = null,
1240
+        ?string $sendMail = null,
1241
+        ?string $token = null,
1242
+    ): DataResponse {
1243
+        try {
1244
+            $share = $this->getShareById($id);
1245
+        } catch (ShareNotFound $e) {
1246
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1247
+        }
1248
+
1249
+        $this->lock($share->getNode());
1250
+
1251
+        if (!$this->canAccessShare($share, false)) {
1252
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1253
+        }
1254
+
1255
+        if (!$this->canEditShare($share)) {
1256
+            throw new OCSForbiddenException($this->l->t('You are not allowed to edit incoming shares'));
1257
+        }
1258
+
1259
+        if (
1260
+            $permissions === null &&
1261
+            $password === null &&
1262
+            $sendPasswordByTalk === null &&
1263
+            $publicUpload === null &&
1264
+            $expireDate === null &&
1265
+            $note === null &&
1266
+            $label === null &&
1267
+            $hideDownload === null &&
1268
+            $attributes === null &&
1269
+            $sendMail === null &&
1270
+            $token === null
1271
+        ) {
1272
+            throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
1273
+        }
1274
+
1275
+        if ($note !== null) {
1276
+            $share->setNote($note);
1277
+        }
1278
+
1279
+        if ($attributes !== null) {
1280
+            $share = $this->setShareAttributes($share, $attributes);
1281
+        }
1282
+
1283
+        // Handle mail send
1284
+        if ($sendMail === 'true' || $sendMail === 'false') {
1285
+            $share->setMailSend($sendMail === 'true');
1286
+        }
1287
+
1288
+        /**
1289
+         * expiration date, password and publicUpload only make sense for link shares
1290
+         */
1291
+        if ($share->getShareType() === IShare::TYPE_LINK
1292
+            || $share->getShareType() === IShare::TYPE_EMAIL) {
1293
+
1294
+            // Update hide download state
1295
+            $attributes = $share->getAttributes() ?? $share->newAttributes();
1296
+            if ($hideDownload === 'true') {
1297
+                $share->setHideDownload(true);
1298
+                $attributes->setAttribute('permissions', 'download', false);
1299
+            } elseif ($hideDownload === 'false') {
1300
+                $share->setHideDownload(false);
1301
+                $attributes->setAttribute('permissions', 'download', true);
1302
+            }
1303
+            $share->setAttributes($attributes);
1304
+
1305
+
1306
+            // If either manual permissions are specified or publicUpload
1307
+            // then we need to also update the permissions of the share
1308
+            if ($permissions !== null || $publicUpload !== null) {
1309
+                $hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
1310
+                $permissions = $this->getLinkSharePermissions($permissions ?? Constants::PERMISSION_READ, $hasPublicUpload);
1311
+                $this->validateLinkSharePermissions($share->getNode(), $permissions, $hasPublicUpload);
1312
+                $share->setPermissions($permissions);
1313
+            }
1314
+
1315
+            if ($password === '') {
1316
+                $share->setPassword(null);
1317
+            } elseif ($password !== null) {
1318
+                $share->setPassword($password);
1319
+            }
1320
+
1321
+            if ($label !== null) {
1322
+                if (strlen($label) > 255) {
1323
+                    throw new OCSBadRequestException('Maximum label length is 255');
1324
+                }
1325
+                $share->setLabel($label);
1326
+            }
1327
+
1328
+            if ($sendPasswordByTalk === 'true') {
1329
+                if (!$this->appManager->isEnabledForUser('spreed')) {
1330
+                    throw new OCSForbiddenException($this->l->t('"Sending the password by Nextcloud Talk" for sharing a file or folder failed because Nextcloud Talk is not enabled.'));
1331
+                }
1332
+
1333
+                $share->setSendPasswordByTalk(true);
1334
+            } elseif ($sendPasswordByTalk !== null) {
1335
+                $share->setSendPasswordByTalk(false);
1336
+            }
1337
+
1338
+            if ($token !== null) {
1339
+                if (!$this->shareManager->allowCustomTokens()) {
1340
+                    throw new OCSForbiddenException($this->l->t('Custom share link tokens have been disabled by the administrator'));
1341
+                }
1342
+                if (!$this->validateToken($token)) {
1343
+                    throw new OCSBadRequestException($this->l->t('Tokens must contain at least 1 character and may only contain letters, numbers, or a hyphen'));
1344
+                }
1345
+                $share->setToken($token);
1346
+            }
1347
+        }
1348
+
1349
+        // NOT A LINK SHARE
1350
+        else {
1351
+            if ($permissions !== null) {
1352
+                $share->setPermissions($permissions);
1353
+            }
1354
+        }
1355
+
1356
+        if ($expireDate === '') {
1357
+            $share->setExpirationDate(null);
1358
+        } elseif ($expireDate !== null) {
1359
+            try {
1360
+                $expireDateTime = $this->parseDate($expireDate);
1361
+                $share->setExpirationDate($expireDateTime);
1362
+            } catch (\Exception $e) {
1363
+                throw new OCSBadRequestException($e->getMessage(), $e);
1364
+            }
1365
+        }
1366
+
1367
+        try {
1368
+            $this->checkInheritedAttributes($share);
1369
+            $share = $this->shareManager->updateShare($share);
1370
+        } catch (HintException $e) {
1371
+            $code = $e->getCode() === 0 ? 403 : $e->getCode();
1372
+            throw new OCSException($e->getHint(), (int)$code);
1373
+        } catch (\Exception $e) {
1374
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
1375
+            throw new OCSBadRequestException('Failed to update share.', $e);
1376
+        }
1377
+
1378
+        return new DataResponse($this->formatShare($share));
1379
+    }
1380
+
1381
+    private function validateToken(string $token): bool {
1382
+        if (mb_strlen($token) === 0) {
1383
+            return false;
1384
+        }
1385
+        if (!preg_match('/^[a-z0-9-]+$/i', $token)) {
1386
+            return false;
1387
+        }
1388
+        return true;
1389
+    }
1390
+
1391
+    /**
1392
+     * Get all shares that are still pending
1393
+     *
1394
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1395
+     *
1396
+     * 200: Pending shares returned
1397
+     */
1398
+    #[NoAdminRequired]
1399
+    public function pendingShares(): DataResponse {
1400
+        $pendingShares = [];
1401
+
1402
+        $shareTypes = [
1403
+            IShare::TYPE_USER,
1404
+            IShare::TYPE_GROUP
1405
+        ];
1406
+
1407
+        foreach ($shareTypes as $shareType) {
1408
+            $shares = $this->shareManager->getSharedWith($this->userId, $shareType, null, -1, 0);
1409
+
1410
+            foreach ($shares as $share) {
1411
+                if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
1412
+                    $pendingShares[] = $share;
1413
+                }
1414
+            }
1415
+        }
1416
+
1417
+        $result = array_values(array_filter(array_map(function (IShare $share) {
1418
+            $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1419
+            $node = $userFolder->getFirstNodeById($share->getNodeId());
1420
+            if (!$node) {
1421
+                // fallback to guessing the path
1422
+                $node = $userFolder->get($share->getTarget());
1423
+                if ($node === null || $share->getTarget() === '') {
1424
+                    return null;
1425
+                }
1426
+            }
1427
+
1428
+            try {
1429
+                $formattedShare = $this->formatShare($share, $node);
1430
+                $formattedShare['path'] = '/' . $share->getNode()->getName();
1431
+                $formattedShare['permissions'] = 0;
1432
+                return $formattedShare;
1433
+            } catch (NotFoundException $e) {
1434
+                return null;
1435
+            }
1436
+        }, $pendingShares), function ($entry) {
1437
+            return $entry !== null;
1438
+        }));
1439
+
1440
+        return new DataResponse($result);
1441
+    }
1442
+
1443
+    /**
1444
+     * Accept a share
1445
+     *
1446
+     * @param string $id ID of the share
1447
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
1448
+     * @throws OCSNotFoundException Share not found
1449
+     * @throws OCSException
1450
+     * @throws OCSBadRequestException Share could not be accepted
1451
+     *
1452
+     * 200: Share accepted successfully
1453
+     */
1454
+    #[NoAdminRequired]
1455
+    public function acceptShare(string $id): DataResponse {
1456
+        try {
1457
+            $share = $this->getShareById($id);
1458
+        } catch (ShareNotFound $e) {
1459
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1460
+        }
1461
+
1462
+        if (!$this->canAccessShare($share)) {
1463
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1464
+        }
1465
+
1466
+        try {
1467
+            $this->shareManager->acceptShare($share, $this->userId);
1468
+        } catch (HintException $e) {
1469
+            $code = $e->getCode() === 0 ? 403 : $e->getCode();
1470
+            throw new OCSException($e->getHint(), (int)$code);
1471
+        } catch (\Exception $e) {
1472
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
1473
+            throw new OCSBadRequestException('Failed to accept share.', $e);
1474
+        }
1475
+
1476
+        return new DataResponse();
1477
+    }
1478
+
1479
+    /**
1480
+     * Does the user have read permission on the share
1481
+     *
1482
+     * @param IShare $share the share to check
1483
+     * @param boolean $checkGroups check groups as well?
1484
+     * @return boolean
1485
+     * @throws NotFoundException
1486
+     *
1487
+     * @suppress PhanUndeclaredClassMethod
1488
+     */
1489
+    protected function canAccessShare(IShare $share, bool $checkGroups = true): bool {
1490
+        // A file with permissions 0 can't be accessed by us. So Don't show it
1491
+        if ($share->getPermissions() === 0) {
1492
+            return false;
1493
+        }
1494
+
1495
+        // Owner of the file and the sharer of the file can always get share
1496
+        if ($share->getShareOwner() === $this->userId
1497
+            || $share->getSharedBy() === $this->userId) {
1498
+            return true;
1499
+        }
1500
+
1501
+        // If the share is shared with you, you can access it!
1502
+        if ($share->getShareType() === IShare::TYPE_USER
1503
+            && $share->getSharedWith() === $this->userId) {
1504
+            return true;
1505
+        }
1506
+
1507
+        // Have reshare rights on the shared file/folder ?
1508
+        // Does the currentUser have access to the shared file?
1509
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1510
+        $file = $userFolder->getFirstNodeById($share->getNodeId());
1511
+        if ($file && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1512
+            return true;
1513
+        }
1514
+
1515
+        // If in the recipient group, you can see the share
1516
+        if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
1517
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
1518
+            $user = $this->userManager->get($this->userId);
1519
+            if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1520
+                return true;
1521
+            }
1522
+        }
1523
+
1524
+        if ($share->getShareType() === IShare::TYPE_CIRCLE) {
1525
+            // TODO: have a sanity check like above?
1526
+            return true;
1527
+        }
1528
+
1529
+        if ($share->getShareType() === IShare::TYPE_ROOM) {
1530
+            try {
1531
+                return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1532
+            } catch (ContainerExceptionInterface $e) {
1533
+                return false;
1534
+            }
1535
+        }
1536
+
1537
+        if ($share->getShareType() === IShare::TYPE_DECK) {
1538
+            try {
1539
+                return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1540
+            } catch (ContainerExceptionInterface $e) {
1541
+                return false;
1542
+            }
1543
+        }
1544
+
1545
+        if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1546
+            try {
1547
+                return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1548
+            } catch (ContainerExceptionInterface $e) {
1549
+                return false;
1550
+            }
1551
+        }
1552
+
1553
+        return false;
1554
+    }
1555
+
1556
+    /**
1557
+     * Does the user have edit permission on the share
1558
+     *
1559
+     * @param IShare $share the share to check
1560
+     * @return boolean
1561
+     */
1562
+    protected function canEditShare(IShare $share): bool {
1563
+        // A file with permissions 0 can't be accessed by us. So Don't show it
1564
+        if ($share->getPermissions() === 0) {
1565
+            return false;
1566
+        }
1567
+
1568
+        // The owner of the file and the creator of the share
1569
+        // can always edit the share
1570
+        if ($share->getShareOwner() === $this->userId ||
1571
+            $share->getSharedBy() === $this->userId
1572
+        ) {
1573
+            return true;
1574
+        }
1575
+
1576
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1577
+        $file = $userFolder->getFirstNodeById($share->getNodeId());
1578
+        if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1579
+            return true;
1580
+        }
1581
+
1582
+        //! we do NOT support some kind of `admin` in groups.
1583
+        //! You cannot edit shares shared to a group you're
1584
+        //! a member of if you're not the share owner or the file owner!
1585
+
1586
+        return false;
1587
+    }
1588
+
1589
+    /**
1590
+     * Does the user have delete permission on the share
1591
+     *
1592
+     * @param IShare $share the share to check
1593
+     * @return boolean
1594
+     */
1595
+    protected function canDeleteShare(IShare $share): bool {
1596
+        // A file with permissions 0 can't be accessed by us. So Don't show it
1597
+        if ($share->getPermissions() === 0) {
1598
+            return false;
1599
+        }
1600
+
1601
+        // if the user is the recipient, i can unshare
1602
+        // the share with self
1603
+        if ($share->getShareType() === IShare::TYPE_USER &&
1604
+            $share->getSharedWith() === $this->userId
1605
+        ) {
1606
+            return true;
1607
+        }
1608
+
1609
+        // The owner of the file and the creator of the share
1610
+        // can always delete the share
1611
+        if ($share->getShareOwner() === $this->userId ||
1612
+            $share->getSharedBy() === $this->userId
1613
+        ) {
1614
+            return true;
1615
+        }
1616
+
1617
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1618
+        $file = $userFolder->getFirstNodeById($share->getNodeId());
1619
+        if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1620
+            return true;
1621
+        }
1622
+
1623
+        return false;
1624
+    }
1625
+
1626
+    /**
1627
+     * Does the user have delete permission on the share
1628
+     * This differs from the canDeleteShare function as it only
1629
+     * remove the share for the current user. It does NOT
1630
+     * completely delete the share but only the mount point.
1631
+     * It can then be restored from the deleted shares section.
1632
+     *
1633
+     * @param IShare $share the share to check
1634
+     * @return boolean
1635
+     *
1636
+     * @suppress PhanUndeclaredClassMethod
1637
+     */
1638
+    protected function canDeleteShareFromSelf(IShare $share): bool {
1639
+        if ($share->getShareType() !== IShare::TYPE_GROUP &&
1640
+            $share->getShareType() !== IShare::TYPE_ROOM &&
1641
+            $share->getShareType() !== IShare::TYPE_DECK &&
1642
+            $share->getShareType() !== IShare::TYPE_SCIENCEMESH
1643
+        ) {
1644
+            return false;
1645
+        }
1646
+
1647
+        if ($share->getShareOwner() === $this->userId ||
1648
+            $share->getSharedBy() === $this->userId
1649
+        ) {
1650
+            // Delete the whole share, not just for self
1651
+            return false;
1652
+        }
1653
+
1654
+        // If in the recipient group, you can delete the share from self
1655
+        if ($share->getShareType() === IShare::TYPE_GROUP) {
1656
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
1657
+            $user = $this->userManager->get($this->userId);
1658
+            if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1659
+                return true;
1660
+            }
1661
+        }
1662
+
1663
+        if ($share->getShareType() === IShare::TYPE_ROOM) {
1664
+            try {
1665
+                return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1666
+            } catch (ContainerExceptionInterface $e) {
1667
+                return false;
1668
+            }
1669
+        }
1670
+
1671
+        if ($share->getShareType() === IShare::TYPE_DECK) {
1672
+            try {
1673
+                return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1674
+            } catch (ContainerExceptionInterface $e) {
1675
+                return false;
1676
+            }
1677
+        }
1678
+
1679
+        if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1680
+            try {
1681
+                return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1682
+            } catch (ContainerExceptionInterface $e) {
1683
+                return false;
1684
+            }
1685
+        }
1686
+
1687
+        return false;
1688
+    }
1689
+
1690
+    /**
1691
+     * Make sure that the passed date is valid ISO 8601
1692
+     * So YYYY-MM-DD
1693
+     * If not throw an exception
1694
+     *
1695
+     * @param string $expireDate
1696
+     *
1697
+     * @throws \Exception
1698
+     * @return \DateTime
1699
+     */
1700
+    private function parseDate(string $expireDate): \DateTime {
1701
+        try {
1702
+            $date = new \DateTime(trim($expireDate, '"'), $this->dateTimeZone->getTimeZone());
1703
+            // Make sure it expires at midnight in owner timezone
1704
+            $date->setTime(0, 0, 0);
1705
+        } catch (\Exception $e) {
1706
+            throw new \Exception($this->l->t('Invalid date. Format must be YYYY-MM-DD'));
1707
+        }
1708
+
1709
+        return $date;
1710
+    }
1711
+
1712
+    /**
1713
+     * Since we have multiple providers but the OCS Share API v1 does
1714
+     * not support this we need to check all backends.
1715
+     *
1716
+     * @param string $id
1717
+     * @return IShare
1718
+     * @throws ShareNotFound
1719
+     */
1720
+    private function getShareById(string $id): IShare {
1721
+        $share = null;
1722
+
1723
+        // First check if it is an internal share.
1724
+        try {
1725
+            $share = $this->shareManager->getShareById('ocinternal:' . $id, $this->userId);
1726
+            return $share;
1727
+        } catch (ShareNotFound $e) {
1728
+            // Do nothing, just try the other share type
1729
+        }
1730
+
1731
+
1732
+        try {
1733
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1734
+                $share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->userId);
1735
+                return $share;
1736
+            }
1737
+        } catch (ShareNotFound $e) {
1738
+            // Do nothing, just try the other share type
1739
+        }
1740
+
1741
+        try {
1742
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1743
+                $share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->userId);
1744
+                return $share;
1745
+            }
1746
+        } catch (ShareNotFound $e) {
1747
+            // Do nothing, just try the other share type
1748
+        }
1749
+
1750
+        try {
1751
+            $share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->userId);
1752
+            return $share;
1753
+        } catch (ShareNotFound $e) {
1754
+            // Do nothing, just try the other share type
1755
+        }
1756
+
1757
+        try {
1758
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1759
+                $share = $this->shareManager->getShareById('deck:' . $id, $this->userId);
1760
+                return $share;
1761
+            }
1762
+        } catch (ShareNotFound $e) {
1763
+            // Do nothing, just try the other share type
1764
+        }
1765
+
1766
+        try {
1767
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
1768
+                $share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->userId);
1769
+                return $share;
1770
+            }
1771
+        } catch (ShareNotFound $e) {
1772
+            // Do nothing, just try the other share type
1773
+        }
1774
+
1775
+        if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1776
+            throw new ShareNotFound();
1777
+        }
1778
+        $share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->userId);
1779
+
1780
+        return $share;
1781
+    }
1782
+
1783
+    /**
1784
+     * Lock a Node
1785
+     *
1786
+     * @param Node $node
1787
+     * @throws LockedException
1788
+     */
1789
+    private function lock(Node $node) {
1790
+        $node->lock(ILockingProvider::LOCK_SHARED);
1791
+        $this->lockedNode = $node;
1792
+    }
1793
+
1794
+    /**
1795
+     * Cleanup the remaining locks
1796
+     * @throws LockedException
1797
+     */
1798
+    public function cleanup() {
1799
+        if ($this->lockedNode !== null) {
1800
+            $this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1801
+        }
1802
+    }
1803
+
1804
+    /**
1805
+     * Returns the helper of ShareAPIController for room shares.
1806
+     *
1807
+     * If the Talk application is not enabled or the helper is not available
1808
+     * a ContainerExceptionInterface is thrown instead.
1809
+     *
1810
+     * @return \OCA\Talk\Share\Helper\ShareAPIController
1811
+     * @throws ContainerExceptionInterface
1812
+     */
1813
+    private function getRoomShareHelper() {
1814
+        if (!$this->appManager->isEnabledForUser('spreed')) {
1815
+            throw new QueryException();
1816
+        }
1817
+
1818
+        return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
1819
+    }
1820
+
1821
+    /**
1822
+     * Returns the helper of ShareAPIHelper for deck shares.
1823
+     *
1824
+     * If the Deck application is not enabled or the helper is not available
1825
+     * a ContainerExceptionInterface is thrown instead.
1826
+     *
1827
+     * @return \OCA\Deck\Sharing\ShareAPIHelper
1828
+     * @throws ContainerExceptionInterface
1829
+     */
1830
+    private function getDeckShareHelper() {
1831
+        if (!$this->appManager->isEnabledForUser('deck')) {
1832
+            throw new QueryException();
1833
+        }
1834
+
1835
+        return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
1836
+    }
1837
+
1838
+    /**
1839
+     * Returns the helper of ShareAPIHelper for sciencemesh shares.
1840
+     *
1841
+     * If the sciencemesh application is not enabled or the helper is not available
1842
+     * a ContainerExceptionInterface is thrown instead.
1843
+     *
1844
+     * @return \OCA\Deck\Sharing\ShareAPIHelper
1845
+     * @throws ContainerExceptionInterface
1846
+     */
1847
+    private function getSciencemeshShareHelper() {
1848
+        if (!$this->appManager->isEnabledForUser('sciencemesh')) {
1849
+            throw new QueryException();
1850
+        }
1851
+
1852
+        return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
1853
+    }
1854
+
1855
+    /**
1856
+     * @param string $viewer
1857
+     * @param Node $node
1858
+     * @param bool $reShares
1859
+     *
1860
+     * @return IShare[]
1861
+     */
1862
+    private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
1863
+        $providers = [
1864
+            IShare::TYPE_USER,
1865
+            IShare::TYPE_GROUP,
1866
+            IShare::TYPE_LINK,
1867
+            IShare::TYPE_EMAIL,
1868
+            IShare::TYPE_CIRCLE,
1869
+            IShare::TYPE_ROOM,
1870
+            IShare::TYPE_DECK,
1871
+            IShare::TYPE_SCIENCEMESH
1872
+        ];
1873
+
1874
+        // Should we assume that the (currentUser) viewer is the owner of the node !?
1875
+        $shares = [];
1876
+        foreach ($providers as $provider) {
1877
+            if (!$this->shareManager->shareProviderExists($provider)) {
1878
+                continue;
1879
+            }
1880
+
1881
+            $providerShares =
1882
+                $this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
1883
+            $shares = array_merge($shares, $providerShares);
1884
+        }
1885
+
1886
+        if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1887
+            $federatedShares = $this->shareManager->getSharesBy(
1888
+                $this->userId, IShare::TYPE_REMOTE, $node, $reShares, -1, 0
1889
+            );
1890
+            $shares = array_merge($shares, $federatedShares);
1891
+        }
1892
+
1893
+        if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1894
+            $federatedShares = $this->shareManager->getSharesBy(
1895
+                $this->userId, IShare::TYPE_REMOTE_GROUP, $node, $reShares, -1, 0
1896
+            );
1897
+            $shares = array_merge($shares, $federatedShares);
1898
+        }
1899
+
1900
+        return $shares;
1901
+    }
1902
+
1903
+
1904
+    /**
1905
+     * @param Node $node
1906
+     *
1907
+     * @throws SharingRightsException
1908
+     */
1909
+    private function confirmSharingRights(Node $node): void {
1910
+        if (!$this->hasResharingRights($this->userId, $node)) {
1911
+            throw new SharingRightsException($this->l->t('No sharing rights on this item'));
1912
+        }
1913
+    }
1914
+
1915
+
1916
+    /**
1917
+     * @param string $viewer
1918
+     * @param Node $node
1919
+     *
1920
+     * @return bool
1921
+     */
1922
+    private function hasResharingRights($viewer, $node): bool {
1923
+        if ($viewer === $node->getOwner()->getUID()) {
1924
+            return true;
1925
+        }
1926
+
1927
+        foreach ([$node, $node->getParent()] as $node) {
1928
+            $shares = $this->getSharesFromNode($viewer, $node, true);
1929
+            foreach ($shares as $share) {
1930
+                try {
1931
+                    if ($this->shareProviderResharingRights($viewer, $share, $node)) {
1932
+                        return true;
1933
+                    }
1934
+                } catch (InvalidPathException|NotFoundException $e) {
1935
+                }
1936
+            }
1937
+        }
1938
+
1939
+        return false;
1940
+    }
1941
+
1942
+
1943
+    /**
1944
+     * Returns if we can find resharing rights in an IShare object for a specific user.
1945
+     *
1946
+     * @suppress PhanUndeclaredClassMethod
1947
+     *
1948
+     * @param string $userId
1949
+     * @param IShare $share
1950
+     * @param Node $node
1951
+     *
1952
+     * @return bool
1953
+     * @throws NotFoundException
1954
+     * @throws InvalidPathException
1955
+     */
1956
+    private function shareProviderResharingRights(string $userId, IShare $share, $node): bool {
1957
+        if ($share->getShareOwner() === $userId) {
1958
+            return true;
1959
+        }
1960
+
1961
+        // we check that current user have parent resharing rights on the current file
1962
+        if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) {
1963
+            return true;
1964
+        }
1965
+
1966
+        if ((Constants::PERMISSION_SHARE & $share->getPermissions()) === 0) {
1967
+            return false;
1968
+        }
1969
+
1970
+        if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $userId) {
1971
+            return true;
1972
+        }
1973
+
1974
+        if ($share->getShareType() === IShare::TYPE_GROUP && $this->groupManager->isInGroup($userId, $share->getSharedWith())) {
1975
+            return true;
1976
+        }
1977
+
1978
+        if ($share->getShareType() === IShare::TYPE_CIRCLE && Server::get(IAppManager::class)->isEnabledForUser('circles')
1979
+            && class_exists('\OCA\Circles\Api\v1\Circles')) {
1980
+            $hasCircleId = (str_ends_with($share->getSharedWith(), ']'));
1981
+            $shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
1982
+            $shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
1983
+            if ($shareWithLength === false) {
1984
+                $sharedWith = substr($share->getSharedWith(), $shareWithStart);
1985
+            } else {
1986
+                $sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
1987
+            }
1988
+            try {
1989
+                $member = Circles::getMember($sharedWith, $userId, 1);
1990
+                if ($member->getLevel() >= 4) {
1991
+                    return true;
1992
+                }
1993
+                return false;
1994
+            } catch (ContainerExceptionInterface $e) {
1995
+                return false;
1996
+            }
1997
+        }
1998
+
1999
+        return false;
2000
+    }
2001
+
2002
+    /**
2003
+     * Get all the shares for the current user
2004
+     *
2005
+     * @param Node|null $path
2006
+     * @param boolean $reshares
2007
+     * @return IShare[]
2008
+     */
2009
+    private function getAllShares(?Node $path = null, bool $reshares = false) {
2010
+        // Get all shares
2011
+        $userShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_USER, $path, $reshares, -1, 0);
2012
+        $groupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
2013
+        $linkShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_LINK, $path, $reshares, -1, 0);
2014
+
2015
+        // EMAIL SHARES
2016
+        $mailShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
2017
+
2018
+        // TEAM SHARES
2019
+        $circleShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
2020
+
2021
+        // TALK SHARES
2022
+        $roomShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
2023
+
2024
+        // DECK SHARES
2025
+        $deckShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_DECK, $path, $reshares, -1, 0);
2026
+
2027
+        // SCIENCEMESH SHARES
2028
+        $sciencemeshShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_SCIENCEMESH, $path, $reshares, -1, 0);
2029
+
2030
+        // FEDERATION
2031
+        if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
2032
+            $federatedShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
2033
+        } else {
2034
+            $federatedShares = [];
2035
+        }
2036
+        if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
2037
+            $federatedGroupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
2038
+        } else {
2039
+            $federatedGroupShares = [];
2040
+        }
2041
+
2042
+        return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares, $federatedShares, $federatedGroupShares);
2043
+    }
2044
+
2045
+
2046
+    /**
2047
+     * merging already formatted shares.
2048
+     * We'll make an associative array to easily detect duplicate Ids.
2049
+     * Keys _needs_ to be removed after all shares are retrieved and merged.
2050
+     *
2051
+     * @param array $shares
2052
+     * @param array $newShares
2053
+     */
2054
+    private function mergeFormattedShares(array &$shares, array $newShares) {
2055
+        foreach ($newShares as $newShare) {
2056
+            if (!array_key_exists($newShare['id'], $shares)) {
2057
+                $shares[$newShare['id']] = $newShare;
2058
+            }
2059
+        }
2060
+    }
2061
+
2062
+    /**
2063
+     * @param IShare $share
2064
+     * @param string|null $attributesString
2065
+     * @return IShare modified share
2066
+     */
2067
+    private function setShareAttributes(IShare $share, ?string $attributesString) {
2068
+        $newShareAttributes = null;
2069
+        if ($attributesString !== null) {
2070
+            $newShareAttributes = $this->shareManager->newShare()->newAttributes();
2071
+            $formattedShareAttributes = \json_decode($attributesString, true);
2072
+            if (is_array($formattedShareAttributes)) {
2073
+                foreach ($formattedShareAttributes as $formattedAttr) {
2074
+                    $newShareAttributes->setAttribute(
2075
+                        $formattedAttr['scope'],
2076
+                        $formattedAttr['key'],
2077
+                        $formattedAttr['value'],
2078
+                    );
2079
+                }
2080
+            } else {
2081
+                throw new OCSBadRequestException($this->l->t('Invalid share attributes provided: "%s"', [$attributesString]));
2082
+            }
2083
+        }
2084
+        $share->setAttributes($newShareAttributes);
2085
+
2086
+        return $share;
2087
+    }
2088
+
2089
+    private function checkInheritedAttributes(IShare $share): void {
2090
+        if (!$share->getSharedBy()) {
2091
+            return; // Probably in a test
2092
+        }
2093
+
2094
+        $canDownload = false;
2095
+        $hideDownload = true;
2096
+
2097
+        $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
2098
+        $nodes = $userFolder->getById($share->getNodeId());
2099
+        foreach ($nodes as $node) {
2100
+            // Owner always can download it - so allow it and break
2101
+            if ($node->getOwner()?->getUID() === $share->getSharedBy()) {
2102
+                $canDownload = true;
2103
+                $hideDownload = false;
2104
+                break;
2105
+            }
2106
+
2107
+            if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
2108
+                $storage = $node->getStorage();
2109
+                if ($storage instanceof Wrapper) {
2110
+                    $storage = $storage->getInstanceOfStorage(SharedStorage::class);
2111
+                    if ($storage === null) {
2112
+                        throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
2113
+                    }
2114
+                } else {
2115
+                    throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
2116
+                }
2117
+
2118
+                /** @var SharedStorage $storage */
2119
+                $originalShare = $storage->getShare();
2120
+                $inheritedAttributes = $originalShare->getAttributes();
2121
+                // hide if hidden and also the current share enforces hide (can only be false if one share is false or user is owner)
2122
+                $hideDownload = $hideDownload && $originalShare->getHideDownload();
2123
+                // allow download if already allowed by previous share or when the current share allows downloading
2124
+                $canDownload = $canDownload || $inheritedAttributes === null || $inheritedAttributes->getAttribute('permissions', 'download') !== false;
2125
+            }
2126
+        }
2127
+
2128
+        if ($hideDownload || !$canDownload) {
2129
+            $share->setHideDownload(true);
2130
+
2131
+            if (!$canDownload) {
2132
+                $attributes = $share->getAttributes() ?? $share->newAttributes();
2133
+                $attributes->setAttribute('permissions', 'download', false);
2134
+                $share->setAttributes($attributes);
2135
+            }
2136
+        }
2137
+    }
2138
+
2139
+    /**
2140
+     * Send a mail notification again for a share.
2141
+     * The mail_send option must be enabled for the given share.
2142
+     * @param string $id the share ID
2143
+     * @param string $password the password to check against. Necessary for password protected shares.
2144
+     * @throws OCSNotFoundException Share not found
2145
+     * @throws OCSForbiddenException You are not allowed to send mail notifications
2146
+     * @throws OCSBadRequestException Invalid request or wrong password
2147
+     * @throws OCSException Error while sending mail notification
2148
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
2149
+     *
2150
+     * 200: The email notification was sent successfully
2151
+     */
2152
+    #[NoAdminRequired]
2153
+    #[UserRateLimit(limit: 5, period: 120)]
2154
+    public function sendShareEmail(string $id, $password = ''): DataResponse {
2155
+        try {
2156
+            $share = $this->getShareById($id);
2157
+
2158
+            if (!$this->canAccessShare($share, false)) {
2159
+                throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2160
+            }
2161
+
2162
+            if (!$this->canEditShare($share)) {
2163
+                throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2164
+            }
2165
+
2166
+            // For mail and link shares, the user must be
2167
+            // the owner of the share, not only the file owner.
2168
+            if ($share->getShareType() === IShare::TYPE_EMAIL
2169
+                || $share->getShareType() === IShare::TYPE_LINK) {
2170
+                if ($share->getSharedBy() !== $this->userId) {
2171
+                    throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2172
+                }
2173
+            }
2174
+
2175
+            try {
2176
+                $provider = $this->factory->getProviderForType($share->getShareType());
2177
+                if (!($provider instanceof IShareProviderWithNotification)) {
2178
+                    throw new OCSBadRequestException($this->l->t('No mail notification configured for this share type'));
2179
+                }
2180
+
2181
+                // Circumvent the password encrypted data by
2182
+                // setting the password clear. We're not storing
2183
+                // the password clear, it is just a temporary
2184
+                // object manipulation. The password will stay
2185
+                // encrypted in the database.
2186
+                if ($share->getPassword() !== null && $share->getPassword() !== $password) {
2187
+                    if (!$this->shareManager->checkPassword($share, $password)) {
2188
+                        throw new OCSBadRequestException($this->l->t('Wrong password'));
2189
+                    }
2190
+                    $share = $share->setPassword($password);
2191
+                }
2192
+
2193
+                $provider->sendMailNotification($share);
2194
+                return new DataResponse();
2195
+            } catch (Exception $e) {
2196
+                $this->logger->error($e->getMessage(), ['exception' => $e]);
2197
+                throw new OCSException($this->l->t('Error while sending mail notification'));
2198
+            }
2199
+
2200
+        } catch (ShareNotFound $e) {
2201
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2202
+        }
2203
+    }
2204
+
2205
+    /**
2206
+     * Get a unique share token
2207
+     *
2208
+     * @throws OCSException Failed to generate a unique token
2209
+     *
2210
+     * @return DataResponse<Http::STATUS_OK, array{token: string}, array{}>
2211
+     *
2212
+     * 200: Token generated successfully
2213
+     */
2214
+    #[ApiRoute(verb: 'GET', url: '/api/v1/token')]
2215
+    #[NoAdminRequired]
2216
+    public function generateToken(): DataResponse {
2217
+        try {
2218
+            $token = $this->shareManager->generateToken();
2219
+            return new DataResponse([
2220
+                'token' => $token,
2221
+            ]);
2222
+        } catch (ShareTokenException $e) {
2223
+            throw new OCSException($this->l->t('Failed to generate a unique token'));
2224
+        }
2225
+    }
2226 2226
 }
Please login to merge, or discard this patch.
Spacing   +21 added lines, -21 removed lines patch added patch discarded remove patch
@@ -218,7 +218,7 @@  discard block
 block discarded – undo
218 218
 					'message' => $userStatus->getMessage(),
219 219
 					'icon' => $userStatus->getIcon(),
220 220
 					'clearAt' => $userStatus->getClearAt()
221
-						? (int)$userStatus->getClearAt()->format('U')
221
+						? (int) $userStatus->getClearAt()->format('U')
222 222
 						: null,
223 223
 				];
224 224
 			}
@@ -231,7 +231,7 @@  discard block
 block discarded – undo
231 231
 			// "share_with" and "share_with_displayname" for passwords of link
232 232
 			// shares was deprecated in Nextcloud 15, use "password" instead.
233 233
 			$result['share_with'] = $share->getPassword();
234
-			$result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
234
+			$result['share_with_displayname'] = '('.$this->l->t('Shared link').')';
235 235
 
236 236
 			$result['password'] = $share->getPassword();
237 237
 
@@ -312,7 +312,7 @@  discard block
 block discarded – undo
312 312
 
313 313
 		$result['attributes'] = null;
314 314
 		if ($attributes = $share->getAttributes()) {
315
-			$result['attributes'] = (string)\json_encode($attributes->toArray());
315
+			$result['attributes'] = (string) \json_encode($attributes->toArray());
316 316
 		}
317 317
 
318 318
 		return $result;
@@ -614,7 +614,7 @@  discard block
 block discarded – undo
614 614
 		} else {
615 615
 			// Use default permissions only for non-link shares to keep legacy behavior
616 616
 			if ($permissions === null) {
617
-				$permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
617
+				$permissions = (int) $this->config->getAppValue('core', 'shareapi_default_permissions', (string) Constants::PERMISSION_ALL);
618 618
 			}
619 619
 			// Non-link shares always require read permissions (link shares could be file drop)
620 620
 			$permissions |= Constants::PERMISSION_READ;
@@ -801,7 +801,7 @@  discard block
 block discarded – undo
801 801
 		} catch (HintException $e) {
802 802
 			$code = $e->getCode() === 0 ? 403 : $e->getCode();
803 803
 			throw new OCSException($e->getHint(), $code);
804
-		} catch (GenericShareException|\InvalidArgumentException $e) {
804
+		} catch (GenericShareException | \InvalidArgumentException $e) {
805 805
 			$this->logger->error($e->getMessage(), ['exception' => $e]);
806 806
 			throw new OCSForbiddenException($e->getMessage(), $e);
807 807
 		} catch (\Exception $e) {
@@ -830,7 +830,7 @@  discard block
 block discarded – undo
830 830
 
831 831
 		$shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares);
832 832
 
833
-		$filteredShares = array_filter($shares, function (IShare $share) {
833
+		$filteredShares = array_filter($shares, function(IShare $share) {
834 834
 			return $share->getShareOwner() !== $this->userId;
835 835
 		});
836 836
 
@@ -867,7 +867,7 @@  discard block
 block discarded – undo
867 867
 		$nodes = $folder->getDirectoryListing();
868 868
 
869 869
 		/** @var IShare[] $shares */
870
-		$shares = array_reduce($nodes, function ($carry, $node) {
870
+		$shares = array_reduce($nodes, function($carry, $node) {
871 871
 			$carry = array_merge($carry, $this->getAllShares($node, true));
872 872
 			return $carry;
873 873
 		}, []);
@@ -1087,7 +1087,7 @@  discard block
 block discarded – undo
1087 1087
 				if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $node)) {
1088 1088
 					$resharingRight = true;
1089 1089
 				}
1090
-			} catch (InvalidPathException|NotFoundException $e) {
1090
+			} catch (InvalidPathException | NotFoundException $e) {
1091 1091
 			}
1092 1092
 		}
1093 1093
 
@@ -1369,7 +1369,7 @@  discard block
 block discarded – undo
1369 1369
 			$share = $this->shareManager->updateShare($share);
1370 1370
 		} catch (HintException $e) {
1371 1371
 			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1372
-			throw new OCSException($e->getHint(), (int)$code);
1372
+			throw new OCSException($e->getHint(), (int) $code);
1373 1373
 		} catch (\Exception $e) {
1374 1374
 			$this->logger->error($e->getMessage(), ['exception' => $e]);
1375 1375
 			throw new OCSBadRequestException('Failed to update share.', $e);
@@ -1414,7 +1414,7 @@  discard block
 block discarded – undo
1414 1414
 			}
1415 1415
 		}
1416 1416
 
1417
-		$result = array_values(array_filter(array_map(function (IShare $share) {
1417
+		$result = array_values(array_filter(array_map(function(IShare $share) {
1418 1418
 			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1419 1419
 			$node = $userFolder->getFirstNodeById($share->getNodeId());
1420 1420
 			if (!$node) {
@@ -1427,13 +1427,13 @@  discard block
 block discarded – undo
1427 1427
 
1428 1428
 			try {
1429 1429
 				$formattedShare = $this->formatShare($share, $node);
1430
-				$formattedShare['path'] = '/' . $share->getNode()->getName();
1430
+				$formattedShare['path'] = '/'.$share->getNode()->getName();
1431 1431
 				$formattedShare['permissions'] = 0;
1432 1432
 				return $formattedShare;
1433 1433
 			} catch (NotFoundException $e) {
1434 1434
 				return null;
1435 1435
 			}
1436
-		}, $pendingShares), function ($entry) {
1436
+		}, $pendingShares), function($entry) {
1437 1437
 			return $entry !== null;
1438 1438
 		}));
1439 1439
 
@@ -1467,7 +1467,7 @@  discard block
 block discarded – undo
1467 1467
 			$this->shareManager->acceptShare($share, $this->userId);
1468 1468
 		} catch (HintException $e) {
1469 1469
 			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1470
-			throw new OCSException($e->getHint(), (int)$code);
1470
+			throw new OCSException($e->getHint(), (int) $code);
1471 1471
 		} catch (\Exception $e) {
1472 1472
 			$this->logger->error($e->getMessage(), ['exception' => $e]);
1473 1473
 			throw new OCSBadRequestException('Failed to accept share.', $e);
@@ -1722,7 +1722,7 @@  discard block
 block discarded – undo
1722 1722
 
1723 1723
 		// First check if it is an internal share.
1724 1724
 		try {
1725
-			$share = $this->shareManager->getShareById('ocinternal:' . $id, $this->userId);
1725
+			$share = $this->shareManager->getShareById('ocinternal:'.$id, $this->userId);
1726 1726
 			return $share;
1727 1727
 		} catch (ShareNotFound $e) {
1728 1728
 			// Do nothing, just try the other share type
@@ -1731,7 +1731,7 @@  discard block
 block discarded – undo
1731 1731
 
1732 1732
 		try {
1733 1733
 			if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1734
-				$share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->userId);
1734
+				$share = $this->shareManager->getShareById('ocCircleShare:'.$id, $this->userId);
1735 1735
 				return $share;
1736 1736
 			}
1737 1737
 		} catch (ShareNotFound $e) {
@@ -1740,7 +1740,7 @@  discard block
 block discarded – undo
1740 1740
 
1741 1741
 		try {
1742 1742
 			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1743
-				$share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->userId);
1743
+				$share = $this->shareManager->getShareById('ocMailShare:'.$id, $this->userId);
1744 1744
 				return $share;
1745 1745
 			}
1746 1746
 		} catch (ShareNotFound $e) {
@@ -1748,7 +1748,7 @@  discard block
 block discarded – undo
1748 1748
 		}
1749 1749
 
1750 1750
 		try {
1751
-			$share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->userId);
1751
+			$share = $this->shareManager->getShareById('ocRoomShare:'.$id, $this->userId);
1752 1752
 			return $share;
1753 1753
 		} catch (ShareNotFound $e) {
1754 1754
 			// Do nothing, just try the other share type
@@ -1756,7 +1756,7 @@  discard block
 block discarded – undo
1756 1756
 
1757 1757
 		try {
1758 1758
 			if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1759
-				$share = $this->shareManager->getShareById('deck:' . $id, $this->userId);
1759
+				$share = $this->shareManager->getShareById('deck:'.$id, $this->userId);
1760 1760
 				return $share;
1761 1761
 			}
1762 1762
 		} catch (ShareNotFound $e) {
@@ -1765,7 +1765,7 @@  discard block
 block discarded – undo
1765 1765
 
1766 1766
 		try {
1767 1767
 			if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
1768
-				$share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->userId);
1768
+				$share = $this->shareManager->getShareById('sciencemesh:'.$id, $this->userId);
1769 1769
 				return $share;
1770 1770
 			}
1771 1771
 		} catch (ShareNotFound $e) {
@@ -1775,7 +1775,7 @@  discard block
 block discarded – undo
1775 1775
 		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1776 1776
 			throw new ShareNotFound();
1777 1777
 		}
1778
-		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->userId);
1778
+		$share = $this->shareManager->getShareById('ocFederatedSharing:'.$id, $this->userId);
1779 1779
 
1780 1780
 		return $share;
1781 1781
 	}
@@ -1931,7 +1931,7 @@  discard block
 block discarded – undo
1931 1931
 					if ($this->shareProviderResharingRights($viewer, $share, $node)) {
1932 1932
 						return true;
1933 1933
 					}
1934
-				} catch (InvalidPathException|NotFoundException $e) {
1934
+				} catch (InvalidPathException | NotFoundException $e) {
1935 1935
 				}
1936 1936
 			}
1937 1937
 		}
Please login to merge, or discard this patch.
tests/lib/Share20/ManagerTest.php 1 patch
Indentation   +4702 added lines, -4702 removed lines patch added patch discarded remove patch
@@ -66,4905 +66,4905 @@
 block discarded – undo
66 66
  * @group DB
67 67
  */
68 68
 class ManagerTest extends \Test\TestCase {
69
-	/** @var Manager */
70
-	protected $manager;
71
-	/** @var LoggerInterface|MockObject */
72
-	protected $logger;
73
-	/** @var IConfig|MockObject */
74
-	protected $config;
75
-	/** @var ISecureRandom|MockObject */
76
-	protected $secureRandom;
77
-	/** @var IHasher|MockObject */
78
-	protected $hasher;
79
-	/** @var IShareProvider|MockObject */
80
-	protected $defaultProvider;
81
-	/** @var IMountManager|MockObject */
82
-	protected $mountManager;
83
-	/** @var IGroupManager|MockObject */
84
-	protected $groupManager;
85
-	/** @var IL10N|MockObject */
86
-	protected $l;
87
-	/** @var IFactory|MockObject */
88
-	protected $l10nFactory;
89
-	/** @var DummyFactory */
90
-	protected $factory;
91
-	/** @var IUserManager|MockObject */
92
-	protected $userManager;
93
-	/** @var IRootFolder | MockObject */
94
-	protected $rootFolder;
95
-	/** @var IEventDispatcher|MockObject */
96
-	protected $dispatcher;
97
-	/** @var IMailer|MockObject */
98
-	protected $mailer;
99
-	/** @var IURLGenerator|MockObject */
100
-	protected $urlGenerator;
101
-	/** @var \OC_Defaults|MockObject */
102
-	protected $defaults;
103
-	/** @var IUserSession|MockObject */
104
-	protected $userSession;
105
-	/** @var KnownUserService|MockObject */
106
-	protected $knownUserService;
107
-	/** @var ShareDisableChecker|MockObject */
108
-	protected $shareDisabledChecker;
109
-	private DateTimeZone $timezone;
110
-	/** @var IDateTimeZone|MockObject */
111
-	protected $dateTimeZone;
112
-	/** @var IAppConfig|MockObject */
113
-	protected $appConfig;
114
-
115
-	protected function setUp(): void {
116
-		$this->logger = $this->createMock(LoggerInterface::class);
117
-		$this->config = $this->createMock(IConfig::class);
118
-		$this->secureRandom = $this->createMock(ISecureRandom::class);
119
-		$this->hasher = $this->createMock(IHasher::class);
120
-		$this->mountManager = $this->createMock(IMountManager::class);
121
-		$this->groupManager = $this->createMock(IGroupManager::class);
122
-		$this->userManager = $this->createMock(IUserManager::class);
123
-		$this->rootFolder = $this->createMock(IRootFolder::class);
124
-		$this->mailer = $this->createMock(IMailer::class);
125
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
126
-		$this->defaults = $this->createMock(\OC_Defaults::class);
127
-		$this->dispatcher = $this->createMock(IEventDispatcher::class);
128
-		$this->userSession = $this->createMock(IUserSession::class);
129
-		$this->knownUserService = $this->createMock(KnownUserService::class);
130
-
131
-		$this->shareDisabledChecker = new ShareDisableChecker($this->config, $this->userManager, $this->groupManager);
132
-		$this->dateTimeZone = $this->createMock(IDateTimeZone::class);
133
-		$this->timezone = new \DateTimeZone('Pacific/Auckland');
134
-		$this->dateTimeZone->method('getTimeZone')->willReturnCallback(fn () => $this->timezone);
135
-
136
-		$this->appConfig = $this->createMock(IAppConfig::class);
137
-
138
-		$this->l10nFactory = $this->createMock(IFactory::class);
139
-		$this->l = $this->createMock(IL10N::class);
140
-		$this->l->method('t')
141
-			->willReturnCallback(function ($text, $parameters = []) {
142
-				return vsprintf($text, $parameters);
143
-			});
144
-		$this->l->method('n')
145
-			->willReturnCallback(function ($singular, $plural, $count, $parameters = []) {
146
-				return vsprintf(str_replace('%n', $count, ($count === 1) ? $singular : $plural), $parameters);
147
-			});
148
-		$this->l10nFactory->method('get')->willReturn($this->l);
149
-
150
-		$this->factory = new DummyFactory(\OC::$server);
151
-
152
-		$this->manager = $this->createManager($this->factory);
153
-
154
-		$this->defaultProvider = $this->createMock(DefaultShareProvider::class);
155
-		$this->defaultProvider->method('identifier')->willReturn('default');
156
-		$this->factory->setProvider($this->defaultProvider);
157
-	}
158
-
159
-	private function createManager(IProviderFactory $factory): Manager {
160
-		return new Manager(
161
-			$this->logger,
162
-			$this->config,
163
-			$this->secureRandom,
164
-			$this->hasher,
165
-			$this->mountManager,
166
-			$this->groupManager,
167
-			$this->l10nFactory,
168
-			$factory,
169
-			$this->userManager,
170
-			$this->rootFolder,
171
-			$this->mailer,
172
-			$this->urlGenerator,
173
-			$this->defaults,
174
-			$this->dispatcher,
175
-			$this->userSession,
176
-			$this->knownUserService,
177
-			$this->shareDisabledChecker,
178
-			$this->dateTimeZone,
179
-			$this->appConfig,
180
-		);
181
-	}
182
-
183
-	/**
184
-	 * @return MockBuilder
185
-	 */
186
-	private function createManagerMock() {
187
-		return $this->getMockBuilder(Manager::class)
188
-			->setConstructorArgs([
189
-				$this->logger,
190
-				$this->config,
191
-				$this->secureRandom,
192
-				$this->hasher,
193
-				$this->mountManager,
194
-				$this->groupManager,
195
-				$this->l10nFactory,
196
-				$this->factory,
197
-				$this->userManager,
198
-				$this->rootFolder,
199
-				$this->mailer,
200
-				$this->urlGenerator,
201
-				$this->defaults,
202
-				$this->dispatcher,
203
-				$this->userSession,
204
-				$this->knownUserService,
205
-				$this->shareDisabledChecker,
206
-				$this->dateTimeZone,
207
-				$this->appConfig,
208
-			]);
209
-	}
210
-
211
-	private function createFolderMock(string $folderPath): MockObject&Folder {
212
-		$folder = $this->createMock(Folder::class);
213
-		$folder->method('getPath')->willReturn($folderPath);
214
-		$folder->method('getRelativePath')->willReturnCallback(
215
-			fn (string $path): ?string => PathHelper::getRelativePath($folderPath, $path)
216
-		);
217
-		return $folder;
218
-	}
219
-
220
-	public function testDeleteNoShareId(): void {
221
-		$this->expectException(\InvalidArgumentException::class);
222
-
223
-		$share = $this->manager->newShare();
224
-
225
-		$this->manager->deleteShare($share);
226
-	}
227
-
228
-	public function dataTestDelete() {
229
-		$user = $this->createMock(IUser::class);
230
-		$user->method('getUID')->willReturn('sharedWithUser');
231
-
232
-		$group = $this->createMock(IGroup::class);
233
-		$group->method('getGID')->willReturn('sharedWithGroup');
234
-
235
-		return [
236
-			[IShare::TYPE_USER, 'sharedWithUser'],
237
-			[IShare::TYPE_GROUP, 'sharedWithGroup'],
238
-			[IShare::TYPE_LINK, ''],
239
-			[IShare::TYPE_REMOTE, '[email protected]'],
240
-		];
241
-	}
242
-
243
-	/**
244
-	 * @dataProvider dataTestDelete
245
-	 */
246
-	public function testDelete($shareType, $sharedWith): void {
247
-		$manager = $this->createManagerMock()
248
-			->setMethods(['getShareById', 'deleteChildren', 'promoteReshares'])
249
-			->getMock();
250
-
251
-		$manager->method('deleteChildren')->willReturn([]);
252
-
253
-		$path = $this->createMock(File::class);
254
-		$path->method('getId')->willReturn(1);
255
-
256
-		$share = $this->manager->newShare();
257
-		$share->setId(42)
258
-			->setProviderId('prov')
259
-			->setShareType($shareType)
260
-			->setSharedWith($sharedWith)
261
-			->setSharedBy('sharedBy')
262
-			->setNode($path)
263
-			->setTarget('myTarget');
264
-
265
-		$manager->expects($this->once())->method('deleteChildren')->with($share);
266
-		$manager->expects($this->once())->method('promoteReshares')->with($share);
267
-
268
-		$this->defaultProvider
269
-			->expects($this->once())
270
-			->method('delete')
271
-			->with($share);
272
-
273
-		$this->dispatcher->expects($this->exactly(2))
274
-			->method('dispatchTyped')
275
-			->withConsecutive(
276
-				[
277
-					$this->callBack(function (BeforeShareDeletedEvent $e) use ($share) {
278
-						return $e->getShare() === $share;
279
-					})],
280
-				[
281
-					$this->callBack(function (ShareDeletedEvent $e) use ($share) {
282
-						return $e->getShare() === $share;
283
-					})]
284
-			);
285
-
286
-		$manager->deleteShare($share);
287
-	}
288
-
289
-	public function testDeleteLazyShare(): void {
290
-		$manager = $this->createManagerMock()
291
-			->setMethods(['getShareById', 'deleteChildren', 'promoteReshares'])
292
-			->getMock();
293
-
294
-		$manager->method('deleteChildren')->willReturn([]);
295
-
296
-		$share = $this->manager->newShare();
297
-		$share->setId(42)
298
-			->setProviderId('prov')
299
-			->setShareType(IShare::TYPE_USER)
300
-			->setSharedWith('sharedWith')
301
-			->setSharedBy('sharedBy')
302
-			->setShareOwner('shareOwner')
303
-			->setTarget('myTarget')
304
-			->setNodeId(1)
305
-			->setNodeType('file');
306
-
307
-		$this->rootFolder->expects($this->never())->method($this->anything());
308
-
309
-		$manager->expects($this->once())->method('deleteChildren')->with($share);
310
-		$manager->expects($this->once())->method('promoteReshares')->with($share);
311
-
312
-		$this->defaultProvider
313
-			->expects($this->once())
314
-			->method('delete')
315
-			->with($share);
316
-
317
-		$this->dispatcher->expects($this->exactly(2))
318
-			->method('dispatchTyped')
319
-			->withConsecutive(
320
-				[
321
-					$this->callBack(function (BeforeShareDeletedEvent $e) use ($share) {
322
-						return $e->getShare() === $share;
323
-					})],
324
-				[
325
-					$this->callBack(function (ShareDeletedEvent $e) use ($share) {
326
-						return $e->getShare() === $share;
327
-					})]
328
-			);
329
-
330
-		$manager->deleteShare($share);
331
-	}
332
-
333
-	public function testDeleteNested(): void {
334
-		$manager = $this->createManagerMock()
335
-			->setMethods(['getShareById', 'promoteReshares'])
336
-			->getMock();
337
-
338
-		$path = $this->createMock(File::class);
339
-		$path->method('getId')->willReturn(1);
340
-
341
-		$share1 = $this->manager->newShare();
342
-		$share1->setId(42)
343
-			->setProviderId('prov')
344
-			->setShareType(IShare::TYPE_USER)
345
-			->setSharedWith('sharedWith1')
346
-			->setSharedBy('sharedBy1')
347
-			->setNode($path)
348
-			->setTarget('myTarget1');
349
-
350
-		$share2 = $this->manager->newShare();
351
-		$share2->setId(43)
352
-			->setProviderId('prov')
353
-			->setShareType(IShare::TYPE_GROUP)
354
-			->setSharedWith('sharedWith2')
355
-			->setSharedBy('sharedBy2')
356
-			->setNode($path)
357
-			->setTarget('myTarget2')
358
-			->setParent(42);
359
-
360
-		$share3 = $this->manager->newShare();
361
-		$share3->setId(44)
362
-			->setProviderId('prov')
363
-			->setShareType(IShare::TYPE_LINK)
364
-			->setSharedBy('sharedBy3')
365
-			->setNode($path)
366
-			->setTarget('myTarget3')
367
-			->setParent(43);
368
-
369
-		$this->defaultProvider
370
-			->method('getChildren')
371
-			->willReturnMap([
372
-				[$share1, [$share2]],
373
-				[$share2, [$share3]],
374
-				[$share3, []],
375
-			]);
376
-
377
-		$this->defaultProvider
378
-			->method('delete')
379
-			->withConsecutive([$share3], [$share2], [$share1]);
380
-
381
-		$this->dispatcher->expects($this->exactly(6))
382
-			->method('dispatchTyped')
383
-			->withConsecutive(
384
-				[
385
-					$this->callBack(function (BeforeShareDeletedEvent $e) use ($share1) {
386
-						return $e->getShare()->getId() === $share1->getId();
387
-					})
388
-				],
389
-				[
390
-					$this->callBack(function (BeforeShareDeletedEvent $e) use ($share2) {
391
-						return $e->getShare()->getId() === $share2->getId();
392
-					})
393
-				],
394
-				[
395
-					$this->callBack(function (BeforeShareDeletedEvent $e) use ($share3) {
396
-						return $e->getShare()->getId() === $share3->getId();
397
-					})
398
-				],
399
-				[
400
-					$this->callBack(function (ShareDeletedEvent $e) use ($share3) {
401
-						return $e->getShare()->getId() === $share3->getId();
402
-					})
403
-				],
404
-				[
405
-					$this->callBack(function (ShareDeletedEvent $e) use ($share2) {
406
-						return $e->getShare()->getId() === $share2->getId();
407
-					})
408
-				],
409
-				[
410
-					$this->callBack(function (ShareDeletedEvent $e) use ($share1) {
411
-						return $e->getShare()->getId() === $share1->getId();
412
-					})
413
-				],
414
-			);
415
-
416
-		$manager->deleteShare($share1);
417
-	}
418
-
419
-	public function testDeleteFromSelf(): void {
420
-		$manager = $this->createManagerMock()
421
-			->setMethods(['getShareById'])
422
-			->getMock();
423
-
424
-		$recipientId = 'unshareFrom';
425
-		$share = $this->manager->newShare();
426
-		$share->setId(42)
427
-			->setProviderId('prov')
428
-			->setShareType(IShare::TYPE_USER)
429
-			->setSharedWith('sharedWith')
430
-			->setSharedBy('sharedBy')
431
-			->setShareOwner('shareOwner')
432
-			->setTarget('myTarget')
433
-			->setNodeId(1)
434
-			->setNodeType('file');
435
-
436
-		$this->defaultProvider
437
-			->expects($this->once())
438
-			->method('deleteFromSelf')
439
-			->with($share, $recipientId);
440
-
441
-		$this->dispatcher->expects($this->once())
442
-			->method('dispatchTyped')
443
-			->with(
444
-				$this->callBack(function (ShareDeletedFromSelfEvent $e) use ($share) {
445
-					return $e->getShare() === $share;
446
-				})
447
-			);
448
-
449
-		$manager->deleteFromSelf($share, $recipientId);
450
-	}
451
-
452
-	public function testDeleteChildren(): void {
453
-		$manager = $this->createManagerMock()
454
-			->setMethods(['deleteShare'])
455
-			->getMock();
456
-
457
-		$share = $this->createMock(IShare::class);
458
-		$share->method('getShareType')->willReturn(IShare::TYPE_USER);
459
-
460
-		$child1 = $this->createMock(IShare::class);
461
-		$child1->method('getShareType')->willReturn(IShare::TYPE_USER);
462
-		$child2 = $this->createMock(IShare::class);
463
-		$child2->method('getShareType')->willReturn(IShare::TYPE_USER);
464
-		$child3 = $this->createMock(IShare::class);
465
-		$child3->method('getShareType')->willReturn(IShare::TYPE_USER);
466
-
467
-		$shares = [
468
-			$child1,
469
-			$child2,
470
-			$child3,
471
-		];
472
-
473
-		$this->defaultProvider
474
-			->expects($this->exactly(4))
475
-			->method('getChildren')
476
-			->willReturnCallback(function ($_share) use ($share, $shares) {
477
-				if ($_share === $share) {
478
-					return $shares;
479
-				}
480
-				return [];
481
-			});
482
-
483
-		$this->defaultProvider
484
-			->expects($this->exactly(3))
485
-			->method('delete')
486
-			->withConsecutive([$child1], [$child2], [$child3]);
487
-
488
-		$result = self::invokePrivate($manager, 'deleteChildren', [$share]);
489
-		$this->assertSame($shares, $result);
490
-	}
491
-
492
-	public function testPromoteReshareFile(): void {
493
-		$manager = $this->createManagerMock()
494
-			->setMethods(['updateShare', 'getSharesInFolder', 'generalCreateChecks'])
495
-			->getMock();
496
-
497
-		$file = $this->createMock(File::class);
498
-
499
-		$share = $this->createMock(IShare::class);
500
-		$share->method('getShareType')->willReturn(IShare::TYPE_USER);
501
-		$share->method('getNodeType')->willReturn('folder');
502
-		$share->method('getSharedWith')->willReturn('userB');
503
-		$share->method('getNode')->willReturn($file);
504
-
505
-		$reShare = $this->createMock(IShare::class);
506
-		$reShare->method('getShareType')->willReturn(IShare::TYPE_USER);
507
-		$reShare->method('getSharedBy')->willReturn('userB');
508
-		$reShare->method('getSharedWith')->willReturn('userC');
509
-		$reShare->method('getNode')->willReturn($file);
510
-
511
-		$this->defaultProvider->method('getSharesBy')
512
-			->willReturnCallback(function ($userId, $shareType, $node, $reshares, $limit, $offset) use ($reShare, $file) {
513
-				$this->assertEquals($file, $node);
514
-				if ($shareType === IShare::TYPE_USER) {
515
-					return match($userId) {
516
-						'userB' => [$reShare],
517
-					};
518
-				} else {
519
-					return [];
520
-				}
521
-			});
522
-		$manager->method('generalCreateChecks')->willThrowException(new GenericShareException());
523
-
524
-		$manager->expects($this->exactly(1))->method('updateShare')->with($reShare);
525
-
526
-		self::invokePrivate($manager, 'promoteReshares', [$share]);
527
-	}
528
-
529
-	public function testPromoteReshare(): void {
530
-		$manager = $this->createManagerMock()
531
-			->setMethods(['updateShare', 'getSharesInFolder', 'generalCreateChecks'])
532
-			->getMock();
533
-
534
-		$folder = $this->createFolderMock('/path/to/folder');
535
-
536
-		$subFolder = $this->createFolderMock('/path/to/folder/sub');
537
-
538
-		$otherFolder = $this->createFolderMock('/path/to/otherfolder/');
539
-
540
-		$share = $this->createMock(IShare::class);
541
-		$share->method('getShareType')->willReturn(IShare::TYPE_USER);
542
-		$share->method('getNodeType')->willReturn('folder');
543
-		$share->method('getSharedWith')->willReturn('userB');
544
-		$share->method('getNode')->willReturn($folder);
545
-
546
-		$reShare = $this->createMock(IShare::class);
547
-		$reShare->method('getShareType')->willReturn(IShare::TYPE_USER);
548
-		$reShare->method('getSharedBy')->willReturn('userB');
549
-		$reShare->method('getSharedWith')->willReturn('userC');
550
-		$reShare->method('getNode')->willReturn($folder);
551
-
552
-		$reShareInSubFolder = $this->createMock(IShare::class);
553
-		$reShareInSubFolder->method('getShareType')->willReturn(IShare::TYPE_USER);
554
-		$reShareInSubFolder->method('getSharedBy')->willReturn('userB');
555
-		$reShareInSubFolder->method('getNode')->willReturn($subFolder);
556
-
557
-		$reShareInOtherFolder = $this->createMock(IShare::class);
558
-		$reShareInOtherFolder->method('getShareType')->willReturn(IShare::TYPE_USER);
559
-		$reShareInOtherFolder->method('getSharedBy')->willReturn('userB');
560
-		$reShareInOtherFolder->method('getNode')->willReturn($otherFolder);
561
-
562
-		$this->defaultProvider->method('getSharesBy')
563
-			->willReturnCallback(function ($userId, $shareType, $node, $reshares, $limit, $offset) use ($reShare, $reShareInSubFolder, $reShareInOtherFolder) {
564
-				if ($shareType === IShare::TYPE_USER) {
565
-					return match($userId) {
566
-						'userB' => [$reShare,$reShareInSubFolder,$reShareInOtherFolder],
567
-					};
568
-				} else {
569
-					return [];
570
-				}
571
-			});
572
-		$manager->method('generalCreateChecks')->willThrowException(new GenericShareException());
573
-
574
-		$manager->expects($this->exactly(2))->method('updateShare')->withConsecutive([$reShare], [$reShareInSubFolder]);
575
-
576
-		self::invokePrivate($manager, 'promoteReshares', [$share]);
577
-	}
578
-
579
-	public function testPromoteReshareWhenUserHasAnotherShare(): void {
580
-		$manager = $this->createManagerMock()
581
-			->setMethods(['updateShare', 'getSharesInFolder', 'getSharedWith', 'generalCreateChecks'])
582
-			->getMock();
583
-
584
-		$folder = $this->createFolderMock('/path/to/folder');
585
-
586
-		$share = $this->createMock(IShare::class);
587
-		$share->method('getShareType')->willReturn(IShare::TYPE_USER);
588
-		$share->method('getNodeType')->willReturn('folder');
589
-		$share->method('getSharedWith')->willReturn('userB');
590
-		$share->method('getNode')->willReturn($folder);
591
-
592
-		$reShare = $this->createMock(IShare::class);
593
-		$reShare->method('getShareType')->willReturn(IShare::TYPE_USER);
594
-		$reShare->method('getNodeType')->willReturn('folder');
595
-		$reShare->method('getSharedBy')->willReturn('userB');
596
-		$reShare->method('getNode')->willReturn($folder);
597
-
598
-		$this->defaultProvider->method('getSharesBy')->willReturn([$reShare]);
599
-		$manager->method('generalCreateChecks')->willReturn(true);
600
-
601
-		/* No share is promoted because generalCreateChecks does not throw */
602
-		$manager->expects($this->never())->method('updateShare');
603
-
604
-		self::invokePrivate($manager, 'promoteReshares', [$share]);
605
-	}
606
-
607
-	public function testPromoteReshareOfUsersInGroupShare(): void {
608
-		$manager = $this->createManagerMock()
609
-			->setMethods(['updateShare', 'getSharesInFolder', 'getSharedWith', 'generalCreateChecks'])
610
-			->getMock();
611
-
612
-		$folder = $this->createFolderMock('/path/to/folder');
613
-
614
-		$userA = $this->createMock(IUser::class);
615
-		$userA->method('getUID')->willReturn('userA');
616
-
617
-		$share = $this->createMock(IShare::class);
618
-		$share->method('getShareType')->willReturn(IShare::TYPE_GROUP);
619
-		$share->method('getNodeType')->willReturn('folder');
620
-		$share->method('getSharedWith')->willReturn('Group');
621
-		$share->method('getNode')->willReturn($folder);
622
-		$share->method('getShareOwner')->willReturn($userA);
623
-
624
-		$reShare1 = $this->createMock(IShare::class);
625
-		$reShare1->method('getShareType')->willReturn(IShare::TYPE_USER);
626
-		$reShare1->method('getNodeType')->willReturn('folder');
627
-		$reShare1->method('getSharedBy')->willReturn('userB');
628
-		$reShare1->method('getNode')->willReturn($folder);
629
-
630
-		$reShare2 = $this->createMock(IShare::class);
631
-		$reShare2->method('getShareType')->willReturn(IShare::TYPE_USER);
632
-		$reShare2->method('getNodeType')->willReturn('folder');
633
-		$reShare2->method('getSharedBy')->willReturn('userC');
634
-		$reShare2->method('getNode')->willReturn($folder);
635
-
636
-		$userB = $this->createMock(IUser::class);
637
-		$userB->method('getUID')->willReturn('userB');
638
-		$userC = $this->createMock(IUser::class);
639
-		$userC->method('getUID')->willReturn('userC');
640
-		$group = $this->createMock(IGroup::class);
641
-		$group->method('getUsers')->willReturn([$userB, $userC]);
642
-		$this->groupManager->method('get')->with('Group')->willReturn($group);
643
-
644
-		$this->defaultProvider->method('getSharesBy')
645
-			->willReturnCallback(function ($userId, $shareType, $node, $reshares, $limit, $offset) use ($reShare1, $reShare2) {
646
-				if ($shareType === IShare::TYPE_USER) {
647
-					return match($userId) {
648
-						'userB' => [$reShare1],
649
-						'userC' => [$reShare2],
650
-					};
651
-				} else {
652
-					return [];
653
-				}
654
-			});
655
-		$manager->method('generalCreateChecks')->willThrowException(new GenericShareException());
656
-
657
-		$manager->method('getSharedWith')->willReturn([]);
658
-
659
-		$manager->expects($this->exactly(2))->method('updateShare')->withConsecutive([$reShare1], [$reShare2]);
660
-
661
-		self::invokePrivate($manager, 'promoteReshares', [$share]);
662
-	}
663
-
664
-	public function testGetShareById(): void {
665
-		$share = $this->createMock(IShare::class);
666
-
667
-		$this->defaultProvider
668
-			->expects($this->once())
669
-			->method('getShareById')
670
-			->with(42)
671
-			->willReturn($share);
672
-
673
-		$this->assertEquals($share, $this->manager->getShareById('default:42'));
674
-	}
675
-
676
-
677
-	public function testGetExpiredShareById(): void {
678
-		$this->expectException(\OCP\Share\Exceptions\ShareNotFound::class);
679
-
680
-		$manager = $this->createManagerMock()
681
-			->setMethods(['deleteShare'])
682
-			->getMock();
683
-
684
-		$date = new \DateTime();
685
-		$date->setTime(0, 0, 0);
686
-
687
-		$share = $this->manager->newShare();
688
-		$share->setExpirationDate($date)
689
-			->setShareType(IShare::TYPE_LINK);
690
-
691
-		$this->defaultProvider->expects($this->once())
692
-			->method('getShareById')
693
-			->with('42')
694
-			->willReturn($share);
695
-
696
-		$manager->expects($this->once())
697
-			->method('deleteShare')
698
-			->with($share);
699
-
700
-		$manager->getShareById('default:42');
701
-	}
702
-
703
-
704
-	public function testVerifyPasswordNullButEnforced(): void {
705
-		$this->expectException(\InvalidArgumentException::class);
706
-		$this->expectExceptionMessage('Passwords are enforced for link and mail shares');
707
-
708
-		$this->config->method('getAppValue')->willReturnMap([
709
-			['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
710
-			['core', 'shareapi_enforce_links_password', 'no', 'yes'],
711
-		]);
712
-
713
-		self::invokePrivate($this->manager, 'verifyPassword', [null]);
714
-	}
715
-
716
-	public function testVerifyPasswordNotEnforcedGroup(): void {
717
-		$this->config->method('getAppValue')->willReturnMap([
718
-			['core', 'shareapi_enforce_links_password_excluded_groups', '', '["admin"]'],
719
-			['core', 'shareapi_enforce_links_password', 'no', 'yes'],
720
-		]);
721
-
722
-		// Create admin user
723
-		$user = $this->createMock(IUser::class);
724
-		$this->userSession->method('getUser')->willReturn($user);
725
-		$this->groupManager->method('getUserGroupIds')->with($user)->willReturn(['admin']);
726
-
727
-		$result = self::invokePrivate($this->manager, 'verifyPassword', [null]);
728
-		$this->assertNull($result);
729
-	}
730
-
731
-	public function testVerifyPasswordNotEnforcedMultipleGroups(): void {
732
-		$this->config->method('getAppValue')->willReturnMap([
733
-			['core', 'shareapi_enforce_links_password_excluded_groups', '', '["admin", "special"]'],
734
-			['core', 'shareapi_enforce_links_password', 'no', 'yes'],
735
-		]);
736
-
737
-		// Create admin user
738
-		$user = $this->createMock(IUser::class);
739
-		$this->userSession->method('getUser')->willReturn($user);
740
-		$this->groupManager->method('getUserGroupIds')->with($user)->willReturn(['special']);
741
-
742
-		$result = self::invokePrivate($this->manager, 'verifyPassword', [null]);
743
-		$this->assertNull($result);
744
-	}
745
-
746
-	public function testVerifyPasswordNull(): void {
747
-		$this->config->method('getAppValue')->willReturnMap([
748
-			['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
749
-			['core', 'shareapi_enforce_links_password', 'no', 'no'],
750
-		]);
751
-
752
-		$result = self::invokePrivate($this->manager, 'verifyPassword', [null]);
753
-		$this->assertNull($result);
754
-	}
755
-
756
-	public function testVerifyPasswordHook(): void {
757
-		$this->config->method('getAppValue')->willReturnMap([
758
-			['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
759
-			['core', 'shareapi_enforce_links_password', 'no', 'no'],
760
-		]);
761
-
762
-		$this->dispatcher->expects($this->once())->method('dispatchTyped')
763
-			->willReturnCallback(function (Event $event) {
764
-				$this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
765
-				/** @var ValidatePasswordPolicyEvent $event */
766
-				$this->assertSame('password', $event->getPassword());
767
-			}
768
-			);
769
-
770
-		$result = self::invokePrivate($this->manager, 'verifyPassword', ['password']);
771
-		$this->assertNull($result);
772
-	}
773
-
774
-
775
-	public function testVerifyPasswordHookFails(): void {
776
-		$this->expectException(\Exception::class);
777
-		$this->expectExceptionMessage('password not accepted');
778
-
779
-		$this->config->method('getAppValue')->willReturnMap([
780
-			['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
781
-			['core', 'shareapi_enforce_links_password', 'no', 'no'],
782
-		]);
783
-
784
-		$this->dispatcher->expects($this->once())->method('dispatchTyped')
785
-			->willReturnCallback(function (Event $event) {
786
-				$this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
787
-				/** @var ValidatePasswordPolicyEvent $event */
788
-				$this->assertSame('password', $event->getPassword());
789
-				throw new HintException('password not accepted');
790
-			}
791
-			);
792
-
793
-		self::invokePrivate($this->manager, 'verifyPassword', ['password']);
794
-	}
795
-
796
-	public function createShare($id, $type, $node, $sharedWith, $sharedBy, $shareOwner,
797
-		$permissions, $expireDate = null, $password = null, $attributes = null) {
798
-		$share = $this->createMock(IShare::class);
799
-
800
-		$share->method('getShareType')->willReturn($type);
801
-		$share->method('getSharedWith')->willReturn($sharedWith);
802
-		$share->method('getSharedBy')->willReturn($sharedBy);
803
-		$share->method('getShareOwner')->willReturn($shareOwner);
804
-		$share->method('getNode')->willReturn($node);
805
-		if ($node && $node->getId()) {
806
-			$share->method('getNodeId')->willReturn($node->getId());
807
-		}
808
-		$share->method('getPermissions')->willReturn($permissions);
809
-		$share->method('getAttributes')->willReturn($attributes);
810
-		$share->method('getExpirationDate')->willReturn($expireDate);
811
-		$share->method('getPassword')->willReturn($password);
812
-
813
-		return $share;
814
-	}
815
-
816
-	public function dataGeneralChecks() {
817
-		$user0 = 'user0';
818
-		$user2 = 'user1';
819
-		$group0 = 'group0';
820
-		$owner = $this->createMock(IUser::class);
821
-		$owner->method('getUID')
822
-			->willReturn($user0);
823
-
824
-		$file = $this->createMock(File::class);
825
-		$node = $this->createMock(Node::class);
826
-		$storage = $this->createMock(IStorage::class);
827
-		$storage->method('instanceOfStorage')
828
-			->with('\OCA\Files_Sharing\External\Storage')
829
-			->willReturn(false);
830
-		$file->method('getStorage')
831
-			->willReturn($storage);
832
-		$file->method('getId')->willReturn(108);
833
-		$node->method('getStorage')
834
-			->willReturn($storage);
835
-		$node->method('getId')->willReturn(108);
836
-
837
-		$data = [
838
-			[$this->createShare(null, IShare::TYPE_USER, $file, null, $user0, $user0, 31, null, null), 'Share recipient is not a valid user', true],
839
-			[$this->createShare(null, IShare::TYPE_USER, $file, $group0, $user0, $user0, 31, null, null), 'Share recipient is not a valid user', true],
840
-			[$this->createShare(null, IShare::TYPE_USER, $file, '[email protected]', $user0, $user0, 31, null, null), 'Share recipient is not a valid user', true],
841
-			[$this->createShare(null, IShare::TYPE_GROUP, $file, null, $user0, $user0, 31, null, null), 'Share recipient is not a valid group', true],
842
-			[$this->createShare(null, IShare::TYPE_GROUP, $file, $user2, $user0, $user0, 31, null, null), 'Share recipient is not a valid group', true],
843
-			[$this->createShare(null, IShare::TYPE_GROUP, $file, '[email protected]', $user0, $user0, 31, null, null), 'Share recipient is not a valid group', true],
844
-			[$this->createShare(null, IShare::TYPE_LINK, $file, $user2, $user0, $user0, 31, null, null), 'Share recipient should be empty', true],
845
-			[$this->createShare(null, IShare::TYPE_LINK, $file, $group0, $user0, $user0, 31, null, null), 'Share recipient should be empty', true],
846
-			[$this->createShare(null, IShare::TYPE_LINK, $file, '[email protected]', $user0, $user0, 31, null, null), 'Share recipient should be empty', true],
847
-			[$this->createShare(null, -1, $file, null, $user0, $user0, 31, null, null), 'Unknown share type', true],
848
-
849
-			[$this->createShare(null, IShare::TYPE_USER, $file, $user2, null, $user0, 31, null, null), 'Share initiator must be set', true],
850
-			[$this->createShare(null, IShare::TYPE_GROUP, $file, $group0, null, $user0, 31, null, null), 'Share initiator must be set', true],
851
-			[$this->createShare(null, IShare::TYPE_LINK, $file, null, null, $user0, 31, null, null), 'Share initiator must be set', true],
852
-
853
-			[$this->createShare(null, IShare::TYPE_USER, $file, $user0, $user0, $user0, 31, null, null), 'Cannot share with yourself', true],
854
-
855
-			[$this->createShare(null, IShare::TYPE_USER, null, $user2, $user0, $user0, 31, null, null), 'Shared path must be set', true],
856
-			[$this->createShare(null, IShare::TYPE_GROUP, null, $group0, $user0, $user0, 31, null, null), 'Shared path must be set', true],
857
-			[$this->createShare(null, IShare::TYPE_LINK, null, null, $user0, $user0, 31, null, null), 'Shared path must be set', true],
858
-
859
-			[$this->createShare(null, IShare::TYPE_USER, $node, $user2, $user0, $user0, 31, null, null), 'Shared path must be either a file or a folder', true],
860
-			[$this->createShare(null, IShare::TYPE_GROUP, $node, $group0, $user0, $user0, 31, null, null), 'Shared path must be either a file or a folder', true],
861
-			[$this->createShare(null, IShare::TYPE_LINK, $node, null, $user0, $user0, 31, null, null), 'Shared path must be either a file or a folder', true],
862
-		];
863
-
864
-		$nonShareAble = $this->createMock(Folder::class);
865
-		$nonShareAble->method('getId')->willReturn(108);
866
-		$nonShareAble->method('isShareable')->willReturn(false);
867
-		$nonShareAble->method('getPath')->willReturn('path');
868
-		$nonShareAble->method('getName')->willReturn('name');
869
-		$nonShareAble->method('getOwner')
870
-			->willReturn($owner);
871
-		$nonShareAble->method('getStorage')
872
-			->willReturn($storage);
873
-
874
-		$data[] = [$this->createShare(null, IShare::TYPE_USER, $nonShareAble, $user2, $user0, $user0, 31, null, null), 'You are not allowed to share name', true];
875
-		$data[] = [$this->createShare(null, IShare::TYPE_GROUP, $nonShareAble, $group0, $user0, $user0, 31, null, null), 'You are not allowed to share name', true];
876
-		$data[] = [$this->createShare(null, IShare::TYPE_LINK, $nonShareAble, null, $user0, $user0, 31, null, null), 'You are not allowed to share name', true];
877
-
878
-		$limitedPermssions = $this->createMock(File::class);
879
-		$limitedPermssions->method('isShareable')->willReturn(true);
880
-		$limitedPermssions->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ);
881
-		$limitedPermssions->method('getId')->willReturn(108);
882
-		$limitedPermssions->method('getPath')->willReturn('path');
883
-		$limitedPermssions->method('getName')->willReturn('name');
884
-		$limitedPermssions->method('getOwner')
885
-			->willReturn($owner);
886
-		$limitedPermssions->method('getStorage')
887
-			->willReturn($storage);
888
-
889
-		$data[] = [$this->createShare(null, IShare::TYPE_USER, $limitedPermssions, $user2, $user0, $user0, null, null, null), 'Valid permissions are required for sharing', true];
890
-		$data[] = [$this->createShare(null, IShare::TYPE_GROUP, $limitedPermssions, $group0, $user0, $user0, null, null, null), 'Valid permissions are required for sharing', true];
891
-		$data[] = [$this->createShare(null, IShare::TYPE_LINK, $limitedPermssions, null, $user0, $user0, null, null, null), 'Valid permissions are required for sharing', true];
892
-
893
-		$mount = $this->createMock(MoveableMount::class);
894
-		$limitedPermssions->method('getMountPoint')->willReturn($mount);
895
-
896
-		// increase permissions of a re-share
897
-		$data[] = [$this->createShare(null, IShare::TYPE_GROUP, $limitedPermssions, $group0, $user0, $user0, 17, null, null), 'Cannot increase permissions of path', true];
898
-		$data[] = [$this->createShare(null, IShare::TYPE_USER, $limitedPermssions, $user2, $user0, $user0, 3, null, null), 'Cannot increase permissions of path', true];
899
-
900
-		$nonMovableStorage = $this->createMock(IStorage::class);
901
-		$nonMovableStorage->method('instanceOfStorage')
902
-			->with('\OCA\Files_Sharing\External\Storage')
903
-			->willReturn(false);
904
-		$nonMovableStorage->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_ALL);
905
-		$nonMoveableMountPermssions = $this->createMock(Folder::class);
906
-		$nonMoveableMountPermssions->method('isShareable')->willReturn(true);
907
-		$nonMoveableMountPermssions->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ);
908
-		$nonMoveableMountPermssions->method('getId')->willReturn(108);
909
-		$nonMoveableMountPermssions->method('getPath')->willReturn('path');
910
-		$nonMoveableMountPermssions->method('getName')->willReturn('name');
911
-		$nonMoveableMountPermssions->method('getInternalPath')->willReturn('');
912
-		$nonMoveableMountPermssions->method('getOwner')
913
-			->willReturn($owner);
914
-		$nonMoveableMountPermssions->method('getStorage')
915
-			->willReturn($nonMovableStorage);
916
-
917
-		$data[] = [$this->createShare(null, IShare::TYPE_USER, $nonMoveableMountPermssions, $user2, $user0, $user0, 11, null, null), 'Cannot increase permissions of path', false];
918
-		$data[] = [$this->createShare(null, IShare::TYPE_GROUP, $nonMoveableMountPermssions, $group0, $user0, $user0, 11, null, null), 'Cannot increase permissions of path', false];
919
-
920
-		$rootFolder = $this->createMock(Folder::class);
921
-		$rootFolder->method('isShareable')->willReturn(true);
922
-		$rootFolder->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_ALL);
923
-		$rootFolder->method('getId')->willReturn(42);
924
-
925
-		$data[] = [$this->createShare(null, IShare::TYPE_USER, $rootFolder, $user2, $user0, $user0, 30, null, null), 'You cannot share your root folder', true];
926
-		$data[] = [$this->createShare(null, IShare::TYPE_GROUP, $rootFolder, $group0, $user0, $user0, 2, null, null), 'You cannot share your root folder', true];
927
-		$data[] = [$this->createShare(null, IShare::TYPE_LINK, $rootFolder, null, $user0, $user0, 16, null, null), 'You cannot share your root folder', true];
928
-
929
-		$allPermssionsFiles = $this->createMock(File::class);
930
-		$allPermssionsFiles->method('isShareable')->willReturn(true);
931
-		$allPermssionsFiles->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_ALL);
932
-		$allPermssionsFiles->method('getId')->willReturn(187);
933
-		$allPermssionsFiles->method('getOwner')
934
-			->willReturn($owner);
935
-		$allPermssionsFiles->method('getStorage')
936
-			->willReturn($storage);
937
-
938
-		// test invalid CREATE or DELETE permissions
939
-		$data[] = [$this->createShare(null, IShare::TYPE_USER, $allPermssionsFiles, $user2, $user0, $user0, \OCP\Constants::PERMISSION_ALL, null, null), 'File shares cannot have create or delete permissions', true];
940
-		$data[] = [$this->createShare(null, IShare::TYPE_GROUP, $allPermssionsFiles, $group0, $user0, $user0, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE, null, null), 'File shares cannot have create or delete permissions', true];
941
-		$data[] = [$this->createShare(null, IShare::TYPE_LINK, $allPermssionsFiles, null, $user0, $user0, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_DELETE, null, null), 'File shares cannot have create or delete permissions', true];
942
-
943
-		$allPermssions = $this->createMock(Folder::class);
944
-		$allPermssions->method('isShareable')->willReturn(true);
945
-		$allPermssions->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_ALL);
946
-		$allPermssions->method('getId')->willReturn(108);
947
-		$allPermssions->method('getOwner')
948
-			->willReturn($owner);
949
-		$allPermssions->method('getStorage')
950
-			->willReturn($storage);
951
-
952
-		$data[] = [$this->createShare(null, IShare::TYPE_USER, $allPermssions, $user2, $user0, $user0, 30, null, null), 'Shares need at least read permissions', true];
953
-		$data[] = [$this->createShare(null, IShare::TYPE_GROUP, $allPermssions, $group0, $user0, $user0, 2, null, null), 'Shares need at least read permissions', true];
954
-
955
-		// test invalid permissions
956
-		$data[] = [$this->createShare(null, IShare::TYPE_USER, $allPermssions, $user2, $user0, $user0, 32, null, null), 'Valid permissions are required for sharing', true];
957
-		$data[] = [$this->createShare(null, IShare::TYPE_GROUP, $allPermssions, $group0, $user0, $user0, 63, null, null), 'Valid permissions are required for sharing', true];
958
-		$data[] = [$this->createShare(null, IShare::TYPE_LINK, $allPermssions, null, $user0, $user0, -1, null, null), 'Valid permissions are required for sharing', true];
959
-
960
-		// working shares
961
-		$data[] = [$this->createShare(null, IShare::TYPE_USER, $allPermssions, $user2, $user0, $user0, 31, null, null), null, false];
962
-		$data[] = [$this->createShare(null, IShare::TYPE_GROUP, $allPermssions, $group0, $user0, $user0, 3, null, null), null, false];
963
-		$data[] = [$this->createShare(null, IShare::TYPE_LINK, $allPermssions, null, $user0, $user0, 17, null, null), null, false];
964
-
965
-
966
-		$remoteStorage = $this->createMock(IStorage::class);
967
-		$remoteStorage->method('instanceOfStorage')
968
-			->with('\OCA\Files_Sharing\External\Storage')
969
-			->willReturn(true);
970
-		$remoteFile = $this->createMock(Folder::class);
971
-		$remoteFile->method('isShareable')->willReturn(true);
972
-		$remoteFile->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ ^ \OCP\Constants::PERMISSION_UPDATE);
973
-		$remoteFile->method('getId')->willReturn(108);
974
-		$remoteFile->method('getOwner')
975
-			->willReturn($owner);
976
-		$remoteFile->method('getStorage')
977
-			->willReturn($storage);
978
-		$data[] = [$this->createShare(null, IShare::TYPE_REMOTE, $remoteFile, $user2, $user0, $user0, 1, null, null), null, false];
979
-		$data[] = [$this->createShare(null, IShare::TYPE_REMOTE, $remoteFile, $user2, $user0, $user0, 3, null, null), null, false];
980
-		$data[] = [$this->createShare(null, IShare::TYPE_REMOTE, $remoteFile, $user2, $user0, $user0, 31, null, null), 'Cannot increase permissions of ', true];
981
-
982
-		return $data;
983
-	}
984
-
985
-	/**
986
-	 * @dataProvider dataGeneralChecks
987
-	 *
988
-	 * @param $share
989
-	 * @param $exceptionMessage
990
-	 * @param $exception
991
-	 */
992
-	public function testGeneralChecks($share, $exceptionMessage, $exception): void {
993
-		$thrown = null;
994
-
995
-		$this->userManager->method('userExists')->willReturnMap([
996
-			['user0', true],
997
-			['user1', true],
998
-		]);
999
-
1000
-		$this->groupManager->method('groupExists')->willReturnMap([
1001
-			['group0', true],
1002
-		]);
1003
-
1004
-		$userFolder = $this->createMock(Folder::class);
1005
-		$userFolder->expects($this->any())
1006
-			->method('getId')
1007
-			->willReturn(42);
1008
-		// Id 108 is used in the data to refer to the node of the share.
1009
-		$userFolder->method('getById')
1010
-			->with(108)
1011
-			->willReturn([$share->getNode()]);
1012
-		$userFolder->expects($this->any())
1013
-			->method('getRelativePath')
1014
-			->willReturnArgument(0);
1015
-		$this->rootFolder->method('getUserFolder')->willReturn($userFolder);
1016
-
1017
-
1018
-		try {
1019
-			self::invokePrivate($this->manager, 'generalCreateChecks', [$share]);
1020
-			$thrown = false;
1021
-		} catch (\OCP\Share\Exceptions\GenericShareException $e) {
1022
-			$this->assertEquals($exceptionMessage, $e->getHint());
1023
-			$thrown = true;
1024
-		} catch (\InvalidArgumentException $e) {
1025
-			$this->assertEquals($exceptionMessage, $e->getMessage());
1026
-			$thrown = true;
1027
-		}
1028
-
1029
-		$this->assertSame($exception, $thrown);
1030
-	}
1031
-
1032
-
1033
-	public function testGeneralCheckShareRoot(): void {
1034
-		$this->expectException(\InvalidArgumentException::class);
1035
-		$this->expectExceptionMessage('You cannot share your root folder');
1036
-
1037
-		$thrown = null;
1038
-
1039
-		$this->userManager->method('userExists')->willReturnMap([
1040
-			['user0', true],
1041
-			['user1', true],
1042
-		]);
1043
-
1044
-		$userFolder = $this->createMock(Folder::class);
1045
-		$userFolder->method('isSubNode')->with($userFolder)->willReturn(false);
1046
-		$this->rootFolder->method('getUserFolder')->willReturn($userFolder);
1047
-
1048
-		$share = $this->manager->newShare();
1049
-
1050
-		$share->setShareType(IShare::TYPE_USER)
1051
-			->setSharedWith('user0')
1052
-			->setSharedBy('user1')
1053
-			->setNode($userFolder);
1054
-
1055
-		self::invokePrivate($this->manager, 'generalCreateChecks', [$share]);
1056
-	}
1057
-
1058
-	public function validateExpirationDateInternalProvider() {
1059
-		return [[IShare::TYPE_USER], [IShare::TYPE_REMOTE], [IShare::TYPE_REMOTE_GROUP]];
1060
-	}
1061
-
1062
-	/**
1063
-	 * @dataProvider validateExpirationDateInternalProvider
1064
-	 */
1065
-	public function testValidateExpirationDateInternalInPast($shareType): void {
1066
-		$this->expectException(\OCP\Share\Exceptions\GenericShareException::class);
1067
-		$this->expectExceptionMessage('Expiration date is in the past');
1068
-
1069
-		// Expire date in the past
1070
-		$past = new \DateTime();
1071
-		$past->sub(new \DateInterval('P1D'));
1072
-
1073
-		$share = $this->manager->newShare();
1074
-		$share->setShareType($shareType);
1075
-		$share->setExpirationDate($past);
1076
-
1077
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1078
-	}
1079
-
1080
-	/**
1081
-	 * @dataProvider validateExpirationDateInternalProvider
1082
-	 */
1083
-	public function testValidateExpirationDateInternalEnforceButNotSet($shareType): void {
1084
-		$this->expectException(\InvalidArgumentException::class);
1085
-		$this->expectExceptionMessage('Expiration date is enforced');
1086
-
1087
-		$share = $this->manager->newShare();
1088
-		$share->setProviderId('foo')->setId('bar');
1089
-		$share->setShareType($shareType);
1090
-		if ($shareType === IShare::TYPE_USER) {
1091
-			$this->config->method('getAppValue')
1092
-				->willReturnMap([
1093
-					['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1094
-					['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1095
-				]);
1096
-		} else {
1097
-			$this->config->method('getAppValue')
1098
-				->willReturnMap([
1099
-					['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1100
-					['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1101
-				]);
1102
-		}
1103
-
1104
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1105
-	}
1106
-
1107
-	/**
1108
-	 * @dataProvider validateExpirationDateInternalProvider
1109
-	 */
1110
-	public function testValidateExpirationDateInternalEnforceButNotEnabledAndNotSet($shareType): void {
1111
-		$share = $this->manager->newShare();
1112
-		$share->setProviderId('foo')->setId('bar');
1113
-		$share->setShareType($shareType);
1114
-
1115
-		if ($shareType === IShare::TYPE_USER) {
1116
-			$this->config->method('getAppValue')
1117
-				->willReturnMap([
1118
-					['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1119
-				]);
1120
-		} else {
1121
-			$this->config->method('getAppValue')
1122
-				->willReturnMap([
1123
-					['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1124
-				]);
1125
-		}
1126
-
1127
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1128
-
1129
-		$this->assertNull($share->getExpirationDate());
1130
-	}
1131
-
1132
-	/**
1133
-	 * @dataProvider validateExpirationDateInternalProvider
1134
-	 */
1135
-	public function testValidateExpirationDateInternalEnforceButNotSetNewShare($shareType): void {
1136
-		$share = $this->manager->newShare();
1137
-		$share->setShareType($shareType);
1138
-
1139
-		if ($shareType === IShare::TYPE_USER) {
1140
-			$this->config->method('getAppValue')
1141
-				->willReturnMap([
1142
-					['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1143
-					['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1144
-					['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1145
-					['core', 'internal_defaultExpDays', '3', '3'],
1146
-				]);
1147
-		} else {
1148
-			$this->config->method('getAppValue')
1149
-				->willReturnMap([
1150
-					['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1151
-					['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1152
-					['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1153
-					['core', 'remote_defaultExpDays', '3', '3'],
1154
-				]);
1155
-		}
1156
-
1157
-		$expected = new \DateTime('now', $this->timezone);
1158
-		$expected->setTime(0, 0, 0);
1159
-		$expected->add(new \DateInterval('P3D'));
1160
-
1161
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1162
-
1163
-		$this->assertNotNull($share->getExpirationDate());
1164
-		$this->assertEquals($expected, $share->getExpirationDate());
1165
-	}
1166
-
1167
-	/**
1168
-	 * @dataProvider validateExpirationDateInternalProvider
1169
-	 */
1170
-	public function testValidateExpirationDateInternalEnforceRelaxedDefaultButNotSetNewShare($shareType): void {
1171
-		$share = $this->manager->newShare();
1172
-		$share->setShareType($shareType);
1173
-
1174
-		if ($shareType === IShare::TYPE_USER) {
1175
-			$this->config->method('getAppValue')
1176
-				->willReturnMap([
1177
-					['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1178
-					['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1179
-					['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1180
-					['core', 'internal_defaultExpDays', '3', '1'],
1181
-				]);
1182
-		} else {
1183
-			$this->config->method('getAppValue')
1184
-				->willReturnMap([
1185
-					['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1186
-					['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1187
-					['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1188
-					['core', 'remote_defaultExpDays', '3', '1'],
1189
-				]);
1190
-		}
1191
-
1192
-		$expected = new \DateTime('now', $this->timezone);
1193
-		$expected->setTime(0, 0, 0);
1194
-		$expected->add(new \DateInterval('P1D'));
1195
-
1196
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1197
-
1198
-		$this->assertNotNull($share->getExpirationDate());
1199
-		$this->assertEquals($expected, $share->getExpirationDate());
1200
-	}
1201
-
1202
-	/**
1203
-	 * @dataProvider validateExpirationDateInternalProvider
1204
-	 */
1205
-	public function testValidateExpirationDateInternalEnforceTooFarIntoFuture($shareType): void {
1206
-		$this->expectException(\OCP\Share\Exceptions\GenericShareException::class);
1207
-		$this->expectExceptionMessage('Cannot set expiration date more than 3 days in the future');
1208
-
1209
-		$future = new \DateTime();
1210
-		$future->add(new \DateInterval('P7D'));
1211
-
1212
-		$share = $this->manager->newShare();
1213
-		$share->setShareType($shareType);
1214
-		$share->setExpirationDate($future);
1215
-
1216
-		if ($shareType === IShare::TYPE_USER) {
1217
-			$this->config->method('getAppValue')
1218
-				->willReturnMap([
1219
-					['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1220
-					['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1221
-					['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1222
-				]);
1223
-		} else {
1224
-			$this->config->method('getAppValue')
1225
-				->willReturnMap([
1226
-					['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1227
-					['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1228
-					['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1229
-				]);
1230
-		}
1231
-
1232
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1233
-	}
1234
-
1235
-	/**
1236
-	 * @dataProvider validateExpirationDateInternalProvider
1237
-	 */
1238
-	public function testValidateExpirationDateInternalEnforceValid($shareType): void {
1239
-		$future = new \DateTime('now', $this->dateTimeZone->getTimeZone());
1240
-		$future->add(new \DateInterval('P2D'));
1241
-		$future->setTime(1, 2, 3);
1242
-
1243
-		$expected = clone $future;
1244
-		$expected->setTime(0, 0, 0);
1245
-
1246
-		$share = $this->manager->newShare();
1247
-		$share->setShareType($shareType);
1248
-		$share->setExpirationDate($future);
1249
-
1250
-		if ($shareType === IShare::TYPE_USER) {
1251
-			$this->config->method('getAppValue')
1252
-				->willReturnMap([
1253
-					['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1254
-					['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1255
-					['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1256
-				]);
1257
-		} else {
1258
-			$this->config->method('getAppValue')
1259
-				->willReturnMap([
1260
-					['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1261
-					['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1262
-					['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1263
-				]);
1264
-		}
1265
-
1266
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1267
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1268
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($future) {
1269
-			return $data['expirationDate'] == $future;
1270
-		}));
1271
-
1272
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1273
-
1274
-		$this->assertEquals($expected, $share->getExpirationDate());
1275
-	}
1276
-
1277
-	/**
1278
-	 * @dataProvider validateExpirationDateInternalProvider
1279
-	 */
1280
-	public function testValidateExpirationDateInternalNoDefault($shareType): void {
1281
-		$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
1282
-		$date->add(new \DateInterval('P5D'));
1283
-		$date->setTime(1, 2, 3);
1284
-
1285
-		$expected = clone $date;
1286
-		$expected->setTime(0, 0, 0);
1287
-
1288
-		$share = $this->manager->newShare();
1289
-		$share->setShareType($shareType);
1290
-		$share->setExpirationDate($date);
1291
-
1292
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1293
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1294
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1295
-			return $data['expirationDate'] == $expected && $data['passwordSet'] === false;
1296
-		}));
1297
-
1298
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1299
-
1300
-		$this->assertEquals($expected, $share->getExpirationDate());
1301
-	}
1302
-
1303
-	/**
1304
-	 * @dataProvider validateExpirationDateInternalProvider
1305
-	 */
1306
-	public function testValidateExpirationDateInternalNoDateNoDefault($shareType): void {
1307
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1308
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1309
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) {
1310
-			return $data['expirationDate'] === null && $data['passwordSet'] === true;
1311
-		}));
1312
-
1313
-		$share = $this->manager->newShare();
1314
-		$share->setShareType($shareType);
1315
-		$share->setPassword('password');
1316
-
1317
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1318
-
1319
-		$this->assertNull($share->getExpirationDate());
1320
-	}
1321
-
1322
-	/**
1323
-	 * @dataProvider validateExpirationDateInternalProvider
1324
-	 */
1325
-	public function testValidateExpirationDateInternalNoDateDefault($shareType): void {
1326
-		$share = $this->manager->newShare();
1327
-		$share->setShareType($shareType);
1328
-
1329
-		$expected = new \DateTime('now', $this->timezone);
1330
-		$expected->setTime(0, 0);
1331
-		$expected->add(new \DateInterval('P3D'));
1332
-		$expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1333
-
1334
-		if ($shareType === IShare::TYPE_USER) {
1335
-			$this->config->method('getAppValue')
1336
-				->willReturnMap([
1337
-					['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1338
-					['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1339
-					['core', 'internal_defaultExpDays', '3', '3'],
1340
-				]);
1341
-		} else {
1342
-			$this->config->method('getAppValue')
1343
-				->willReturnMap([
1344
-					['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1345
-					['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1346
-					['core', 'remote_defaultExpDays', '3', '3'],
1347
-				]);
1348
-		}
1349
-
1350
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1351
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1352
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1353
-			return $data['expirationDate'] == $expected;
1354
-		}));
1355
-
1356
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1357
-
1358
-		$this->assertEquals($expected, $share->getExpirationDate());
1359
-	}
1360
-
1361
-	/**
1362
-	 * @dataProvider validateExpirationDateInternalProvider
1363
-	 */
1364
-	public function testValidateExpirationDateInternalDefault($shareType): void {
1365
-		$future = new \DateTime('now', $this->timezone);
1366
-		$future->add(new \DateInterval('P5D'));
1367
-		$future->setTime(1, 2, 3);
1368
-
1369
-		$expected = clone $future;
1370
-		$expected->setTime(0, 0);
1371
-
1372
-		$share = $this->manager->newShare();
1373
-		$share->setShareType($shareType);
1374
-		$share->setExpirationDate($future);
1375
-
1376
-		if ($shareType === IShare::TYPE_USER) {
1377
-			$this->config->method('getAppValue')
1378
-				->willReturnMap([
1379
-					['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1380
-					['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1381
-					['core', 'internal_defaultExpDays', '3', '1'],
1382
-				]);
1383
-		} else {
1384
-			$this->config->method('getAppValue')
1385
-				->willReturnMap([
1386
-					['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1387
-					['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1388
-					['core', 'remote_defaultExpDays', '3', '1'],
1389
-				]);
1390
-		}
1391
-
1392
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1393
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1394
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1395
-			return $data['expirationDate'] == $expected;
1396
-		}));
1397
-
1398
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1399
-
1400
-		$this->assertEquals($expected, $share->getExpirationDate());
1401
-	}
1402
-
1403
-	/**
1404
-	 * @dataProvider validateExpirationDateInternalProvider
1405
-	 */
1406
-	public function testValidateExpirationDateInternalHookModification($shareType): void {
1407
-		$nextWeek = new \DateTime('now', $this->timezone);
1408
-		$nextWeek->add(new \DateInterval('P7D'));
1409
-		$nextWeek->setTime(0, 0, 0);
1410
-
1411
-		$save = clone $nextWeek;
1412
-
1413
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1414
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1415
-		$hookListener->expects($this->once())->method('listener')->willReturnCallback(function ($data) {
1416
-			$data['expirationDate']->sub(new \DateInterval('P2D'));
1417
-		});
1418
-
1419
-		$share = $this->manager->newShare();
1420
-		$share->setShareType($shareType);
1421
-		$share->setExpirationDate($nextWeek);
1422
-
1423
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1424
-
1425
-		$save->sub(new \DateInterval('P2D'));
1426
-		$this->assertEquals($save, $share->getExpirationDate());
1427
-	}
1428
-
1429
-	/**
1430
-	 * @dataProvider validateExpirationDateInternalProvider
1431
-	 */
1432
-	public function testValidateExpirationDateInternalHookException($shareType): void {
1433
-		$this->expectException(\Exception::class);
1434
-		$this->expectExceptionMessage('Invalid date!');
1435
-
1436
-		$nextWeek = new \DateTime();
1437
-		$nextWeek->add(new \DateInterval('P7D'));
1438
-		$nextWeek->setTime(0, 0, 0);
1439
-
1440
-		$share = $this->manager->newShare();
1441
-		$share->setShareType($shareType);
1442
-		$share->setExpirationDate($nextWeek);
1443
-
1444
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1445
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1446
-		$hookListener->expects($this->once())->method('listener')->willReturnCallback(function ($data) {
1447
-			$data['accepted'] = false;
1448
-			$data['message'] = 'Invalid date!';
1449
-		});
1450
-
1451
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1452
-	}
1453
-
1454
-	/**
1455
-	 * @dataProvider validateExpirationDateInternalProvider
1456
-	 */
1457
-	public function testValidateExpirationDateInternalExistingShareNoDefault($shareType): void {
1458
-		$share = $this->manager->newShare();
1459
-		$share->setShareType($shareType);
1460
-		$share->setId('42')->setProviderId('foo');
1461
-
1462
-		if ($shareType === IShare::TYPE_USER) {
1463
-			$this->config->method('getAppValue')
1464
-				->willReturnMap([
1465
-					['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1466
-					['core', 'shareapi_internal_expire_after_n_days', '7', '6'],
1467
-				]);
1468
-		} else {
1469
-			$this->config->method('getAppValue')
1470
-				->willReturnMap([
1471
-					['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1472
-					['core', 'shareapi_remote_expire_after_n_days', '7', '6'],
1473
-				]);
1474
-		}
1475
-
1476
-		self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1477
-
1478
-		$this->assertEquals(null, $share->getExpirationDate());
1479
-	}
1480
-
1481
-	public function testValidateExpirationDateInPast(): void {
1482
-		$this->expectException(\OCP\Share\Exceptions\GenericShareException::class);
1483
-		$this->expectExceptionMessage('Expiration date is in the past');
1484
-
1485
-		// Expire date in the past
1486
-		$past = new \DateTime();
1487
-		$past->sub(new \DateInterval('P1D'));
1488
-
1489
-		$share = $this->manager->newShare();
1490
-		$share->setExpirationDate($past);
1491
-
1492
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1493
-	}
1494
-
1495
-	public function testValidateExpirationDateEnforceButNotSet(): void {
1496
-		$this->expectException(\InvalidArgumentException::class);
1497
-		$this->expectExceptionMessage('Expiration date is enforced');
1498
-
1499
-		$share = $this->manager->newShare();
1500
-		$share->setProviderId('foo')->setId('bar');
1501
-
1502
-		$this->config->method('getAppValue')
1503
-			->willReturnMap([
1504
-				['core', 'shareapi_default_expire_date', 'no', 'yes'],
1505
-				['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1506
-			]);
1507
-
1508
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1509
-	}
1510
-
1511
-	public function testValidateExpirationDateEnforceButNotEnabledAndNotSet(): void {
1512
-		$share = $this->manager->newShare();
1513
-		$share->setProviderId('foo')->setId('bar');
1514
-
1515
-		$this->config->method('getAppValue')
1516
-			->willReturnMap([
1517
-				['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1518
-			]);
1519
-
1520
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1521
-
1522
-		$this->assertNull($share->getExpirationDate());
1523
-	}
1524
-
1525
-	public function testValidateExpirationDateEnforceButNotSetNewShare(): void {
1526
-		$share = $this->manager->newShare();
1527
-
1528
-		$this->config->method('getAppValue')
1529
-			->willReturnMap([
1530
-				['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1531
-				['core', 'shareapi_expire_after_n_days', '7', '3'],
1532
-				['core', 'shareapi_default_expire_date', 'no', 'yes'],
1533
-				['core', 'link_defaultExpDays', '3', '3'],
1534
-			]);
1535
-
1536
-		$expected = new \DateTime('now', $this->timezone);
1537
-		$expected->setTime(0, 0, 0);
1538
-		$expected->add(new \DateInterval('P3D'));
1539
-
1540
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1541
-
1542
-		$this->assertNotNull($share->getExpirationDate());
1543
-		$this->assertEquals($expected, $share->getExpirationDate());
1544
-	}
1545
-
1546
-	public function testValidateExpirationDateEnforceRelaxedDefaultButNotSetNewShare(): void {
1547
-		$share = $this->manager->newShare();
1548
-
1549
-		$this->config->method('getAppValue')
1550
-			->willReturnMap([
1551
-				['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1552
-				['core', 'shareapi_expire_after_n_days', '7', '3'],
1553
-				['core', 'shareapi_default_expire_date', 'no', 'yes'],
1554
-				['core', 'link_defaultExpDays', '3', '1'],
1555
-			]);
1556
-
1557
-		$expected = new \DateTime('now', $this->timezone);
1558
-		$expected->setTime(0, 0, 0);
1559
-		$expected->add(new \DateInterval('P1D'));
1560
-
1561
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1562
-
1563
-		$this->assertNotNull($share->getExpirationDate());
1564
-		$this->assertEquals($expected, $share->getExpirationDate());
1565
-	}
1566
-
1567
-	public function testValidateExpirationDateEnforceTooFarIntoFuture(): void {
1568
-		$this->expectException(\OCP\Share\Exceptions\GenericShareException::class);
1569
-		$this->expectExceptionMessage('Cannot set expiration date more than 3 days in the future');
69
+    /** @var Manager */
70
+    protected $manager;
71
+    /** @var LoggerInterface|MockObject */
72
+    protected $logger;
73
+    /** @var IConfig|MockObject */
74
+    protected $config;
75
+    /** @var ISecureRandom|MockObject */
76
+    protected $secureRandom;
77
+    /** @var IHasher|MockObject */
78
+    protected $hasher;
79
+    /** @var IShareProvider|MockObject */
80
+    protected $defaultProvider;
81
+    /** @var IMountManager|MockObject */
82
+    protected $mountManager;
83
+    /** @var IGroupManager|MockObject */
84
+    protected $groupManager;
85
+    /** @var IL10N|MockObject */
86
+    protected $l;
87
+    /** @var IFactory|MockObject */
88
+    protected $l10nFactory;
89
+    /** @var DummyFactory */
90
+    protected $factory;
91
+    /** @var IUserManager|MockObject */
92
+    protected $userManager;
93
+    /** @var IRootFolder | MockObject */
94
+    protected $rootFolder;
95
+    /** @var IEventDispatcher|MockObject */
96
+    protected $dispatcher;
97
+    /** @var IMailer|MockObject */
98
+    protected $mailer;
99
+    /** @var IURLGenerator|MockObject */
100
+    protected $urlGenerator;
101
+    /** @var \OC_Defaults|MockObject */
102
+    protected $defaults;
103
+    /** @var IUserSession|MockObject */
104
+    protected $userSession;
105
+    /** @var KnownUserService|MockObject */
106
+    protected $knownUserService;
107
+    /** @var ShareDisableChecker|MockObject */
108
+    protected $shareDisabledChecker;
109
+    private DateTimeZone $timezone;
110
+    /** @var IDateTimeZone|MockObject */
111
+    protected $dateTimeZone;
112
+    /** @var IAppConfig|MockObject */
113
+    protected $appConfig;
114
+
115
+    protected function setUp(): void {
116
+        $this->logger = $this->createMock(LoggerInterface::class);
117
+        $this->config = $this->createMock(IConfig::class);
118
+        $this->secureRandom = $this->createMock(ISecureRandom::class);
119
+        $this->hasher = $this->createMock(IHasher::class);
120
+        $this->mountManager = $this->createMock(IMountManager::class);
121
+        $this->groupManager = $this->createMock(IGroupManager::class);
122
+        $this->userManager = $this->createMock(IUserManager::class);
123
+        $this->rootFolder = $this->createMock(IRootFolder::class);
124
+        $this->mailer = $this->createMock(IMailer::class);
125
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
126
+        $this->defaults = $this->createMock(\OC_Defaults::class);
127
+        $this->dispatcher = $this->createMock(IEventDispatcher::class);
128
+        $this->userSession = $this->createMock(IUserSession::class);
129
+        $this->knownUserService = $this->createMock(KnownUserService::class);
130
+
131
+        $this->shareDisabledChecker = new ShareDisableChecker($this->config, $this->userManager, $this->groupManager);
132
+        $this->dateTimeZone = $this->createMock(IDateTimeZone::class);
133
+        $this->timezone = new \DateTimeZone('Pacific/Auckland');
134
+        $this->dateTimeZone->method('getTimeZone')->willReturnCallback(fn () => $this->timezone);
135
+
136
+        $this->appConfig = $this->createMock(IAppConfig::class);
137
+
138
+        $this->l10nFactory = $this->createMock(IFactory::class);
139
+        $this->l = $this->createMock(IL10N::class);
140
+        $this->l->method('t')
141
+            ->willReturnCallback(function ($text, $parameters = []) {
142
+                return vsprintf($text, $parameters);
143
+            });
144
+        $this->l->method('n')
145
+            ->willReturnCallback(function ($singular, $plural, $count, $parameters = []) {
146
+                return vsprintf(str_replace('%n', $count, ($count === 1) ? $singular : $plural), $parameters);
147
+            });
148
+        $this->l10nFactory->method('get')->willReturn($this->l);
149
+
150
+        $this->factory = new DummyFactory(\OC::$server);
151
+
152
+        $this->manager = $this->createManager($this->factory);
153
+
154
+        $this->defaultProvider = $this->createMock(DefaultShareProvider::class);
155
+        $this->defaultProvider->method('identifier')->willReturn('default');
156
+        $this->factory->setProvider($this->defaultProvider);
157
+    }
158
+
159
+    private function createManager(IProviderFactory $factory): Manager {
160
+        return new Manager(
161
+            $this->logger,
162
+            $this->config,
163
+            $this->secureRandom,
164
+            $this->hasher,
165
+            $this->mountManager,
166
+            $this->groupManager,
167
+            $this->l10nFactory,
168
+            $factory,
169
+            $this->userManager,
170
+            $this->rootFolder,
171
+            $this->mailer,
172
+            $this->urlGenerator,
173
+            $this->defaults,
174
+            $this->dispatcher,
175
+            $this->userSession,
176
+            $this->knownUserService,
177
+            $this->shareDisabledChecker,
178
+            $this->dateTimeZone,
179
+            $this->appConfig,
180
+        );
181
+    }
182
+
183
+    /**
184
+     * @return MockBuilder
185
+     */
186
+    private function createManagerMock() {
187
+        return $this->getMockBuilder(Manager::class)
188
+            ->setConstructorArgs([
189
+                $this->logger,
190
+                $this->config,
191
+                $this->secureRandom,
192
+                $this->hasher,
193
+                $this->mountManager,
194
+                $this->groupManager,
195
+                $this->l10nFactory,
196
+                $this->factory,
197
+                $this->userManager,
198
+                $this->rootFolder,
199
+                $this->mailer,
200
+                $this->urlGenerator,
201
+                $this->defaults,
202
+                $this->dispatcher,
203
+                $this->userSession,
204
+                $this->knownUserService,
205
+                $this->shareDisabledChecker,
206
+                $this->dateTimeZone,
207
+                $this->appConfig,
208
+            ]);
209
+    }
210
+
211
+    private function createFolderMock(string $folderPath): MockObject&Folder {
212
+        $folder = $this->createMock(Folder::class);
213
+        $folder->method('getPath')->willReturn($folderPath);
214
+        $folder->method('getRelativePath')->willReturnCallback(
215
+            fn (string $path): ?string => PathHelper::getRelativePath($folderPath, $path)
216
+        );
217
+        return $folder;
218
+    }
219
+
220
+    public function testDeleteNoShareId(): void {
221
+        $this->expectException(\InvalidArgumentException::class);
222
+
223
+        $share = $this->manager->newShare();
224
+
225
+        $this->manager->deleteShare($share);
226
+    }
227
+
228
+    public function dataTestDelete() {
229
+        $user = $this->createMock(IUser::class);
230
+        $user->method('getUID')->willReturn('sharedWithUser');
231
+
232
+        $group = $this->createMock(IGroup::class);
233
+        $group->method('getGID')->willReturn('sharedWithGroup');
234
+
235
+        return [
236
+            [IShare::TYPE_USER, 'sharedWithUser'],
237
+            [IShare::TYPE_GROUP, 'sharedWithGroup'],
238
+            [IShare::TYPE_LINK, ''],
239
+            [IShare::TYPE_REMOTE, '[email protected]'],
240
+        ];
241
+    }
242
+
243
+    /**
244
+     * @dataProvider dataTestDelete
245
+     */
246
+    public function testDelete($shareType, $sharedWith): void {
247
+        $manager = $this->createManagerMock()
248
+            ->setMethods(['getShareById', 'deleteChildren', 'promoteReshares'])
249
+            ->getMock();
250
+
251
+        $manager->method('deleteChildren')->willReturn([]);
252
+
253
+        $path = $this->createMock(File::class);
254
+        $path->method('getId')->willReturn(1);
255
+
256
+        $share = $this->manager->newShare();
257
+        $share->setId(42)
258
+            ->setProviderId('prov')
259
+            ->setShareType($shareType)
260
+            ->setSharedWith($sharedWith)
261
+            ->setSharedBy('sharedBy')
262
+            ->setNode($path)
263
+            ->setTarget('myTarget');
264
+
265
+        $manager->expects($this->once())->method('deleteChildren')->with($share);
266
+        $manager->expects($this->once())->method('promoteReshares')->with($share);
267
+
268
+        $this->defaultProvider
269
+            ->expects($this->once())
270
+            ->method('delete')
271
+            ->with($share);
272
+
273
+        $this->dispatcher->expects($this->exactly(2))
274
+            ->method('dispatchTyped')
275
+            ->withConsecutive(
276
+                [
277
+                    $this->callBack(function (BeforeShareDeletedEvent $e) use ($share) {
278
+                        return $e->getShare() === $share;
279
+                    })],
280
+                [
281
+                    $this->callBack(function (ShareDeletedEvent $e) use ($share) {
282
+                        return $e->getShare() === $share;
283
+                    })]
284
+            );
285
+
286
+        $manager->deleteShare($share);
287
+    }
288
+
289
+    public function testDeleteLazyShare(): void {
290
+        $manager = $this->createManagerMock()
291
+            ->setMethods(['getShareById', 'deleteChildren', 'promoteReshares'])
292
+            ->getMock();
293
+
294
+        $manager->method('deleteChildren')->willReturn([]);
295
+
296
+        $share = $this->manager->newShare();
297
+        $share->setId(42)
298
+            ->setProviderId('prov')
299
+            ->setShareType(IShare::TYPE_USER)
300
+            ->setSharedWith('sharedWith')
301
+            ->setSharedBy('sharedBy')
302
+            ->setShareOwner('shareOwner')
303
+            ->setTarget('myTarget')
304
+            ->setNodeId(1)
305
+            ->setNodeType('file');
306
+
307
+        $this->rootFolder->expects($this->never())->method($this->anything());
308
+
309
+        $manager->expects($this->once())->method('deleteChildren')->with($share);
310
+        $manager->expects($this->once())->method('promoteReshares')->with($share);
311
+
312
+        $this->defaultProvider
313
+            ->expects($this->once())
314
+            ->method('delete')
315
+            ->with($share);
316
+
317
+        $this->dispatcher->expects($this->exactly(2))
318
+            ->method('dispatchTyped')
319
+            ->withConsecutive(
320
+                [
321
+                    $this->callBack(function (BeforeShareDeletedEvent $e) use ($share) {
322
+                        return $e->getShare() === $share;
323
+                    })],
324
+                [
325
+                    $this->callBack(function (ShareDeletedEvent $e) use ($share) {
326
+                        return $e->getShare() === $share;
327
+                    })]
328
+            );
329
+
330
+        $manager->deleteShare($share);
331
+    }
332
+
333
+    public function testDeleteNested(): void {
334
+        $manager = $this->createManagerMock()
335
+            ->setMethods(['getShareById', 'promoteReshares'])
336
+            ->getMock();
337
+
338
+        $path = $this->createMock(File::class);
339
+        $path->method('getId')->willReturn(1);
340
+
341
+        $share1 = $this->manager->newShare();
342
+        $share1->setId(42)
343
+            ->setProviderId('prov')
344
+            ->setShareType(IShare::TYPE_USER)
345
+            ->setSharedWith('sharedWith1')
346
+            ->setSharedBy('sharedBy1')
347
+            ->setNode($path)
348
+            ->setTarget('myTarget1');
349
+
350
+        $share2 = $this->manager->newShare();
351
+        $share2->setId(43)
352
+            ->setProviderId('prov')
353
+            ->setShareType(IShare::TYPE_GROUP)
354
+            ->setSharedWith('sharedWith2')
355
+            ->setSharedBy('sharedBy2')
356
+            ->setNode($path)
357
+            ->setTarget('myTarget2')
358
+            ->setParent(42);
359
+
360
+        $share3 = $this->manager->newShare();
361
+        $share3->setId(44)
362
+            ->setProviderId('prov')
363
+            ->setShareType(IShare::TYPE_LINK)
364
+            ->setSharedBy('sharedBy3')
365
+            ->setNode($path)
366
+            ->setTarget('myTarget3')
367
+            ->setParent(43);
368
+
369
+        $this->defaultProvider
370
+            ->method('getChildren')
371
+            ->willReturnMap([
372
+                [$share1, [$share2]],
373
+                [$share2, [$share3]],
374
+                [$share3, []],
375
+            ]);
376
+
377
+        $this->defaultProvider
378
+            ->method('delete')
379
+            ->withConsecutive([$share3], [$share2], [$share1]);
380
+
381
+        $this->dispatcher->expects($this->exactly(6))
382
+            ->method('dispatchTyped')
383
+            ->withConsecutive(
384
+                [
385
+                    $this->callBack(function (BeforeShareDeletedEvent $e) use ($share1) {
386
+                        return $e->getShare()->getId() === $share1->getId();
387
+                    })
388
+                ],
389
+                [
390
+                    $this->callBack(function (BeforeShareDeletedEvent $e) use ($share2) {
391
+                        return $e->getShare()->getId() === $share2->getId();
392
+                    })
393
+                ],
394
+                [
395
+                    $this->callBack(function (BeforeShareDeletedEvent $e) use ($share3) {
396
+                        return $e->getShare()->getId() === $share3->getId();
397
+                    })
398
+                ],
399
+                [
400
+                    $this->callBack(function (ShareDeletedEvent $e) use ($share3) {
401
+                        return $e->getShare()->getId() === $share3->getId();
402
+                    })
403
+                ],
404
+                [
405
+                    $this->callBack(function (ShareDeletedEvent $e) use ($share2) {
406
+                        return $e->getShare()->getId() === $share2->getId();
407
+                    })
408
+                ],
409
+                [
410
+                    $this->callBack(function (ShareDeletedEvent $e) use ($share1) {
411
+                        return $e->getShare()->getId() === $share1->getId();
412
+                    })
413
+                ],
414
+            );
415
+
416
+        $manager->deleteShare($share1);
417
+    }
418
+
419
+    public function testDeleteFromSelf(): void {
420
+        $manager = $this->createManagerMock()
421
+            ->setMethods(['getShareById'])
422
+            ->getMock();
423
+
424
+        $recipientId = 'unshareFrom';
425
+        $share = $this->manager->newShare();
426
+        $share->setId(42)
427
+            ->setProviderId('prov')
428
+            ->setShareType(IShare::TYPE_USER)
429
+            ->setSharedWith('sharedWith')
430
+            ->setSharedBy('sharedBy')
431
+            ->setShareOwner('shareOwner')
432
+            ->setTarget('myTarget')
433
+            ->setNodeId(1)
434
+            ->setNodeType('file');
435
+
436
+        $this->defaultProvider
437
+            ->expects($this->once())
438
+            ->method('deleteFromSelf')
439
+            ->with($share, $recipientId);
440
+
441
+        $this->dispatcher->expects($this->once())
442
+            ->method('dispatchTyped')
443
+            ->with(
444
+                $this->callBack(function (ShareDeletedFromSelfEvent $e) use ($share) {
445
+                    return $e->getShare() === $share;
446
+                })
447
+            );
448
+
449
+        $manager->deleteFromSelf($share, $recipientId);
450
+    }
451
+
452
+    public function testDeleteChildren(): void {
453
+        $manager = $this->createManagerMock()
454
+            ->setMethods(['deleteShare'])
455
+            ->getMock();
456
+
457
+        $share = $this->createMock(IShare::class);
458
+        $share->method('getShareType')->willReturn(IShare::TYPE_USER);
459
+
460
+        $child1 = $this->createMock(IShare::class);
461
+        $child1->method('getShareType')->willReturn(IShare::TYPE_USER);
462
+        $child2 = $this->createMock(IShare::class);
463
+        $child2->method('getShareType')->willReturn(IShare::TYPE_USER);
464
+        $child3 = $this->createMock(IShare::class);
465
+        $child3->method('getShareType')->willReturn(IShare::TYPE_USER);
466
+
467
+        $shares = [
468
+            $child1,
469
+            $child2,
470
+            $child3,
471
+        ];
472
+
473
+        $this->defaultProvider
474
+            ->expects($this->exactly(4))
475
+            ->method('getChildren')
476
+            ->willReturnCallback(function ($_share) use ($share, $shares) {
477
+                if ($_share === $share) {
478
+                    return $shares;
479
+                }
480
+                return [];
481
+            });
482
+
483
+        $this->defaultProvider
484
+            ->expects($this->exactly(3))
485
+            ->method('delete')
486
+            ->withConsecutive([$child1], [$child2], [$child3]);
487
+
488
+        $result = self::invokePrivate($manager, 'deleteChildren', [$share]);
489
+        $this->assertSame($shares, $result);
490
+    }
491
+
492
+    public function testPromoteReshareFile(): void {
493
+        $manager = $this->createManagerMock()
494
+            ->setMethods(['updateShare', 'getSharesInFolder', 'generalCreateChecks'])
495
+            ->getMock();
496
+
497
+        $file = $this->createMock(File::class);
498
+
499
+        $share = $this->createMock(IShare::class);
500
+        $share->method('getShareType')->willReturn(IShare::TYPE_USER);
501
+        $share->method('getNodeType')->willReturn('folder');
502
+        $share->method('getSharedWith')->willReturn('userB');
503
+        $share->method('getNode')->willReturn($file);
504
+
505
+        $reShare = $this->createMock(IShare::class);
506
+        $reShare->method('getShareType')->willReturn(IShare::TYPE_USER);
507
+        $reShare->method('getSharedBy')->willReturn('userB');
508
+        $reShare->method('getSharedWith')->willReturn('userC');
509
+        $reShare->method('getNode')->willReturn($file);
510
+
511
+        $this->defaultProvider->method('getSharesBy')
512
+            ->willReturnCallback(function ($userId, $shareType, $node, $reshares, $limit, $offset) use ($reShare, $file) {
513
+                $this->assertEquals($file, $node);
514
+                if ($shareType === IShare::TYPE_USER) {
515
+                    return match($userId) {
516
+                        'userB' => [$reShare],
517
+                    };
518
+                } else {
519
+                    return [];
520
+                }
521
+            });
522
+        $manager->method('generalCreateChecks')->willThrowException(new GenericShareException());
523
+
524
+        $manager->expects($this->exactly(1))->method('updateShare')->with($reShare);
525
+
526
+        self::invokePrivate($manager, 'promoteReshares', [$share]);
527
+    }
528
+
529
+    public function testPromoteReshare(): void {
530
+        $manager = $this->createManagerMock()
531
+            ->setMethods(['updateShare', 'getSharesInFolder', 'generalCreateChecks'])
532
+            ->getMock();
533
+
534
+        $folder = $this->createFolderMock('/path/to/folder');
535
+
536
+        $subFolder = $this->createFolderMock('/path/to/folder/sub');
537
+
538
+        $otherFolder = $this->createFolderMock('/path/to/otherfolder/');
539
+
540
+        $share = $this->createMock(IShare::class);
541
+        $share->method('getShareType')->willReturn(IShare::TYPE_USER);
542
+        $share->method('getNodeType')->willReturn('folder');
543
+        $share->method('getSharedWith')->willReturn('userB');
544
+        $share->method('getNode')->willReturn($folder);
545
+
546
+        $reShare = $this->createMock(IShare::class);
547
+        $reShare->method('getShareType')->willReturn(IShare::TYPE_USER);
548
+        $reShare->method('getSharedBy')->willReturn('userB');
549
+        $reShare->method('getSharedWith')->willReturn('userC');
550
+        $reShare->method('getNode')->willReturn($folder);
551
+
552
+        $reShareInSubFolder = $this->createMock(IShare::class);
553
+        $reShareInSubFolder->method('getShareType')->willReturn(IShare::TYPE_USER);
554
+        $reShareInSubFolder->method('getSharedBy')->willReturn('userB');
555
+        $reShareInSubFolder->method('getNode')->willReturn($subFolder);
556
+
557
+        $reShareInOtherFolder = $this->createMock(IShare::class);
558
+        $reShareInOtherFolder->method('getShareType')->willReturn(IShare::TYPE_USER);
559
+        $reShareInOtherFolder->method('getSharedBy')->willReturn('userB');
560
+        $reShareInOtherFolder->method('getNode')->willReturn($otherFolder);
561
+
562
+        $this->defaultProvider->method('getSharesBy')
563
+            ->willReturnCallback(function ($userId, $shareType, $node, $reshares, $limit, $offset) use ($reShare, $reShareInSubFolder, $reShareInOtherFolder) {
564
+                if ($shareType === IShare::TYPE_USER) {
565
+                    return match($userId) {
566
+                        'userB' => [$reShare,$reShareInSubFolder,$reShareInOtherFolder],
567
+                    };
568
+                } else {
569
+                    return [];
570
+                }
571
+            });
572
+        $manager->method('generalCreateChecks')->willThrowException(new GenericShareException());
573
+
574
+        $manager->expects($this->exactly(2))->method('updateShare')->withConsecutive([$reShare], [$reShareInSubFolder]);
575
+
576
+        self::invokePrivate($manager, 'promoteReshares', [$share]);
577
+    }
578
+
579
+    public function testPromoteReshareWhenUserHasAnotherShare(): void {
580
+        $manager = $this->createManagerMock()
581
+            ->setMethods(['updateShare', 'getSharesInFolder', 'getSharedWith', 'generalCreateChecks'])
582
+            ->getMock();
583
+
584
+        $folder = $this->createFolderMock('/path/to/folder');
585
+
586
+        $share = $this->createMock(IShare::class);
587
+        $share->method('getShareType')->willReturn(IShare::TYPE_USER);
588
+        $share->method('getNodeType')->willReturn('folder');
589
+        $share->method('getSharedWith')->willReturn('userB');
590
+        $share->method('getNode')->willReturn($folder);
591
+
592
+        $reShare = $this->createMock(IShare::class);
593
+        $reShare->method('getShareType')->willReturn(IShare::TYPE_USER);
594
+        $reShare->method('getNodeType')->willReturn('folder');
595
+        $reShare->method('getSharedBy')->willReturn('userB');
596
+        $reShare->method('getNode')->willReturn($folder);
597
+
598
+        $this->defaultProvider->method('getSharesBy')->willReturn([$reShare]);
599
+        $manager->method('generalCreateChecks')->willReturn(true);
600
+
601
+        /* No share is promoted because generalCreateChecks does not throw */
602
+        $manager->expects($this->never())->method('updateShare');
603
+
604
+        self::invokePrivate($manager, 'promoteReshares', [$share]);
605
+    }
606
+
607
+    public function testPromoteReshareOfUsersInGroupShare(): void {
608
+        $manager = $this->createManagerMock()
609
+            ->setMethods(['updateShare', 'getSharesInFolder', 'getSharedWith', 'generalCreateChecks'])
610
+            ->getMock();
611
+
612
+        $folder = $this->createFolderMock('/path/to/folder');
613
+
614
+        $userA = $this->createMock(IUser::class);
615
+        $userA->method('getUID')->willReturn('userA');
616
+
617
+        $share = $this->createMock(IShare::class);
618
+        $share->method('getShareType')->willReturn(IShare::TYPE_GROUP);
619
+        $share->method('getNodeType')->willReturn('folder');
620
+        $share->method('getSharedWith')->willReturn('Group');
621
+        $share->method('getNode')->willReturn($folder);
622
+        $share->method('getShareOwner')->willReturn($userA);
623
+
624
+        $reShare1 = $this->createMock(IShare::class);
625
+        $reShare1->method('getShareType')->willReturn(IShare::TYPE_USER);
626
+        $reShare1->method('getNodeType')->willReturn('folder');
627
+        $reShare1->method('getSharedBy')->willReturn('userB');
628
+        $reShare1->method('getNode')->willReturn($folder);
629
+
630
+        $reShare2 = $this->createMock(IShare::class);
631
+        $reShare2->method('getShareType')->willReturn(IShare::TYPE_USER);
632
+        $reShare2->method('getNodeType')->willReturn('folder');
633
+        $reShare2->method('getSharedBy')->willReturn('userC');
634
+        $reShare2->method('getNode')->willReturn($folder);
635
+
636
+        $userB = $this->createMock(IUser::class);
637
+        $userB->method('getUID')->willReturn('userB');
638
+        $userC = $this->createMock(IUser::class);
639
+        $userC->method('getUID')->willReturn('userC');
640
+        $group = $this->createMock(IGroup::class);
641
+        $group->method('getUsers')->willReturn([$userB, $userC]);
642
+        $this->groupManager->method('get')->with('Group')->willReturn($group);
643
+
644
+        $this->defaultProvider->method('getSharesBy')
645
+            ->willReturnCallback(function ($userId, $shareType, $node, $reshares, $limit, $offset) use ($reShare1, $reShare2) {
646
+                if ($shareType === IShare::TYPE_USER) {
647
+                    return match($userId) {
648
+                        'userB' => [$reShare1],
649
+                        'userC' => [$reShare2],
650
+                    };
651
+                } else {
652
+                    return [];
653
+                }
654
+            });
655
+        $manager->method('generalCreateChecks')->willThrowException(new GenericShareException());
656
+
657
+        $manager->method('getSharedWith')->willReturn([]);
658
+
659
+        $manager->expects($this->exactly(2))->method('updateShare')->withConsecutive([$reShare1], [$reShare2]);
660
+
661
+        self::invokePrivate($manager, 'promoteReshares', [$share]);
662
+    }
663
+
664
+    public function testGetShareById(): void {
665
+        $share = $this->createMock(IShare::class);
666
+
667
+        $this->defaultProvider
668
+            ->expects($this->once())
669
+            ->method('getShareById')
670
+            ->with(42)
671
+            ->willReturn($share);
672
+
673
+        $this->assertEquals($share, $this->manager->getShareById('default:42'));
674
+    }
675
+
676
+
677
+    public function testGetExpiredShareById(): void {
678
+        $this->expectException(\OCP\Share\Exceptions\ShareNotFound::class);
679
+
680
+        $manager = $this->createManagerMock()
681
+            ->setMethods(['deleteShare'])
682
+            ->getMock();
683
+
684
+        $date = new \DateTime();
685
+        $date->setTime(0, 0, 0);
686
+
687
+        $share = $this->manager->newShare();
688
+        $share->setExpirationDate($date)
689
+            ->setShareType(IShare::TYPE_LINK);
690
+
691
+        $this->defaultProvider->expects($this->once())
692
+            ->method('getShareById')
693
+            ->with('42')
694
+            ->willReturn($share);
695
+
696
+        $manager->expects($this->once())
697
+            ->method('deleteShare')
698
+            ->with($share);
699
+
700
+        $manager->getShareById('default:42');
701
+    }
702
+
703
+
704
+    public function testVerifyPasswordNullButEnforced(): void {
705
+        $this->expectException(\InvalidArgumentException::class);
706
+        $this->expectExceptionMessage('Passwords are enforced for link and mail shares');
707
+
708
+        $this->config->method('getAppValue')->willReturnMap([
709
+            ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
710
+            ['core', 'shareapi_enforce_links_password', 'no', 'yes'],
711
+        ]);
712
+
713
+        self::invokePrivate($this->manager, 'verifyPassword', [null]);
714
+    }
715
+
716
+    public function testVerifyPasswordNotEnforcedGroup(): void {
717
+        $this->config->method('getAppValue')->willReturnMap([
718
+            ['core', 'shareapi_enforce_links_password_excluded_groups', '', '["admin"]'],
719
+            ['core', 'shareapi_enforce_links_password', 'no', 'yes'],
720
+        ]);
721
+
722
+        // Create admin user
723
+        $user = $this->createMock(IUser::class);
724
+        $this->userSession->method('getUser')->willReturn($user);
725
+        $this->groupManager->method('getUserGroupIds')->with($user)->willReturn(['admin']);
726
+
727
+        $result = self::invokePrivate($this->manager, 'verifyPassword', [null]);
728
+        $this->assertNull($result);
729
+    }
730
+
731
+    public function testVerifyPasswordNotEnforcedMultipleGroups(): void {
732
+        $this->config->method('getAppValue')->willReturnMap([
733
+            ['core', 'shareapi_enforce_links_password_excluded_groups', '', '["admin", "special"]'],
734
+            ['core', 'shareapi_enforce_links_password', 'no', 'yes'],
735
+        ]);
736
+
737
+        // Create admin user
738
+        $user = $this->createMock(IUser::class);
739
+        $this->userSession->method('getUser')->willReturn($user);
740
+        $this->groupManager->method('getUserGroupIds')->with($user)->willReturn(['special']);
741
+
742
+        $result = self::invokePrivate($this->manager, 'verifyPassword', [null]);
743
+        $this->assertNull($result);
744
+    }
745
+
746
+    public function testVerifyPasswordNull(): void {
747
+        $this->config->method('getAppValue')->willReturnMap([
748
+            ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
749
+            ['core', 'shareapi_enforce_links_password', 'no', 'no'],
750
+        ]);
751
+
752
+        $result = self::invokePrivate($this->manager, 'verifyPassword', [null]);
753
+        $this->assertNull($result);
754
+    }
755
+
756
+    public function testVerifyPasswordHook(): void {
757
+        $this->config->method('getAppValue')->willReturnMap([
758
+            ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
759
+            ['core', 'shareapi_enforce_links_password', 'no', 'no'],
760
+        ]);
761
+
762
+        $this->dispatcher->expects($this->once())->method('dispatchTyped')
763
+            ->willReturnCallback(function (Event $event) {
764
+                $this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
765
+                /** @var ValidatePasswordPolicyEvent $event */
766
+                $this->assertSame('password', $event->getPassword());
767
+            }
768
+            );
769
+
770
+        $result = self::invokePrivate($this->manager, 'verifyPassword', ['password']);
771
+        $this->assertNull($result);
772
+    }
773
+
774
+
775
+    public function testVerifyPasswordHookFails(): void {
776
+        $this->expectException(\Exception::class);
777
+        $this->expectExceptionMessage('password not accepted');
778
+
779
+        $this->config->method('getAppValue')->willReturnMap([
780
+            ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
781
+            ['core', 'shareapi_enforce_links_password', 'no', 'no'],
782
+        ]);
783
+
784
+        $this->dispatcher->expects($this->once())->method('dispatchTyped')
785
+            ->willReturnCallback(function (Event $event) {
786
+                $this->assertInstanceOf(ValidatePasswordPolicyEvent::class, $event);
787
+                /** @var ValidatePasswordPolicyEvent $event */
788
+                $this->assertSame('password', $event->getPassword());
789
+                throw new HintException('password not accepted');
790
+            }
791
+            );
792
+
793
+        self::invokePrivate($this->manager, 'verifyPassword', ['password']);
794
+    }
795
+
796
+    public function createShare($id, $type, $node, $sharedWith, $sharedBy, $shareOwner,
797
+        $permissions, $expireDate = null, $password = null, $attributes = null) {
798
+        $share = $this->createMock(IShare::class);
799
+
800
+        $share->method('getShareType')->willReturn($type);
801
+        $share->method('getSharedWith')->willReturn($sharedWith);
802
+        $share->method('getSharedBy')->willReturn($sharedBy);
803
+        $share->method('getShareOwner')->willReturn($shareOwner);
804
+        $share->method('getNode')->willReturn($node);
805
+        if ($node && $node->getId()) {
806
+            $share->method('getNodeId')->willReturn($node->getId());
807
+        }
808
+        $share->method('getPermissions')->willReturn($permissions);
809
+        $share->method('getAttributes')->willReturn($attributes);
810
+        $share->method('getExpirationDate')->willReturn($expireDate);
811
+        $share->method('getPassword')->willReturn($password);
812
+
813
+        return $share;
814
+    }
815
+
816
+    public function dataGeneralChecks() {
817
+        $user0 = 'user0';
818
+        $user2 = 'user1';
819
+        $group0 = 'group0';
820
+        $owner = $this->createMock(IUser::class);
821
+        $owner->method('getUID')
822
+            ->willReturn($user0);
823
+
824
+        $file = $this->createMock(File::class);
825
+        $node = $this->createMock(Node::class);
826
+        $storage = $this->createMock(IStorage::class);
827
+        $storage->method('instanceOfStorage')
828
+            ->with('\OCA\Files_Sharing\External\Storage')
829
+            ->willReturn(false);
830
+        $file->method('getStorage')
831
+            ->willReturn($storage);
832
+        $file->method('getId')->willReturn(108);
833
+        $node->method('getStorage')
834
+            ->willReturn($storage);
835
+        $node->method('getId')->willReturn(108);
836
+
837
+        $data = [
838
+            [$this->createShare(null, IShare::TYPE_USER, $file, null, $user0, $user0, 31, null, null), 'Share recipient is not a valid user', true],
839
+            [$this->createShare(null, IShare::TYPE_USER, $file, $group0, $user0, $user0, 31, null, null), 'Share recipient is not a valid user', true],
840
+            [$this->createShare(null, IShare::TYPE_USER, $file, '[email protected]', $user0, $user0, 31, null, null), 'Share recipient is not a valid user', true],
841
+            [$this->createShare(null, IShare::TYPE_GROUP, $file, null, $user0, $user0, 31, null, null), 'Share recipient is not a valid group', true],
842
+            [$this->createShare(null, IShare::TYPE_GROUP, $file, $user2, $user0, $user0, 31, null, null), 'Share recipient is not a valid group', true],
843
+            [$this->createShare(null, IShare::TYPE_GROUP, $file, '[email protected]', $user0, $user0, 31, null, null), 'Share recipient is not a valid group', true],
844
+            [$this->createShare(null, IShare::TYPE_LINK, $file, $user2, $user0, $user0, 31, null, null), 'Share recipient should be empty', true],
845
+            [$this->createShare(null, IShare::TYPE_LINK, $file, $group0, $user0, $user0, 31, null, null), 'Share recipient should be empty', true],
846
+            [$this->createShare(null, IShare::TYPE_LINK, $file, '[email protected]', $user0, $user0, 31, null, null), 'Share recipient should be empty', true],
847
+            [$this->createShare(null, -1, $file, null, $user0, $user0, 31, null, null), 'Unknown share type', true],
848
+
849
+            [$this->createShare(null, IShare::TYPE_USER, $file, $user2, null, $user0, 31, null, null), 'Share initiator must be set', true],
850
+            [$this->createShare(null, IShare::TYPE_GROUP, $file, $group0, null, $user0, 31, null, null), 'Share initiator must be set', true],
851
+            [$this->createShare(null, IShare::TYPE_LINK, $file, null, null, $user0, 31, null, null), 'Share initiator must be set', true],
852
+
853
+            [$this->createShare(null, IShare::TYPE_USER, $file, $user0, $user0, $user0, 31, null, null), 'Cannot share with yourself', true],
854
+
855
+            [$this->createShare(null, IShare::TYPE_USER, null, $user2, $user0, $user0, 31, null, null), 'Shared path must be set', true],
856
+            [$this->createShare(null, IShare::TYPE_GROUP, null, $group0, $user0, $user0, 31, null, null), 'Shared path must be set', true],
857
+            [$this->createShare(null, IShare::TYPE_LINK, null, null, $user0, $user0, 31, null, null), 'Shared path must be set', true],
858
+
859
+            [$this->createShare(null, IShare::TYPE_USER, $node, $user2, $user0, $user0, 31, null, null), 'Shared path must be either a file or a folder', true],
860
+            [$this->createShare(null, IShare::TYPE_GROUP, $node, $group0, $user0, $user0, 31, null, null), 'Shared path must be either a file or a folder', true],
861
+            [$this->createShare(null, IShare::TYPE_LINK, $node, null, $user0, $user0, 31, null, null), 'Shared path must be either a file or a folder', true],
862
+        ];
863
+
864
+        $nonShareAble = $this->createMock(Folder::class);
865
+        $nonShareAble->method('getId')->willReturn(108);
866
+        $nonShareAble->method('isShareable')->willReturn(false);
867
+        $nonShareAble->method('getPath')->willReturn('path');
868
+        $nonShareAble->method('getName')->willReturn('name');
869
+        $nonShareAble->method('getOwner')
870
+            ->willReturn($owner);
871
+        $nonShareAble->method('getStorage')
872
+            ->willReturn($storage);
873
+
874
+        $data[] = [$this->createShare(null, IShare::TYPE_USER, $nonShareAble, $user2, $user0, $user0, 31, null, null), 'You are not allowed to share name', true];
875
+        $data[] = [$this->createShare(null, IShare::TYPE_GROUP, $nonShareAble, $group0, $user0, $user0, 31, null, null), 'You are not allowed to share name', true];
876
+        $data[] = [$this->createShare(null, IShare::TYPE_LINK, $nonShareAble, null, $user0, $user0, 31, null, null), 'You are not allowed to share name', true];
877
+
878
+        $limitedPermssions = $this->createMock(File::class);
879
+        $limitedPermssions->method('isShareable')->willReturn(true);
880
+        $limitedPermssions->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ);
881
+        $limitedPermssions->method('getId')->willReturn(108);
882
+        $limitedPermssions->method('getPath')->willReturn('path');
883
+        $limitedPermssions->method('getName')->willReturn('name');
884
+        $limitedPermssions->method('getOwner')
885
+            ->willReturn($owner);
886
+        $limitedPermssions->method('getStorage')
887
+            ->willReturn($storage);
888
+
889
+        $data[] = [$this->createShare(null, IShare::TYPE_USER, $limitedPermssions, $user2, $user0, $user0, null, null, null), 'Valid permissions are required for sharing', true];
890
+        $data[] = [$this->createShare(null, IShare::TYPE_GROUP, $limitedPermssions, $group0, $user0, $user0, null, null, null), 'Valid permissions are required for sharing', true];
891
+        $data[] = [$this->createShare(null, IShare::TYPE_LINK, $limitedPermssions, null, $user0, $user0, null, null, null), 'Valid permissions are required for sharing', true];
892
+
893
+        $mount = $this->createMock(MoveableMount::class);
894
+        $limitedPermssions->method('getMountPoint')->willReturn($mount);
895
+
896
+        // increase permissions of a re-share
897
+        $data[] = [$this->createShare(null, IShare::TYPE_GROUP, $limitedPermssions, $group0, $user0, $user0, 17, null, null), 'Cannot increase permissions of path', true];
898
+        $data[] = [$this->createShare(null, IShare::TYPE_USER, $limitedPermssions, $user2, $user0, $user0, 3, null, null), 'Cannot increase permissions of path', true];
899
+
900
+        $nonMovableStorage = $this->createMock(IStorage::class);
901
+        $nonMovableStorage->method('instanceOfStorage')
902
+            ->with('\OCA\Files_Sharing\External\Storage')
903
+            ->willReturn(false);
904
+        $nonMovableStorage->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_ALL);
905
+        $nonMoveableMountPermssions = $this->createMock(Folder::class);
906
+        $nonMoveableMountPermssions->method('isShareable')->willReturn(true);
907
+        $nonMoveableMountPermssions->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ);
908
+        $nonMoveableMountPermssions->method('getId')->willReturn(108);
909
+        $nonMoveableMountPermssions->method('getPath')->willReturn('path');
910
+        $nonMoveableMountPermssions->method('getName')->willReturn('name');
911
+        $nonMoveableMountPermssions->method('getInternalPath')->willReturn('');
912
+        $nonMoveableMountPermssions->method('getOwner')
913
+            ->willReturn($owner);
914
+        $nonMoveableMountPermssions->method('getStorage')
915
+            ->willReturn($nonMovableStorage);
916
+
917
+        $data[] = [$this->createShare(null, IShare::TYPE_USER, $nonMoveableMountPermssions, $user2, $user0, $user0, 11, null, null), 'Cannot increase permissions of path', false];
918
+        $data[] = [$this->createShare(null, IShare::TYPE_GROUP, $nonMoveableMountPermssions, $group0, $user0, $user0, 11, null, null), 'Cannot increase permissions of path', false];
919
+
920
+        $rootFolder = $this->createMock(Folder::class);
921
+        $rootFolder->method('isShareable')->willReturn(true);
922
+        $rootFolder->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_ALL);
923
+        $rootFolder->method('getId')->willReturn(42);
924
+
925
+        $data[] = [$this->createShare(null, IShare::TYPE_USER, $rootFolder, $user2, $user0, $user0, 30, null, null), 'You cannot share your root folder', true];
926
+        $data[] = [$this->createShare(null, IShare::TYPE_GROUP, $rootFolder, $group0, $user0, $user0, 2, null, null), 'You cannot share your root folder', true];
927
+        $data[] = [$this->createShare(null, IShare::TYPE_LINK, $rootFolder, null, $user0, $user0, 16, null, null), 'You cannot share your root folder', true];
928
+
929
+        $allPermssionsFiles = $this->createMock(File::class);
930
+        $allPermssionsFiles->method('isShareable')->willReturn(true);
931
+        $allPermssionsFiles->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_ALL);
932
+        $allPermssionsFiles->method('getId')->willReturn(187);
933
+        $allPermssionsFiles->method('getOwner')
934
+            ->willReturn($owner);
935
+        $allPermssionsFiles->method('getStorage')
936
+            ->willReturn($storage);
937
+
938
+        // test invalid CREATE or DELETE permissions
939
+        $data[] = [$this->createShare(null, IShare::TYPE_USER, $allPermssionsFiles, $user2, $user0, $user0, \OCP\Constants::PERMISSION_ALL, null, null), 'File shares cannot have create or delete permissions', true];
940
+        $data[] = [$this->createShare(null, IShare::TYPE_GROUP, $allPermssionsFiles, $group0, $user0, $user0, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE, null, null), 'File shares cannot have create or delete permissions', true];
941
+        $data[] = [$this->createShare(null, IShare::TYPE_LINK, $allPermssionsFiles, null, $user0, $user0, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_DELETE, null, null), 'File shares cannot have create or delete permissions', true];
942
+
943
+        $allPermssions = $this->createMock(Folder::class);
944
+        $allPermssions->method('isShareable')->willReturn(true);
945
+        $allPermssions->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_ALL);
946
+        $allPermssions->method('getId')->willReturn(108);
947
+        $allPermssions->method('getOwner')
948
+            ->willReturn($owner);
949
+        $allPermssions->method('getStorage')
950
+            ->willReturn($storage);
951
+
952
+        $data[] = [$this->createShare(null, IShare::TYPE_USER, $allPermssions, $user2, $user0, $user0, 30, null, null), 'Shares need at least read permissions', true];
953
+        $data[] = [$this->createShare(null, IShare::TYPE_GROUP, $allPermssions, $group0, $user0, $user0, 2, null, null), 'Shares need at least read permissions', true];
954
+
955
+        // test invalid permissions
956
+        $data[] = [$this->createShare(null, IShare::TYPE_USER, $allPermssions, $user2, $user0, $user0, 32, null, null), 'Valid permissions are required for sharing', true];
957
+        $data[] = [$this->createShare(null, IShare::TYPE_GROUP, $allPermssions, $group0, $user0, $user0, 63, null, null), 'Valid permissions are required for sharing', true];
958
+        $data[] = [$this->createShare(null, IShare::TYPE_LINK, $allPermssions, null, $user0, $user0, -1, null, null), 'Valid permissions are required for sharing', true];
959
+
960
+        // working shares
961
+        $data[] = [$this->createShare(null, IShare::TYPE_USER, $allPermssions, $user2, $user0, $user0, 31, null, null), null, false];
962
+        $data[] = [$this->createShare(null, IShare::TYPE_GROUP, $allPermssions, $group0, $user0, $user0, 3, null, null), null, false];
963
+        $data[] = [$this->createShare(null, IShare::TYPE_LINK, $allPermssions, null, $user0, $user0, 17, null, null), null, false];
964
+
965
+
966
+        $remoteStorage = $this->createMock(IStorage::class);
967
+        $remoteStorage->method('instanceOfStorage')
968
+            ->with('\OCA\Files_Sharing\External\Storage')
969
+            ->willReturn(true);
970
+        $remoteFile = $this->createMock(Folder::class);
971
+        $remoteFile->method('isShareable')->willReturn(true);
972
+        $remoteFile->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ ^ \OCP\Constants::PERMISSION_UPDATE);
973
+        $remoteFile->method('getId')->willReturn(108);
974
+        $remoteFile->method('getOwner')
975
+            ->willReturn($owner);
976
+        $remoteFile->method('getStorage')
977
+            ->willReturn($storage);
978
+        $data[] = [$this->createShare(null, IShare::TYPE_REMOTE, $remoteFile, $user2, $user0, $user0, 1, null, null), null, false];
979
+        $data[] = [$this->createShare(null, IShare::TYPE_REMOTE, $remoteFile, $user2, $user0, $user0, 3, null, null), null, false];
980
+        $data[] = [$this->createShare(null, IShare::TYPE_REMOTE, $remoteFile, $user2, $user0, $user0, 31, null, null), 'Cannot increase permissions of ', true];
981
+
982
+        return $data;
983
+    }
984
+
985
+    /**
986
+     * @dataProvider dataGeneralChecks
987
+     *
988
+     * @param $share
989
+     * @param $exceptionMessage
990
+     * @param $exception
991
+     */
992
+    public function testGeneralChecks($share, $exceptionMessage, $exception): void {
993
+        $thrown = null;
994
+
995
+        $this->userManager->method('userExists')->willReturnMap([
996
+            ['user0', true],
997
+            ['user1', true],
998
+        ]);
999
+
1000
+        $this->groupManager->method('groupExists')->willReturnMap([
1001
+            ['group0', true],
1002
+        ]);
1003
+
1004
+        $userFolder = $this->createMock(Folder::class);
1005
+        $userFolder->expects($this->any())
1006
+            ->method('getId')
1007
+            ->willReturn(42);
1008
+        // Id 108 is used in the data to refer to the node of the share.
1009
+        $userFolder->method('getById')
1010
+            ->with(108)
1011
+            ->willReturn([$share->getNode()]);
1012
+        $userFolder->expects($this->any())
1013
+            ->method('getRelativePath')
1014
+            ->willReturnArgument(0);
1015
+        $this->rootFolder->method('getUserFolder')->willReturn($userFolder);
1016
+
1017
+
1018
+        try {
1019
+            self::invokePrivate($this->manager, 'generalCreateChecks', [$share]);
1020
+            $thrown = false;
1021
+        } catch (\OCP\Share\Exceptions\GenericShareException $e) {
1022
+            $this->assertEquals($exceptionMessage, $e->getHint());
1023
+            $thrown = true;
1024
+        } catch (\InvalidArgumentException $e) {
1025
+            $this->assertEquals($exceptionMessage, $e->getMessage());
1026
+            $thrown = true;
1027
+        }
1028
+
1029
+        $this->assertSame($exception, $thrown);
1030
+    }
1031
+
1032
+
1033
+    public function testGeneralCheckShareRoot(): void {
1034
+        $this->expectException(\InvalidArgumentException::class);
1035
+        $this->expectExceptionMessage('You cannot share your root folder');
1036
+
1037
+        $thrown = null;
1038
+
1039
+        $this->userManager->method('userExists')->willReturnMap([
1040
+            ['user0', true],
1041
+            ['user1', true],
1042
+        ]);
1043
+
1044
+        $userFolder = $this->createMock(Folder::class);
1045
+        $userFolder->method('isSubNode')->with($userFolder)->willReturn(false);
1046
+        $this->rootFolder->method('getUserFolder')->willReturn($userFolder);
1047
+
1048
+        $share = $this->manager->newShare();
1049
+
1050
+        $share->setShareType(IShare::TYPE_USER)
1051
+            ->setSharedWith('user0')
1052
+            ->setSharedBy('user1')
1053
+            ->setNode($userFolder);
1054
+
1055
+        self::invokePrivate($this->manager, 'generalCreateChecks', [$share]);
1056
+    }
1057
+
1058
+    public function validateExpirationDateInternalProvider() {
1059
+        return [[IShare::TYPE_USER], [IShare::TYPE_REMOTE], [IShare::TYPE_REMOTE_GROUP]];
1060
+    }
1061
+
1062
+    /**
1063
+     * @dataProvider validateExpirationDateInternalProvider
1064
+     */
1065
+    public function testValidateExpirationDateInternalInPast($shareType): void {
1066
+        $this->expectException(\OCP\Share\Exceptions\GenericShareException::class);
1067
+        $this->expectExceptionMessage('Expiration date is in the past');
1068
+
1069
+        // Expire date in the past
1070
+        $past = new \DateTime();
1071
+        $past->sub(new \DateInterval('P1D'));
1072
+
1073
+        $share = $this->manager->newShare();
1074
+        $share->setShareType($shareType);
1075
+        $share->setExpirationDate($past);
1076
+
1077
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1078
+    }
1079
+
1080
+    /**
1081
+     * @dataProvider validateExpirationDateInternalProvider
1082
+     */
1083
+    public function testValidateExpirationDateInternalEnforceButNotSet($shareType): void {
1084
+        $this->expectException(\InvalidArgumentException::class);
1085
+        $this->expectExceptionMessage('Expiration date is enforced');
1086
+
1087
+        $share = $this->manager->newShare();
1088
+        $share->setProviderId('foo')->setId('bar');
1089
+        $share->setShareType($shareType);
1090
+        if ($shareType === IShare::TYPE_USER) {
1091
+            $this->config->method('getAppValue')
1092
+                ->willReturnMap([
1093
+                    ['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1094
+                    ['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1095
+                ]);
1096
+        } else {
1097
+            $this->config->method('getAppValue')
1098
+                ->willReturnMap([
1099
+                    ['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1100
+                    ['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1101
+                ]);
1102
+        }
1103
+
1104
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1105
+    }
1106
+
1107
+    /**
1108
+     * @dataProvider validateExpirationDateInternalProvider
1109
+     */
1110
+    public function testValidateExpirationDateInternalEnforceButNotEnabledAndNotSet($shareType): void {
1111
+        $share = $this->manager->newShare();
1112
+        $share->setProviderId('foo')->setId('bar');
1113
+        $share->setShareType($shareType);
1114
+
1115
+        if ($shareType === IShare::TYPE_USER) {
1116
+            $this->config->method('getAppValue')
1117
+                ->willReturnMap([
1118
+                    ['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1119
+                ]);
1120
+        } else {
1121
+            $this->config->method('getAppValue')
1122
+                ->willReturnMap([
1123
+                    ['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1124
+                ]);
1125
+        }
1126
+
1127
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1128
+
1129
+        $this->assertNull($share->getExpirationDate());
1130
+    }
1131
+
1132
+    /**
1133
+     * @dataProvider validateExpirationDateInternalProvider
1134
+     */
1135
+    public function testValidateExpirationDateInternalEnforceButNotSetNewShare($shareType): void {
1136
+        $share = $this->manager->newShare();
1137
+        $share->setShareType($shareType);
1138
+
1139
+        if ($shareType === IShare::TYPE_USER) {
1140
+            $this->config->method('getAppValue')
1141
+                ->willReturnMap([
1142
+                    ['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1143
+                    ['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1144
+                    ['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1145
+                    ['core', 'internal_defaultExpDays', '3', '3'],
1146
+                ]);
1147
+        } else {
1148
+            $this->config->method('getAppValue')
1149
+                ->willReturnMap([
1150
+                    ['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1151
+                    ['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1152
+                    ['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1153
+                    ['core', 'remote_defaultExpDays', '3', '3'],
1154
+                ]);
1155
+        }
1156
+
1157
+        $expected = new \DateTime('now', $this->timezone);
1158
+        $expected->setTime(0, 0, 0);
1159
+        $expected->add(new \DateInterval('P3D'));
1160
+
1161
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1162
+
1163
+        $this->assertNotNull($share->getExpirationDate());
1164
+        $this->assertEquals($expected, $share->getExpirationDate());
1165
+    }
1166
+
1167
+    /**
1168
+     * @dataProvider validateExpirationDateInternalProvider
1169
+     */
1170
+    public function testValidateExpirationDateInternalEnforceRelaxedDefaultButNotSetNewShare($shareType): void {
1171
+        $share = $this->manager->newShare();
1172
+        $share->setShareType($shareType);
1173
+
1174
+        if ($shareType === IShare::TYPE_USER) {
1175
+            $this->config->method('getAppValue')
1176
+                ->willReturnMap([
1177
+                    ['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1178
+                    ['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1179
+                    ['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1180
+                    ['core', 'internal_defaultExpDays', '3', '1'],
1181
+                ]);
1182
+        } else {
1183
+            $this->config->method('getAppValue')
1184
+                ->willReturnMap([
1185
+                    ['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1186
+                    ['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1187
+                    ['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1188
+                    ['core', 'remote_defaultExpDays', '3', '1'],
1189
+                ]);
1190
+        }
1191
+
1192
+        $expected = new \DateTime('now', $this->timezone);
1193
+        $expected->setTime(0, 0, 0);
1194
+        $expected->add(new \DateInterval('P1D'));
1195
+
1196
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1197
+
1198
+        $this->assertNotNull($share->getExpirationDate());
1199
+        $this->assertEquals($expected, $share->getExpirationDate());
1200
+    }
1201
+
1202
+    /**
1203
+     * @dataProvider validateExpirationDateInternalProvider
1204
+     */
1205
+    public function testValidateExpirationDateInternalEnforceTooFarIntoFuture($shareType): void {
1206
+        $this->expectException(\OCP\Share\Exceptions\GenericShareException::class);
1207
+        $this->expectExceptionMessage('Cannot set expiration date more than 3 days in the future');
1208
+
1209
+        $future = new \DateTime();
1210
+        $future->add(new \DateInterval('P7D'));
1211
+
1212
+        $share = $this->manager->newShare();
1213
+        $share->setShareType($shareType);
1214
+        $share->setExpirationDate($future);
1215
+
1216
+        if ($shareType === IShare::TYPE_USER) {
1217
+            $this->config->method('getAppValue')
1218
+                ->willReturnMap([
1219
+                    ['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1220
+                    ['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1221
+                    ['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1222
+                ]);
1223
+        } else {
1224
+            $this->config->method('getAppValue')
1225
+                ->willReturnMap([
1226
+                    ['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1227
+                    ['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1228
+                    ['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1229
+                ]);
1230
+        }
1231
+
1232
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1233
+    }
1234
+
1235
+    /**
1236
+     * @dataProvider validateExpirationDateInternalProvider
1237
+     */
1238
+    public function testValidateExpirationDateInternalEnforceValid($shareType): void {
1239
+        $future = new \DateTime('now', $this->dateTimeZone->getTimeZone());
1240
+        $future->add(new \DateInterval('P2D'));
1241
+        $future->setTime(1, 2, 3);
1242
+
1243
+        $expected = clone $future;
1244
+        $expected->setTime(0, 0, 0);
1245
+
1246
+        $share = $this->manager->newShare();
1247
+        $share->setShareType($shareType);
1248
+        $share->setExpirationDate($future);
1249
+
1250
+        if ($shareType === IShare::TYPE_USER) {
1251
+            $this->config->method('getAppValue')
1252
+                ->willReturnMap([
1253
+                    ['core', 'shareapi_enforce_internal_expire_date', 'no', 'yes'],
1254
+                    ['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1255
+                    ['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1256
+                ]);
1257
+        } else {
1258
+            $this->config->method('getAppValue')
1259
+                ->willReturnMap([
1260
+                    ['core', 'shareapi_enforce_remote_expire_date', 'no', 'yes'],
1261
+                    ['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1262
+                    ['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1263
+                ]);
1264
+        }
1265
+
1266
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1267
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1268
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($future) {
1269
+            return $data['expirationDate'] == $future;
1270
+        }));
1271
+
1272
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1273
+
1274
+        $this->assertEquals($expected, $share->getExpirationDate());
1275
+    }
1276
+
1277
+    /**
1278
+     * @dataProvider validateExpirationDateInternalProvider
1279
+     */
1280
+    public function testValidateExpirationDateInternalNoDefault($shareType): void {
1281
+        $date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
1282
+        $date->add(new \DateInterval('P5D'));
1283
+        $date->setTime(1, 2, 3);
1284
+
1285
+        $expected = clone $date;
1286
+        $expected->setTime(0, 0, 0);
1287
+
1288
+        $share = $this->manager->newShare();
1289
+        $share->setShareType($shareType);
1290
+        $share->setExpirationDate($date);
1291
+
1292
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1293
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1294
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1295
+            return $data['expirationDate'] == $expected && $data['passwordSet'] === false;
1296
+        }));
1297
+
1298
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1299
+
1300
+        $this->assertEquals($expected, $share->getExpirationDate());
1301
+    }
1302
+
1303
+    /**
1304
+     * @dataProvider validateExpirationDateInternalProvider
1305
+     */
1306
+    public function testValidateExpirationDateInternalNoDateNoDefault($shareType): void {
1307
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1308
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1309
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) {
1310
+            return $data['expirationDate'] === null && $data['passwordSet'] === true;
1311
+        }));
1312
+
1313
+        $share = $this->manager->newShare();
1314
+        $share->setShareType($shareType);
1315
+        $share->setPassword('password');
1316
+
1317
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1318
+
1319
+        $this->assertNull($share->getExpirationDate());
1320
+    }
1321
+
1322
+    /**
1323
+     * @dataProvider validateExpirationDateInternalProvider
1324
+     */
1325
+    public function testValidateExpirationDateInternalNoDateDefault($shareType): void {
1326
+        $share = $this->manager->newShare();
1327
+        $share->setShareType($shareType);
1328
+
1329
+        $expected = new \DateTime('now', $this->timezone);
1330
+        $expected->setTime(0, 0);
1331
+        $expected->add(new \DateInterval('P3D'));
1332
+        $expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1333
+
1334
+        if ($shareType === IShare::TYPE_USER) {
1335
+            $this->config->method('getAppValue')
1336
+                ->willReturnMap([
1337
+                    ['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1338
+                    ['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1339
+                    ['core', 'internal_defaultExpDays', '3', '3'],
1340
+                ]);
1341
+        } else {
1342
+            $this->config->method('getAppValue')
1343
+                ->willReturnMap([
1344
+                    ['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1345
+                    ['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1346
+                    ['core', 'remote_defaultExpDays', '3', '3'],
1347
+                ]);
1348
+        }
1349
+
1350
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1351
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1352
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1353
+            return $data['expirationDate'] == $expected;
1354
+        }));
1355
+
1356
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1357
+
1358
+        $this->assertEquals($expected, $share->getExpirationDate());
1359
+    }
1360
+
1361
+    /**
1362
+     * @dataProvider validateExpirationDateInternalProvider
1363
+     */
1364
+    public function testValidateExpirationDateInternalDefault($shareType): void {
1365
+        $future = new \DateTime('now', $this->timezone);
1366
+        $future->add(new \DateInterval('P5D'));
1367
+        $future->setTime(1, 2, 3);
1368
+
1369
+        $expected = clone $future;
1370
+        $expected->setTime(0, 0);
1371
+
1372
+        $share = $this->manager->newShare();
1373
+        $share->setShareType($shareType);
1374
+        $share->setExpirationDate($future);
1375
+
1376
+        if ($shareType === IShare::TYPE_USER) {
1377
+            $this->config->method('getAppValue')
1378
+                ->willReturnMap([
1379
+                    ['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1380
+                    ['core', 'shareapi_internal_expire_after_n_days', '7', '3'],
1381
+                    ['core', 'internal_defaultExpDays', '3', '1'],
1382
+                ]);
1383
+        } else {
1384
+            $this->config->method('getAppValue')
1385
+                ->willReturnMap([
1386
+                    ['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1387
+                    ['core', 'shareapi_remote_expire_after_n_days', '7', '3'],
1388
+                    ['core', 'remote_defaultExpDays', '3', '1'],
1389
+                ]);
1390
+        }
1391
+
1392
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1393
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1394
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1395
+            return $data['expirationDate'] == $expected;
1396
+        }));
1397
+
1398
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1399
+
1400
+        $this->assertEquals($expected, $share->getExpirationDate());
1401
+    }
1402
+
1403
+    /**
1404
+     * @dataProvider validateExpirationDateInternalProvider
1405
+     */
1406
+    public function testValidateExpirationDateInternalHookModification($shareType): void {
1407
+        $nextWeek = new \DateTime('now', $this->timezone);
1408
+        $nextWeek->add(new \DateInterval('P7D'));
1409
+        $nextWeek->setTime(0, 0, 0);
1410
+
1411
+        $save = clone $nextWeek;
1412
+
1413
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1414
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1415
+        $hookListener->expects($this->once())->method('listener')->willReturnCallback(function ($data) {
1416
+            $data['expirationDate']->sub(new \DateInterval('P2D'));
1417
+        });
1418
+
1419
+        $share = $this->manager->newShare();
1420
+        $share->setShareType($shareType);
1421
+        $share->setExpirationDate($nextWeek);
1422
+
1423
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1424
+
1425
+        $save->sub(new \DateInterval('P2D'));
1426
+        $this->assertEquals($save, $share->getExpirationDate());
1427
+    }
1428
+
1429
+    /**
1430
+     * @dataProvider validateExpirationDateInternalProvider
1431
+     */
1432
+    public function testValidateExpirationDateInternalHookException($shareType): void {
1433
+        $this->expectException(\Exception::class);
1434
+        $this->expectExceptionMessage('Invalid date!');
1435
+
1436
+        $nextWeek = new \DateTime();
1437
+        $nextWeek->add(new \DateInterval('P7D'));
1438
+        $nextWeek->setTime(0, 0, 0);
1439
+
1440
+        $share = $this->manager->newShare();
1441
+        $share->setShareType($shareType);
1442
+        $share->setExpirationDate($nextWeek);
1443
+
1444
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1445
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1446
+        $hookListener->expects($this->once())->method('listener')->willReturnCallback(function ($data) {
1447
+            $data['accepted'] = false;
1448
+            $data['message'] = 'Invalid date!';
1449
+        });
1450
+
1451
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1452
+    }
1453
+
1454
+    /**
1455
+     * @dataProvider validateExpirationDateInternalProvider
1456
+     */
1457
+    public function testValidateExpirationDateInternalExistingShareNoDefault($shareType): void {
1458
+        $share = $this->manager->newShare();
1459
+        $share->setShareType($shareType);
1460
+        $share->setId('42')->setProviderId('foo');
1461
+
1462
+        if ($shareType === IShare::TYPE_USER) {
1463
+            $this->config->method('getAppValue')
1464
+                ->willReturnMap([
1465
+                    ['core', 'shareapi_default_internal_expire_date', 'no', 'yes'],
1466
+                    ['core', 'shareapi_internal_expire_after_n_days', '7', '6'],
1467
+                ]);
1468
+        } else {
1469
+            $this->config->method('getAppValue')
1470
+                ->willReturnMap([
1471
+                    ['core', 'shareapi_default_remote_expire_date', 'no', 'yes'],
1472
+                    ['core', 'shareapi_remote_expire_after_n_days', '7', '6'],
1473
+                ]);
1474
+        }
1475
+
1476
+        self::invokePrivate($this->manager, 'validateExpirationDateInternal', [$share]);
1477
+
1478
+        $this->assertEquals(null, $share->getExpirationDate());
1479
+    }
1480
+
1481
+    public function testValidateExpirationDateInPast(): void {
1482
+        $this->expectException(\OCP\Share\Exceptions\GenericShareException::class);
1483
+        $this->expectExceptionMessage('Expiration date is in the past');
1484
+
1485
+        // Expire date in the past
1486
+        $past = new \DateTime();
1487
+        $past->sub(new \DateInterval('P1D'));
1488
+
1489
+        $share = $this->manager->newShare();
1490
+        $share->setExpirationDate($past);
1491
+
1492
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1493
+    }
1494
+
1495
+    public function testValidateExpirationDateEnforceButNotSet(): void {
1496
+        $this->expectException(\InvalidArgumentException::class);
1497
+        $this->expectExceptionMessage('Expiration date is enforced');
1498
+
1499
+        $share = $this->manager->newShare();
1500
+        $share->setProviderId('foo')->setId('bar');
1501
+
1502
+        $this->config->method('getAppValue')
1503
+            ->willReturnMap([
1504
+                ['core', 'shareapi_default_expire_date', 'no', 'yes'],
1505
+                ['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1506
+            ]);
1507
+
1508
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1509
+    }
1510
+
1511
+    public function testValidateExpirationDateEnforceButNotEnabledAndNotSet(): void {
1512
+        $share = $this->manager->newShare();
1513
+        $share->setProviderId('foo')->setId('bar');
1514
+
1515
+        $this->config->method('getAppValue')
1516
+            ->willReturnMap([
1517
+                ['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1518
+            ]);
1519
+
1520
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1521
+
1522
+        $this->assertNull($share->getExpirationDate());
1523
+    }
1524
+
1525
+    public function testValidateExpirationDateEnforceButNotSetNewShare(): void {
1526
+        $share = $this->manager->newShare();
1527
+
1528
+        $this->config->method('getAppValue')
1529
+            ->willReturnMap([
1530
+                ['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1531
+                ['core', 'shareapi_expire_after_n_days', '7', '3'],
1532
+                ['core', 'shareapi_default_expire_date', 'no', 'yes'],
1533
+                ['core', 'link_defaultExpDays', '3', '3'],
1534
+            ]);
1535
+
1536
+        $expected = new \DateTime('now', $this->timezone);
1537
+        $expected->setTime(0, 0, 0);
1538
+        $expected->add(new \DateInterval('P3D'));
1539
+
1540
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1541
+
1542
+        $this->assertNotNull($share->getExpirationDate());
1543
+        $this->assertEquals($expected, $share->getExpirationDate());
1544
+    }
1545
+
1546
+    public function testValidateExpirationDateEnforceRelaxedDefaultButNotSetNewShare(): void {
1547
+        $share = $this->manager->newShare();
1548
+
1549
+        $this->config->method('getAppValue')
1550
+            ->willReturnMap([
1551
+                ['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1552
+                ['core', 'shareapi_expire_after_n_days', '7', '3'],
1553
+                ['core', 'shareapi_default_expire_date', 'no', 'yes'],
1554
+                ['core', 'link_defaultExpDays', '3', '1'],
1555
+            ]);
1556
+
1557
+        $expected = new \DateTime('now', $this->timezone);
1558
+        $expected->setTime(0, 0, 0);
1559
+        $expected->add(new \DateInterval('P1D'));
1560
+
1561
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1562
+
1563
+        $this->assertNotNull($share->getExpirationDate());
1564
+        $this->assertEquals($expected, $share->getExpirationDate());
1565
+    }
1566
+
1567
+    public function testValidateExpirationDateEnforceTooFarIntoFuture(): void {
1568
+        $this->expectException(\OCP\Share\Exceptions\GenericShareException::class);
1569
+        $this->expectExceptionMessage('Cannot set expiration date more than 3 days in the future');
1570 1570
 
1571
-		$future = new \DateTime();
1572
-		$future->add(new \DateInterval('P7D'));
1571
+        $future = new \DateTime();
1572
+        $future->add(new \DateInterval('P7D'));
1573 1573
 
1574
-		$share = $this->manager->newShare();
1575
-		$share->setExpirationDate($future);
1574
+        $share = $this->manager->newShare();
1575
+        $share->setExpirationDate($future);
1576 1576
 
1577
-		$this->config->method('getAppValue')
1578
-			->willReturnMap([
1579
-				['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1580
-				['core', 'shareapi_expire_after_n_days', '7', '3'],
1581
-				['core', 'shareapi_default_expire_date', 'no', 'yes'],
1582
-			]);
1577
+        $this->config->method('getAppValue')
1578
+            ->willReturnMap([
1579
+                ['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1580
+                ['core', 'shareapi_expire_after_n_days', '7', '3'],
1581
+                ['core', 'shareapi_default_expire_date', 'no', 'yes'],
1582
+            ]);
1583 1583
 
1584
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1585
-	}
1584
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1585
+    }
1586 1586
 
1587
-	public function testValidateExpirationDateEnforceValid(): void {
1588
-		$future = new \DateTime('now', $this->timezone);
1589
-		$future->add(new \DateInterval('P2D'));
1590
-		$future->setTime(1, 2, 3);
1587
+    public function testValidateExpirationDateEnforceValid(): void {
1588
+        $future = new \DateTime('now', $this->timezone);
1589
+        $future->add(new \DateInterval('P2D'));
1590
+        $future->setTime(1, 2, 3);
1591 1591
 
1592
-		$expected = clone $future;
1593
-		$expected->setTime(0, 0, 0);
1592
+        $expected = clone $future;
1593
+        $expected->setTime(0, 0, 0);
1594 1594
 
1595
-		$share = $this->manager->newShare();
1596
-		$share->setExpirationDate($future);
1597
-
1598
-		$this->config->method('getAppValue')
1599
-			->willReturnMap([
1600
-				['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1601
-				['core', 'shareapi_expire_after_n_days', '7', '3'],
1602
-				['core', 'shareapi_default_expire_date', 'no', 'yes'],
1603
-			]);
1604
-
1605
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1606
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1607
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($future) {
1608
-			return $data['expirationDate'] == $future;
1609
-		}));
1595
+        $share = $this->manager->newShare();
1596
+        $share->setExpirationDate($future);
1597
+
1598
+        $this->config->method('getAppValue')
1599
+            ->willReturnMap([
1600
+                ['core', 'shareapi_enforce_expire_date', 'no', 'yes'],
1601
+                ['core', 'shareapi_expire_after_n_days', '7', '3'],
1602
+                ['core', 'shareapi_default_expire_date', 'no', 'yes'],
1603
+            ]);
1604
+
1605
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1606
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1607
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($future) {
1608
+            return $data['expirationDate'] == $future;
1609
+        }));
1610 1610
 
1611
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1612
-
1613
-		$this->assertEquals($expected, $share->getExpirationDate());
1614
-	}
1615
-
1616
-	public function testValidateExpirationDateNoDefault(): void {
1617
-		$date = new \DateTime('now', $this->timezone);
1618
-		$date->add(new \DateInterval('P5D'));
1619
-		$date->setTime(1, 2, 3);
1620
-
1621
-		$expected = clone $date;
1622
-		$expected->setTime(0, 0);
1623
-		$expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1624
-
1625
-		$share = $this->manager->newShare();
1626
-		$share->setExpirationDate($date);
1627
-
1628
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1629
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1630
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1631
-			return $data['expirationDate'] == $expected && $data['passwordSet'] === false;
1632
-		}));
1633
-
1634
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1635
-
1636
-		$this->assertEquals($expected, $share->getExpirationDate());
1637
-	}
1638
-
1639
-	public function testValidateExpirationDateNoDateNoDefault(): void {
1640
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1641
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1642
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) {
1643
-			return $data['expirationDate'] === null && $data['passwordSet'] === true;
1644
-		}));
1645
-
1646
-		$share = $this->manager->newShare();
1647
-		$share->setPassword('password');
1648
-
1649
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1650
-
1651
-		$this->assertNull($share->getExpirationDate());
1652
-	}
1653
-
1654
-	public function testValidateExpirationDateNoDateDefault(): void {
1655
-		$share = $this->manager->newShare();
1656
-
1657
-		$expected = new \DateTime('now', $this->timezone);
1658
-		$expected->add(new \DateInterval('P3D'));
1659
-		$expected->setTime(0, 0);
1660
-		$expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1661
-
1662
-		$this->config->method('getAppValue')
1663
-			->willReturnMap([
1664
-				['core', 'shareapi_default_expire_date', 'no', 'yes'],
1665
-				['core', 'shareapi_expire_after_n_days', '7', '3'],
1666
-				['core', 'link_defaultExpDays', '3', '3'],
1667
-			]);
1668
-
1669
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1670
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1671
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1672
-			return $data['expirationDate'] == $expected;
1673
-		}));
1674
-
1675
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1676
-
1677
-		$this->assertEquals($expected, $share->getExpirationDate());
1678
-	}
1679
-
1680
-	public function testValidateExpirationDateDefault(): void {
1681
-		$future = new \DateTime('now', $this->timezone);
1682
-		$future->add(new \DateInterval('P5D'));
1683
-		$future->setTime(1, 2, 3);
1684
-
1685
-		$expected = clone $future;
1686
-		$expected->setTime(0, 0);
1687
-		$expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1688
-
1689
-		$share = $this->manager->newShare();
1690
-		$share->setExpirationDate($future);
1611
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1612
+
1613
+        $this->assertEquals($expected, $share->getExpirationDate());
1614
+    }
1615
+
1616
+    public function testValidateExpirationDateNoDefault(): void {
1617
+        $date = new \DateTime('now', $this->timezone);
1618
+        $date->add(new \DateInterval('P5D'));
1619
+        $date->setTime(1, 2, 3);
1620
+
1621
+        $expected = clone $date;
1622
+        $expected->setTime(0, 0);
1623
+        $expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1624
+
1625
+        $share = $this->manager->newShare();
1626
+        $share->setExpirationDate($date);
1627
+
1628
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1629
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1630
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1631
+            return $data['expirationDate'] == $expected && $data['passwordSet'] === false;
1632
+        }));
1633
+
1634
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1635
+
1636
+        $this->assertEquals($expected, $share->getExpirationDate());
1637
+    }
1638
+
1639
+    public function testValidateExpirationDateNoDateNoDefault(): void {
1640
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1641
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1642
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) {
1643
+            return $data['expirationDate'] === null && $data['passwordSet'] === true;
1644
+        }));
1645
+
1646
+        $share = $this->manager->newShare();
1647
+        $share->setPassword('password');
1648
+
1649
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1650
+
1651
+        $this->assertNull($share->getExpirationDate());
1652
+    }
1653
+
1654
+    public function testValidateExpirationDateNoDateDefault(): void {
1655
+        $share = $this->manager->newShare();
1656
+
1657
+        $expected = new \DateTime('now', $this->timezone);
1658
+        $expected->add(new \DateInterval('P3D'));
1659
+        $expected->setTime(0, 0);
1660
+        $expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1661
+
1662
+        $this->config->method('getAppValue')
1663
+            ->willReturnMap([
1664
+                ['core', 'shareapi_default_expire_date', 'no', 'yes'],
1665
+                ['core', 'shareapi_expire_after_n_days', '7', '3'],
1666
+                ['core', 'link_defaultExpDays', '3', '3'],
1667
+            ]);
1668
+
1669
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1670
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1671
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1672
+            return $data['expirationDate'] == $expected;
1673
+        }));
1674
+
1675
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1676
+
1677
+        $this->assertEquals($expected, $share->getExpirationDate());
1678
+    }
1679
+
1680
+    public function testValidateExpirationDateDefault(): void {
1681
+        $future = new \DateTime('now', $this->timezone);
1682
+        $future->add(new \DateInterval('P5D'));
1683
+        $future->setTime(1, 2, 3);
1684
+
1685
+        $expected = clone $future;
1686
+        $expected->setTime(0, 0);
1687
+        $expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1688
+
1689
+        $share = $this->manager->newShare();
1690
+        $share->setExpirationDate($future);
1691 1691
 
1692
-		$this->config->method('getAppValue')
1693
-			->willReturnMap([
1694
-				['core', 'shareapi_default_expire_date', 'no', 'yes'],
1695
-				['core', 'shareapi_expire_after_n_days', '7', '3'],
1696
-				['core', 'link_defaultExpDays', '3', '1'],
1697
-			]);
1698
-
1699
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1700
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1701
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1702
-			return $data['expirationDate'] == $expected;
1703
-		}));
1692
+        $this->config->method('getAppValue')
1693
+            ->willReturnMap([
1694
+                ['core', 'shareapi_default_expire_date', 'no', 'yes'],
1695
+                ['core', 'shareapi_expire_after_n_days', '7', '3'],
1696
+                ['core', 'link_defaultExpDays', '3', '1'],
1697
+            ]);
1698
+
1699
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1700
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1701
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1702
+            return $data['expirationDate'] == $expected;
1703
+        }));
1704 1704
 
1705
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1705
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1706 1706
 
1707
-		$this->assertEquals($expected, $share->getExpirationDate());
1708
-	}
1707
+        $this->assertEquals($expected, $share->getExpirationDate());
1708
+    }
1709 1709
 
1710
-	public function testValidateExpirationNegativeOffsetTimezone(): void {
1711
-		$this->timezone = new \DateTimeZone('Pacific/Tahiti');
1712
-		$future = new \DateTime();
1713
-		$future->add(new \DateInterval('P5D'));
1710
+    public function testValidateExpirationNegativeOffsetTimezone(): void {
1711
+        $this->timezone = new \DateTimeZone('Pacific/Tahiti');
1712
+        $future = new \DateTime();
1713
+        $future->add(new \DateInterval('P5D'));
1714 1714
 
1715
-		$expected = clone $future;
1716
-		$expected->setTimezone($this->timezone);
1717
-		$expected->setTime(0, 0);
1718
-		$expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1715
+        $expected = clone $future;
1716
+        $expected->setTimezone($this->timezone);
1717
+        $expected->setTime(0, 0);
1718
+        $expected->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1719 1719
 
1720
-		$share = $this->manager->newShare();
1721
-		$share->setExpirationDate($future);
1722
-
1723
-		$this->config->method('getAppValue')
1724
-			->willReturnMap([
1725
-				['core', 'shareapi_default_expire_date', 'no', 'yes'],
1726
-				['core', 'shareapi_expire_after_n_days', '7', '3'],
1727
-				['core', 'link_defaultExpDays', '3', '1'],
1728
-			]);
1729
-
1730
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1731
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1732
-		$hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1733
-			return $data['expirationDate'] == $expected;
1734
-		}));
1735
-
1736
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1737
-
1738
-		$this->assertEquals($expected, $share->getExpirationDate());
1739
-	}
1740
-
1741
-	public function testValidateExpirationDateHookModification(): void {
1742
-		$nextWeek = new \DateTime('now', $this->timezone);
1743
-		$nextWeek->add(new \DateInterval('P7D'));
1720
+        $share = $this->manager->newShare();
1721
+        $share->setExpirationDate($future);
1722
+
1723
+        $this->config->method('getAppValue')
1724
+            ->willReturnMap([
1725
+                ['core', 'shareapi_default_expire_date', 'no', 'yes'],
1726
+                ['core', 'shareapi_expire_after_n_days', '7', '3'],
1727
+                ['core', 'link_defaultExpDays', '3', '1'],
1728
+            ]);
1729
+
1730
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1731
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1732
+        $hookListener->expects($this->once())->method('listener')->with($this->callback(function ($data) use ($expected) {
1733
+            return $data['expirationDate'] == $expected;
1734
+        }));
1735
+
1736
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1737
+
1738
+        $this->assertEquals($expected, $share->getExpirationDate());
1739
+    }
1740
+
1741
+    public function testValidateExpirationDateHookModification(): void {
1742
+        $nextWeek = new \DateTime('now', $this->timezone);
1743
+        $nextWeek->add(new \DateInterval('P7D'));
1744 1744
 
1745
-		$save = clone $nextWeek;
1746
-		$save->setTime(0, 0);
1747
-		$save->sub(new \DateInterval('P2D'));
1748
-		$save->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1749
-
1750
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1751
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1752
-		$hookListener->expects($this->once())->method('listener')->willReturnCallback(function ($data) {
1753
-			$data['expirationDate']->sub(new \DateInterval('P2D'));
1754
-		});
1755
-
1756
-		$share = $this->manager->newShare();
1757
-		$share->setExpirationDate($nextWeek);
1758
-
1759
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1760
-
1761
-		$this->assertEquals($save, $share->getExpirationDate());
1762
-	}
1763
-
1764
-	public function testValidateExpirationDateHookException(): void {
1765
-		$this->expectException(\Exception::class);
1766
-		$this->expectExceptionMessage('Invalid date!');
1767
-
1768
-		$nextWeek = new \DateTime();
1769
-		$nextWeek->add(new \DateInterval('P7D'));
1770
-		$nextWeek->setTime(0, 0, 0);
1771
-
1772
-		$share = $this->manager->newShare();
1773
-		$share->setExpirationDate($nextWeek);
1774
-
1775
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1776
-		\OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1777
-		$hookListener->expects($this->once())->method('listener')->willReturnCallback(function ($data) {
1778
-			$data['accepted'] = false;
1779
-			$data['message'] = 'Invalid date!';
1780
-		});
1781
-
1782
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1783
-	}
1784
-
1785
-	public function testValidateExpirationDateExistingShareNoDefault(): void {
1786
-		$share = $this->manager->newShare();
1787
-
1788
-		$share->setId('42')->setProviderId('foo');
1789
-
1790
-		$this->config->method('getAppValue')
1791
-			->willReturnMap([
1792
-				['core', 'shareapi_default_expire_date', 'no', 'yes'],
1793
-				['core', 'shareapi_expire_after_n_days', '7', '6'],
1794
-			]);
1795
-
1796
-		self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1797
-
1798
-		$this->assertEquals(null, $share->getExpirationDate());
1799
-	}
1800
-
1801
-	public function testUserCreateChecksShareWithGroupMembersOnlyDifferentGroups(): void {
1802
-		$this->expectException(\Exception::class);
1803
-		$this->expectExceptionMessage('Sharing is only allowed with group members');
1745
+        $save = clone $nextWeek;
1746
+        $save->setTime(0, 0);
1747
+        $save->sub(new \DateInterval('P2D'));
1748
+        $save->setTimezone(new \DateTimeZone(date_default_timezone_get()));
1749
+
1750
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1751
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1752
+        $hookListener->expects($this->once())->method('listener')->willReturnCallback(function ($data) {
1753
+            $data['expirationDate']->sub(new \DateInterval('P2D'));
1754
+        });
1755
+
1756
+        $share = $this->manager->newShare();
1757
+        $share->setExpirationDate($nextWeek);
1758
+
1759
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1760
+
1761
+        $this->assertEquals($save, $share->getExpirationDate());
1762
+    }
1763
+
1764
+    public function testValidateExpirationDateHookException(): void {
1765
+        $this->expectException(\Exception::class);
1766
+        $this->expectExceptionMessage('Invalid date!');
1767
+
1768
+        $nextWeek = new \DateTime();
1769
+        $nextWeek->add(new \DateInterval('P7D'));
1770
+        $nextWeek->setTime(0, 0, 0);
1771
+
1772
+        $share = $this->manager->newShare();
1773
+        $share->setExpirationDate($nextWeek);
1774
+
1775
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['listener'])->getMock();
1776
+        \OCP\Util::connectHook('\OC\Share', 'verifyExpirationDate', $hookListener, 'listener');
1777
+        $hookListener->expects($this->once())->method('listener')->willReturnCallback(function ($data) {
1778
+            $data['accepted'] = false;
1779
+            $data['message'] = 'Invalid date!';
1780
+        });
1781
+
1782
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1783
+    }
1784
+
1785
+    public function testValidateExpirationDateExistingShareNoDefault(): void {
1786
+        $share = $this->manager->newShare();
1787
+
1788
+        $share->setId('42')->setProviderId('foo');
1789
+
1790
+        $this->config->method('getAppValue')
1791
+            ->willReturnMap([
1792
+                ['core', 'shareapi_default_expire_date', 'no', 'yes'],
1793
+                ['core', 'shareapi_expire_after_n_days', '7', '6'],
1794
+            ]);
1795
+
1796
+        self::invokePrivate($this->manager, 'validateExpirationDateLink', [$share]);
1797
+
1798
+        $this->assertEquals(null, $share->getExpirationDate());
1799
+    }
1800
+
1801
+    public function testUserCreateChecksShareWithGroupMembersOnlyDifferentGroups(): void {
1802
+        $this->expectException(\Exception::class);
1803
+        $this->expectExceptionMessage('Sharing is only allowed with group members');
1804 1804
 
1805
-		$share = $this->manager->newShare();
1805
+        $share = $this->manager->newShare();
1806 1806
 
1807
-		$sharedBy = $this->createMock(IUser::class);
1808
-		$sharedWith = $this->createMock(IUser::class);
1809
-		$share->setSharedBy('sharedBy')->setSharedWith('sharedWith');
1807
+        $sharedBy = $this->createMock(IUser::class);
1808
+        $sharedWith = $this->createMock(IUser::class);
1809
+        $share->setSharedBy('sharedBy')->setSharedWith('sharedWith');
1810 1810
 
1811
-		$this->groupManager
1812
-			->method('getUserGroupIds')
1813
-			->willReturnMap(
1814
-				[
1815
-					[$sharedBy, ['group1']],
1816
-					[$sharedWith, ['group2']],
1817
-				]
1818
-			);
1811
+        $this->groupManager
1812
+            ->method('getUserGroupIds')
1813
+            ->willReturnMap(
1814
+                [
1815
+                    [$sharedBy, ['group1']],
1816
+                    [$sharedWith, ['group2']],
1817
+                ]
1818
+            );
1819 1819
 
1820
-		$this->userManager->method('get')->willReturnMap([
1821
-			['sharedBy', $sharedBy],
1822
-			['sharedWith', $sharedWith],
1823
-		]);
1820
+        $this->userManager->method('get')->willReturnMap([
1821
+            ['sharedBy', $sharedBy],
1822
+            ['sharedWith', $sharedWith],
1823
+        ]);
1824 1824
 
1825
-		$this->config
1826
-			->method('getAppValue')
1827
-			->willReturnMap([
1828
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
1829
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
1830
-			]);
1825
+        $this->config
1826
+            ->method('getAppValue')
1827
+            ->willReturnMap([
1828
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
1829
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
1830
+            ]);
1831 1831
 
1832
-		self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
1833
-	}
1832
+        self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
1833
+    }
1834 1834
 
1835
-	public function testUserCreateChecksShareWithGroupMembersOnlySharedGroup(): void {
1836
-		$share = $this->manager->newShare();
1835
+    public function testUserCreateChecksShareWithGroupMembersOnlySharedGroup(): void {
1836
+        $share = $this->manager->newShare();
1837 1837
 
1838
-		$sharedBy = $this->createMock(IUser::class);
1839
-		$sharedWith = $this->createMock(IUser::class);
1840
-		$share->setSharedBy('sharedBy')->setSharedWith('sharedWith');
1838
+        $sharedBy = $this->createMock(IUser::class);
1839
+        $sharedWith = $this->createMock(IUser::class);
1840
+        $share->setSharedBy('sharedBy')->setSharedWith('sharedWith');
1841 1841
 
1842
-		$path = $this->createMock(Node::class);
1843
-		$share->setNode($path);
1842
+        $path = $this->createMock(Node::class);
1843
+        $share->setNode($path);
1844 1844
 
1845
-		$this->groupManager
1846
-			->method('getUserGroupIds')
1847
-			->willReturnMap(
1848
-				[
1849
-					[$sharedBy, ['group1', 'group3']],
1850
-					[$sharedWith, ['group2', 'group3']],
1851
-				]
1852
-			);
1845
+        $this->groupManager
1846
+            ->method('getUserGroupIds')
1847
+            ->willReturnMap(
1848
+                [
1849
+                    [$sharedBy, ['group1', 'group3']],
1850
+                    [$sharedWith, ['group2', 'group3']],
1851
+                ]
1852
+            );
1853 1853
 
1854
-		$this->userManager->method('get')->willReturnMap([
1855
-			['sharedBy', $sharedBy],
1856
-			['sharedWith', $sharedWith],
1857
-		]);
1854
+        $this->userManager->method('get')->willReturnMap([
1855
+            ['sharedBy', $sharedBy],
1856
+            ['sharedWith', $sharedWith],
1857
+        ]);
1858 1858
 
1859
-		$this->config
1860
-			->method('getAppValue')
1861
-			->willReturnMap([
1862
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
1863
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
1864
-			]);
1859
+        $this->config
1860
+            ->method('getAppValue')
1861
+            ->willReturnMap([
1862
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
1863
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
1864
+            ]);
1865 1865
 
1866
-		$this->defaultProvider
1867
-			->method('getSharesByPath')
1868
-			->with($path)
1869
-			->willReturn([]);
1866
+        $this->defaultProvider
1867
+            ->method('getSharesByPath')
1868
+            ->with($path)
1869
+            ->willReturn([]);
1870 1870
 
1871
-		self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
1872
-		$this->addToAssertionCount(1);
1873
-	}
1871
+        self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
1872
+        $this->addToAssertionCount(1);
1873
+    }
1874 1874
 
1875 1875
 
1876
-	public function testUserCreateChecksIdenticalShareExists(): void {
1877
-		$this->expectException(AlreadySharedException::class);
1878
-		$this->expectExceptionMessage('Sharing name.txt failed, because this item is already shared with the account user');
1876
+    public function testUserCreateChecksIdenticalShareExists(): void {
1877
+        $this->expectException(AlreadySharedException::class);
1878
+        $this->expectExceptionMessage('Sharing name.txt failed, because this item is already shared with the account user');
1879 1879
 
1880
-		$share = $this->manager->newShare();
1881
-		$share->setSharedWithDisplayName('user');
1882
-		$share2 = $this->manager->newShare();
1880
+        $share = $this->manager->newShare();
1881
+        $share->setSharedWithDisplayName('user');
1882
+        $share2 = $this->manager->newShare();
1883 1883
 
1884
-		$sharedWith = $this->createMock(IUser::class);
1885
-		$path = $this->createMock(Node::class);
1884
+        $sharedWith = $this->createMock(IUser::class);
1885
+        $path = $this->createMock(Node::class);
1886 1886
 
1887
-		$share->setSharedWith('sharedWith')->setNode($path)
1888
-			->setProviderId('foo')->setId('bar');
1887
+        $share->setSharedWith('sharedWith')->setNode($path)
1888
+            ->setProviderId('foo')->setId('bar');
1889 1889
 
1890
-		$share2->setSharedWith('sharedWith')->setNode($path)
1891
-			->setProviderId('foo')->setId('baz');
1890
+        $share2->setSharedWith('sharedWith')->setNode($path)
1891
+            ->setProviderId('foo')->setId('baz');
1892 1892
 
1893
-		$this->defaultProvider
1894
-			->method('getSharesByPath')
1895
-			->with($path)
1896
-			->willReturn([$share2]);
1893
+        $this->defaultProvider
1894
+            ->method('getSharesByPath')
1895
+            ->with($path)
1896
+            ->willReturn([$share2]);
1897 1897
 
1898
-		$path->method('getName')
1899
-			->willReturn('name.txt');
1898
+        $path->method('getName')
1899
+            ->willReturn('name.txt');
1900 1900
 
1901
-		self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
1902
-	}
1903
-
1904
-
1905
-	public function testUserCreateChecksIdenticalPathSharedViaGroup(): void {
1906
-		$this->expectException(AlreadySharedException::class);
1907
-		$this->expectExceptionMessage('Sharing name2.txt failed, because this item is already shared with the account userName');
1908
-
1909
-		$share = $this->manager->newShare();
1901
+        self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
1902
+    }
1903
+
1904
+
1905
+    public function testUserCreateChecksIdenticalPathSharedViaGroup(): void {
1906
+        $this->expectException(AlreadySharedException::class);
1907
+        $this->expectExceptionMessage('Sharing name2.txt failed, because this item is already shared with the account userName');
1908
+
1909
+        $share = $this->manager->newShare();
1910 1910
 
1911
-		$sharedWith = $this->createMock(IUser::class);
1912
-		$sharedWith->method('getUID')->willReturn('sharedWith');
1913
-
1914
-		$this->userManager->method('get')->with('sharedWith')->willReturn($sharedWith);
1911
+        $sharedWith = $this->createMock(IUser::class);
1912
+        $sharedWith->method('getUID')->willReturn('sharedWith');
1913
+
1914
+        $this->userManager->method('get')->with('sharedWith')->willReturn($sharedWith);
1915 1915
 
1916
-		$path = $this->createMock(Node::class);
1916
+        $path = $this->createMock(Node::class);
1917 1917
 
1918
-		$share->setSharedWith('sharedWith')
1919
-			->setNode($path)
1920
-			->setShareOwner('shareOwner')
1921
-			->setSharedWithDisplayName('userName')
1922
-			->setProviderId('foo')
1923
-			->setId('bar');
1918
+        $share->setSharedWith('sharedWith')
1919
+            ->setNode($path)
1920
+            ->setShareOwner('shareOwner')
1921
+            ->setSharedWithDisplayName('userName')
1922
+            ->setProviderId('foo')
1923
+            ->setId('bar');
1924 1924
 
1925
-		$share2 = $this->manager->newShare();
1926
-		$share2->setShareType(IShare::TYPE_GROUP)
1927
-			->setShareOwner('shareOwner2')
1928
-			->setProviderId('foo')
1929
-			->setId('baz')
1930
-			->setSharedWith('group');
1925
+        $share2 = $this->manager->newShare();
1926
+        $share2->setShareType(IShare::TYPE_GROUP)
1927
+            ->setShareOwner('shareOwner2')
1928
+            ->setProviderId('foo')
1929
+            ->setId('baz')
1930
+            ->setSharedWith('group');
1931 1931
 
1932
-		$group = $this->createMock(IGroup::class);
1933
-		$group->method('inGroup')
1934
-			->with($sharedWith)
1935
-			->willReturn(true);
1932
+        $group = $this->createMock(IGroup::class);
1933
+        $group->method('inGroup')
1934
+            ->with($sharedWith)
1935
+            ->willReturn(true);
1936 1936
 
1937
-		$this->groupManager->method('get')->with('group')->willReturn($group);
1937
+        $this->groupManager->method('get')->with('group')->willReturn($group);
1938 1938
 
1939
-		$this->defaultProvider
1940
-			->method('getSharesByPath')
1941
-			->with($path)
1942
-			->willReturn([$share2]);
1939
+        $this->defaultProvider
1940
+            ->method('getSharesByPath')
1941
+            ->with($path)
1942
+            ->willReturn([$share2]);
1943 1943
 
1944
-		$path->method('getName')
1945
-			->willReturn('name2.txt');
1944
+        $path->method('getName')
1945
+            ->willReturn('name2.txt');
1946 1946
 
1947
-		self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
1948
-	}
1947
+        self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
1948
+    }
1949 1949
 
1950
-	public function testUserCreateChecksIdenticalPathSharedViaDeletedGroup(): void {
1951
-		$share = $this->manager->newShare();
1950
+    public function testUserCreateChecksIdenticalPathSharedViaDeletedGroup(): void {
1951
+        $share = $this->manager->newShare();
1952 1952
 
1953
-		$sharedWith = $this->createMock(IUser::class);
1954
-		$sharedWith->method('getUID')->willReturn('sharedWith');
1953
+        $sharedWith = $this->createMock(IUser::class);
1954
+        $sharedWith->method('getUID')->willReturn('sharedWith');
1955 1955
 
1956
-		$this->userManager->method('get')->with('sharedWith')->willReturn($sharedWith);
1956
+        $this->userManager->method('get')->with('sharedWith')->willReturn($sharedWith);
1957 1957
 
1958
-		$path = $this->createMock(Node::class);
1958
+        $path = $this->createMock(Node::class);
1959 1959
 
1960
-		$share->setSharedWith('sharedWith')
1961
-			->setNode($path)
1962
-			->setShareOwner('shareOwner')
1963
-			->setProviderId('foo')
1964
-			->setId('bar');
1960
+        $share->setSharedWith('sharedWith')
1961
+            ->setNode($path)
1962
+            ->setShareOwner('shareOwner')
1963
+            ->setProviderId('foo')
1964
+            ->setId('bar');
1965 1965
 
1966
-		$share2 = $this->manager->newShare();
1967
-		$share2->setShareType(IShare::TYPE_GROUP)
1968
-			->setShareOwner('shareOwner2')
1969
-			->setProviderId('foo')
1970
-			->setId('baz')
1971
-			->setSharedWith('group');
1966
+        $share2 = $this->manager->newShare();
1967
+        $share2->setShareType(IShare::TYPE_GROUP)
1968
+            ->setShareOwner('shareOwner2')
1969
+            ->setProviderId('foo')
1970
+            ->setId('baz')
1971
+            ->setSharedWith('group');
1972 1972
 
1973
-		$this->groupManager->method('get')->with('group')->willReturn(null);
1973
+        $this->groupManager->method('get')->with('group')->willReturn(null);
1974 1974
 
1975
-		$this->defaultProvider
1976
-			->method('getSharesByPath')
1977
-			->with($path)
1978
-			->willReturn([$share2]);
1975
+        $this->defaultProvider
1976
+            ->method('getSharesByPath')
1977
+            ->with($path)
1978
+            ->willReturn([$share2]);
1979 1979
 
1980
-		$this->assertNull($this->invokePrivate($this->manager, 'userCreateChecks', [$share]));
1981
-	}
1980
+        $this->assertNull($this->invokePrivate($this->manager, 'userCreateChecks', [$share]));
1981
+    }
1982 1982
 
1983
-	public function testUserCreateChecksIdenticalPathNotSharedWithUser(): void {
1984
-		$share = $this->manager->newShare();
1985
-		$sharedWith = $this->createMock(IUser::class);
1986
-		$path = $this->createMock(Node::class);
1987
-		$share->setSharedWith('sharedWith')
1988
-			->setNode($path)
1989
-			->setShareOwner('shareOwner')
1990
-			->setProviderId('foo')
1991
-			->setId('bar');
1983
+    public function testUserCreateChecksIdenticalPathNotSharedWithUser(): void {
1984
+        $share = $this->manager->newShare();
1985
+        $sharedWith = $this->createMock(IUser::class);
1986
+        $path = $this->createMock(Node::class);
1987
+        $share->setSharedWith('sharedWith')
1988
+            ->setNode($path)
1989
+            ->setShareOwner('shareOwner')
1990
+            ->setProviderId('foo')
1991
+            ->setId('bar');
1992 1992
 
1993
-		$this->userManager->method('get')->with('sharedWith')->willReturn($sharedWith);
1993
+        $this->userManager->method('get')->with('sharedWith')->willReturn($sharedWith);
1994 1994
 
1995
-		$share2 = $this->manager->newShare();
1996
-		$share2->setShareType(IShare::TYPE_GROUP)
1997
-			->setShareOwner('shareOwner2')
1998
-			->setProviderId('foo')
1999
-			->setId('baz');
1995
+        $share2 = $this->manager->newShare();
1996
+        $share2->setShareType(IShare::TYPE_GROUP)
1997
+            ->setShareOwner('shareOwner2')
1998
+            ->setProviderId('foo')
1999
+            ->setId('baz');
2000 2000
 
2001
-		$group = $this->createMock(IGroup::class);
2002
-		$group->method('inGroup')
2003
-			->with($sharedWith)
2004
-			->willReturn(false);
2001
+        $group = $this->createMock(IGroup::class);
2002
+        $group->method('inGroup')
2003
+            ->with($sharedWith)
2004
+            ->willReturn(false);
2005 2005
 
2006
-		$this->groupManager->method('get')->with('group')->willReturn($group);
2006
+        $this->groupManager->method('get')->with('group')->willReturn($group);
2007 2007
 
2008
-		$share2->setSharedWith('group');
2008
+        $share2->setSharedWith('group');
2009 2009
 
2010
-		$this->defaultProvider
2011
-			->method('getSharesByPath')
2012
-			->with($path)
2013
-			->willReturn([$share2]);
2010
+        $this->defaultProvider
2011
+            ->method('getSharesByPath')
2012
+            ->with($path)
2013
+            ->willReturn([$share2]);
2014 2014
 
2015
-		self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
2016
-		$this->addToAssertionCount(1);
2017
-	}
2015
+        self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
2016
+        $this->addToAssertionCount(1);
2017
+    }
2018 2018
 
2019 2019
 
2020
-	public function testGroupCreateChecksShareWithGroupMembersGroupSharingNotAllowed(): void {
2021
-		$this->expectException(\Exception::class);
2022
-		$this->expectExceptionMessage('Group sharing is now allowed');
2020
+    public function testGroupCreateChecksShareWithGroupMembersGroupSharingNotAllowed(): void {
2021
+        $this->expectException(\Exception::class);
2022
+        $this->expectExceptionMessage('Group sharing is now allowed');
2023 2023
 
2024
-		$share = $this->manager->newShare();
2024
+        $share = $this->manager->newShare();
2025 2025
 
2026
-		$this->config
2027
-			->method('getAppValue')
2028
-			->willReturnMap([
2029
-				['core', 'shareapi_allow_group_sharing', 'yes', 'no'],
2030
-			]);
2026
+        $this->config
2027
+            ->method('getAppValue')
2028
+            ->willReturnMap([
2029
+                ['core', 'shareapi_allow_group_sharing', 'yes', 'no'],
2030
+            ]);
2031 2031
 
2032
-		self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2033
-	}
2032
+        self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2033
+    }
2034 2034
 
2035 2035
 
2036
-	public function testGroupCreateChecksShareWithGroupMembersOnlyNotInGroup(): void {
2037
-		$this->expectException(\Exception::class);
2038
-		$this->expectExceptionMessage('Sharing is only allowed within your own groups');
2036
+    public function testGroupCreateChecksShareWithGroupMembersOnlyNotInGroup(): void {
2037
+        $this->expectException(\Exception::class);
2038
+        $this->expectExceptionMessage('Sharing is only allowed within your own groups');
2039 2039
 
2040
-		$share = $this->manager->newShare();
2040
+        $share = $this->manager->newShare();
2041 2041
 
2042
-		$user = $this->createMock(IUser::class);
2043
-		$group = $this->createMock(IGroup::class);
2044
-		$share->setSharedBy('user')->setSharedWith('group');
2042
+        $user = $this->createMock(IUser::class);
2043
+        $group = $this->createMock(IGroup::class);
2044
+        $share->setSharedBy('user')->setSharedWith('group');
2045 2045
 
2046
-		$group->method('inGroup')->with($user)->willReturn(false);
2046
+        $group->method('inGroup')->with($user)->willReturn(false);
2047 2047
 
2048
-		$this->groupManager->method('get')->with('group')->willReturn($group);
2049
-		$this->userManager->method('get')->with('user')->willReturn($user);
2048
+        $this->groupManager->method('get')->with('group')->willReturn($group);
2049
+        $this->userManager->method('get')->with('user')->willReturn($user);
2050 2050
 
2051
-		$this->config
2052
-			->method('getAppValue')
2053
-			->willReturnMap([
2054
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
2055
-				['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2056
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
2057
-			]);
2051
+        $this->config
2052
+            ->method('getAppValue')
2053
+            ->willReturnMap([
2054
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
2055
+                ['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2056
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
2057
+            ]);
2058 2058
 
2059
-		self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2060
-	}
2059
+        self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2060
+    }
2061 2061
 
2062 2062
 
2063
-	public function testGroupCreateChecksShareWithGroupMembersOnlyNullGroup(): void {
2064
-		$this->expectException(\Exception::class);
2065
-		$this->expectExceptionMessage('Sharing is only allowed within your own groups');
2063
+    public function testGroupCreateChecksShareWithGroupMembersOnlyNullGroup(): void {
2064
+        $this->expectException(\Exception::class);
2065
+        $this->expectExceptionMessage('Sharing is only allowed within your own groups');
2066 2066
 
2067
-		$share = $this->manager->newShare();
2067
+        $share = $this->manager->newShare();
2068 2068
 
2069
-		$user = $this->createMock(IUser::class);
2070
-		$share->setSharedBy('user')->setSharedWith('group');
2069
+        $user = $this->createMock(IUser::class);
2070
+        $share->setSharedBy('user')->setSharedWith('group');
2071 2071
 
2072
-		$this->groupManager->method('get')->with('group')->willReturn(null);
2073
-		$this->userManager->method('get')->with('user')->willReturn($user);
2072
+        $this->groupManager->method('get')->with('group')->willReturn(null);
2073
+        $this->userManager->method('get')->with('user')->willReturn($user);
2074 2074
 
2075
-		$this->config
2076
-			->method('getAppValue')
2077
-			->willReturnMap([
2078
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
2079
-				['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2080
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
2081
-			]);
2075
+        $this->config
2076
+            ->method('getAppValue')
2077
+            ->willReturnMap([
2078
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
2079
+                ['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2080
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
2081
+            ]);
2082 2082
 
2083
-		$this->assertNull($this->invokePrivate($this->manager, 'groupCreateChecks', [$share]));
2084
-	}
2083
+        $this->assertNull($this->invokePrivate($this->manager, 'groupCreateChecks', [$share]));
2084
+    }
2085 2085
 
2086
-	public function testGroupCreateChecksShareWithGroupMembersOnlyInGroup(): void {
2087
-		$share = $this->manager->newShare();
2086
+    public function testGroupCreateChecksShareWithGroupMembersOnlyInGroup(): void {
2087
+        $share = $this->manager->newShare();
2088 2088
 
2089
-		$user = $this->createMock(IUser::class);
2090
-		$group = $this->createMock(IGroup::class);
2091
-		$share->setSharedBy('user')->setSharedWith('group');
2089
+        $user = $this->createMock(IUser::class);
2090
+        $group = $this->createMock(IGroup::class);
2091
+        $share->setSharedBy('user')->setSharedWith('group');
2092 2092
 
2093
-		$this->userManager->method('get')->with('user')->willReturn($user);
2094
-		$this->groupManager->method('get')->with('group')->willReturn($group);
2093
+        $this->userManager->method('get')->with('user')->willReturn($user);
2094
+        $this->groupManager->method('get')->with('group')->willReturn($group);
2095 2095
 
2096
-		$group->method('inGroup')->with($user)->willReturn(true);
2096
+        $group->method('inGroup')->with($user)->willReturn(true);
2097 2097
 
2098
-		$path = $this->createMock(Node::class);
2099
-		$share->setNode($path);
2098
+        $path = $this->createMock(Node::class);
2099
+        $share->setNode($path);
2100 2100
 
2101
-		$this->defaultProvider->method('getSharesByPath')
2102
-			->with($path)
2103
-			->willReturn([]);
2101
+        $this->defaultProvider->method('getSharesByPath')
2102
+            ->with($path)
2103
+            ->willReturn([]);
2104 2104
 
2105
-		$this->config
2106
-			->method('getAppValue')
2107
-			->willReturnMap([
2108
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
2109
-				['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2110
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
2111
-			]);
2105
+        $this->config
2106
+            ->method('getAppValue')
2107
+            ->willReturnMap([
2108
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
2109
+                ['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2110
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
2111
+            ]);
2112 2112
 
2113
-		self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2114
-		$this->addToAssertionCount(1);
2115
-	}
2113
+        self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2114
+        $this->addToAssertionCount(1);
2115
+    }
2116 2116
 
2117 2117
 
2118
-	public function testGroupCreateChecksPathAlreadySharedWithSameGroup(): void {
2119
-		$this->expectException(\Exception::class);
2120
-		$this->expectExceptionMessage('Path is already shared with this group');
2118
+    public function testGroupCreateChecksPathAlreadySharedWithSameGroup(): void {
2119
+        $this->expectException(\Exception::class);
2120
+        $this->expectExceptionMessage('Path is already shared with this group');
2121 2121
 
2122
-		$share = $this->manager->newShare();
2122
+        $share = $this->manager->newShare();
2123 2123
 
2124
-		$path = $this->createMock(Node::class);
2125
-		$share->setSharedWith('sharedWith')
2126
-			->setNode($path)
2127
-			->setProviderId('foo')
2128
-			->setId('bar');
2124
+        $path = $this->createMock(Node::class);
2125
+        $share->setSharedWith('sharedWith')
2126
+            ->setNode($path)
2127
+            ->setProviderId('foo')
2128
+            ->setId('bar');
2129 2129
 
2130
-		$share2 = $this->manager->newShare();
2131
-		$share2->setSharedWith('sharedWith')
2132
-			->setProviderId('foo')
2133
-			->setId('baz');
2130
+        $share2 = $this->manager->newShare();
2131
+        $share2->setSharedWith('sharedWith')
2132
+            ->setProviderId('foo')
2133
+            ->setId('baz');
2134 2134
 
2135
-		$this->defaultProvider->method('getSharesByPath')
2136
-			->with($path)
2137
-			->willReturn([$share2]);
2135
+        $this->defaultProvider->method('getSharesByPath')
2136
+            ->with($path)
2137
+            ->willReturn([$share2]);
2138 2138
 
2139
-		$this->config
2140
-			->method('getAppValue')
2141
-			->willReturnMap([
2142
-				['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2143
-			]);
2139
+        $this->config
2140
+            ->method('getAppValue')
2141
+            ->willReturnMap([
2142
+                ['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2143
+            ]);
2144 2144
 
2145
-		self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2146
-	}
2145
+        self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2146
+    }
2147 2147
 
2148
-	public function testGroupCreateChecksPathAlreadySharedWithDifferentGroup(): void {
2149
-		$share = $this->manager->newShare();
2148
+    public function testGroupCreateChecksPathAlreadySharedWithDifferentGroup(): void {
2149
+        $share = $this->manager->newShare();
2150 2150
 
2151
-		$share->setSharedWith('sharedWith');
2151
+        $share->setSharedWith('sharedWith');
2152 2152
 
2153
-		$path = $this->createMock(Node::class);
2154
-		$share->setNode($path);
2153
+        $path = $this->createMock(Node::class);
2154
+        $share->setNode($path);
2155 2155
 
2156
-		$share2 = $this->manager->newShare();
2157
-		$share2->setSharedWith('sharedWith2');
2156
+        $share2 = $this->manager->newShare();
2157
+        $share2->setSharedWith('sharedWith2');
2158 2158
 
2159
-		$this->defaultProvider->method('getSharesByPath')
2160
-			->with($path)
2161
-			->willReturn([$share2]);
2159
+        $this->defaultProvider->method('getSharesByPath')
2160
+            ->with($path)
2161
+            ->willReturn([$share2]);
2162 2162
 
2163
-		$this->config
2164
-			->method('getAppValue')
2165
-			->willReturnMap([
2166
-				['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2167
-			]);
2163
+        $this->config
2164
+            ->method('getAppValue')
2165
+            ->willReturnMap([
2166
+                ['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
2167
+            ]);
2168 2168
 
2169
-		self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2170
-		$this->addToAssertionCount(1);
2171
-	}
2169
+        self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
2170
+        $this->addToAssertionCount(1);
2171
+    }
2172 2172
 
2173 2173
 
2174
-	public function testLinkCreateChecksNoLinkSharesAllowed(): void {
2175
-		$this->expectException(\Exception::class);
2176
-		$this->expectExceptionMessage('Link sharing is not allowed');
2174
+    public function testLinkCreateChecksNoLinkSharesAllowed(): void {
2175
+        $this->expectException(\Exception::class);
2176
+        $this->expectExceptionMessage('Link sharing is not allowed');
2177 2177
 
2178
-		$share = $this->manager->newShare();
2178
+        $share = $this->manager->newShare();
2179 2179
 
2180
-		$this->config
2181
-			->method('getAppValue')
2182
-			->willReturnMap([
2183
-				['core', 'shareapi_allow_links', 'yes', 'no'],
2184
-			]);
2180
+        $this->config
2181
+            ->method('getAppValue')
2182
+            ->willReturnMap([
2183
+                ['core', 'shareapi_allow_links', 'yes', 'no'],
2184
+            ]);
2185 2185
 
2186
-		self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2187
-	}
2186
+        self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2187
+    }
2188 2188
 
2189 2189
 
2190
-	public function testFileLinkCreateChecksNoPublicUpload(): void {
2191
-		$share = $this->manager->newShare();
2190
+    public function testFileLinkCreateChecksNoPublicUpload(): void {
2191
+        $share = $this->manager->newShare();
2192 2192
 
2193
-		$share->setPermissions(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE);
2194
-		$share->setNodeType('file');
2193
+        $share->setPermissions(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE);
2194
+        $share->setNodeType('file');
2195 2195
 
2196
-		$this->config
2197
-			->method('getAppValue')
2198
-			->willReturnMap([
2199
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
2200
-				['core', 'shareapi_allow_public_upload', 'yes', 'no']
2201
-			]);
2196
+        $this->config
2197
+            ->method('getAppValue')
2198
+            ->willReturnMap([
2199
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
2200
+                ['core', 'shareapi_allow_public_upload', 'yes', 'no']
2201
+            ]);
2202 2202
 
2203
-		self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2204
-		$this->addToAssertionCount(1);
2205
-	}
2203
+        self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2204
+        $this->addToAssertionCount(1);
2205
+    }
2206 2206
 
2207
-	public function testFolderLinkCreateChecksNoPublicUpload(): void {
2208
-		$this->expectException(\Exception::class);
2209
-		$this->expectExceptionMessage('Public upload is not allowed');
2207
+    public function testFolderLinkCreateChecksNoPublicUpload(): void {
2208
+        $this->expectException(\Exception::class);
2209
+        $this->expectExceptionMessage('Public upload is not allowed');
2210 2210
 
2211
-		$share = $this->manager->newShare();
2211
+        $share = $this->manager->newShare();
2212 2212
 
2213
-		$share->setPermissions(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE);
2214
-		$share->setNodeType('folder');
2213
+        $share->setPermissions(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE);
2214
+        $share->setNodeType('folder');
2215 2215
 
2216
-		$this->config
2217
-			->method('getAppValue')
2218
-			->willReturnMap([
2219
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
2220
-				['core', 'shareapi_allow_public_upload', 'yes', 'no']
2221
-			]);
2216
+        $this->config
2217
+            ->method('getAppValue')
2218
+            ->willReturnMap([
2219
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
2220
+                ['core', 'shareapi_allow_public_upload', 'yes', 'no']
2221
+            ]);
2222 2222
 
2223
-		self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2224
-	}
2223
+        self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2224
+    }
2225 2225
 
2226
-	public function testLinkCreateChecksPublicUpload(): void {
2227
-		$share = $this->manager->newShare();
2226
+    public function testLinkCreateChecksPublicUpload(): void {
2227
+        $share = $this->manager->newShare();
2228 2228
 
2229
-		$share->setPermissions(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE);
2230
-		$share->setSharedWith('sharedWith');
2231
-		$folder = $this->createMock(\OC\Files\Node\Folder::class);
2232
-		$share->setNode($folder);
2229
+        $share->setPermissions(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE);
2230
+        $share->setSharedWith('sharedWith');
2231
+        $folder = $this->createMock(\OC\Files\Node\Folder::class);
2232
+        $share->setNode($folder);
2233 2233
 
2234
-		$this->config
2235
-			->method('getAppValue')
2236
-			->willReturnMap([
2237
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
2238
-				['core', 'shareapi_allow_public_upload', 'yes', 'yes']
2239
-			]);
2234
+        $this->config
2235
+            ->method('getAppValue')
2236
+            ->willReturnMap([
2237
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
2238
+                ['core', 'shareapi_allow_public_upload', 'yes', 'yes']
2239
+            ]);
2240 2240
 
2241
-		self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2242
-		$this->addToAssertionCount(1);
2243
-	}
2241
+        self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2242
+        $this->addToAssertionCount(1);
2243
+    }
2244 2244
 
2245
-	public function testLinkCreateChecksReadOnly(): void {
2246
-		$share = $this->manager->newShare();
2245
+    public function testLinkCreateChecksReadOnly(): void {
2246
+        $share = $this->manager->newShare();
2247 2247
 
2248
-		$share->setPermissions(\OCP\Constants::PERMISSION_READ);
2249
-		$share->setSharedWith('sharedWith');
2250
-		$folder = $this->createMock(\OC\Files\Node\Folder::class);
2251
-		$share->setNode($folder);
2248
+        $share->setPermissions(\OCP\Constants::PERMISSION_READ);
2249
+        $share->setSharedWith('sharedWith');
2250
+        $folder = $this->createMock(\OC\Files\Node\Folder::class);
2251
+        $share->setNode($folder);
2252 2252
 
2253
-		$this->config
2254
-			->method('getAppValue')
2255
-			->willReturnMap([
2256
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
2257
-				['core', 'shareapi_allow_public_upload', 'yes', 'no']
2258
-			]);
2253
+        $this->config
2254
+            ->method('getAppValue')
2255
+            ->willReturnMap([
2256
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
2257
+                ['core', 'shareapi_allow_public_upload', 'yes', 'no']
2258
+            ]);
2259 2259
 
2260
-		self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2261
-		$this->addToAssertionCount(1);
2262
-	}
2263
-
2264
-
2265
-	public function testPathCreateChecksContainsSharedMount(): void {
2266
-		$this->expectException(\InvalidArgumentException::class);
2267
-		$this->expectExceptionMessage('You cannot share a folder that contains other shares');
2260
+        self::invokePrivate($this->manager, 'linkCreateChecks', [$share]);
2261
+        $this->addToAssertionCount(1);
2262
+    }
2263
+
2264
+
2265
+    public function testPathCreateChecksContainsSharedMount(): void {
2266
+        $this->expectException(\InvalidArgumentException::class);
2267
+        $this->expectExceptionMessage('You cannot share a folder that contains other shares');
2268 2268
 
2269
-		$path = $this->createMock(Folder::class);
2270
-		$path->method('getPath')->willReturn('path');
2269
+        $path = $this->createMock(Folder::class);
2270
+        $path->method('getPath')->willReturn('path');
2271 2271
 
2272
-		$mount = $this->createMock(IMountPoint::class);
2273
-		$storage = $this->createMock(IStorage::class);
2274
-		$mount->method('getStorage')->willReturn($storage);
2275
-		$storage->method('instanceOfStorage')->with('\OCA\Files_Sharing\ISharedStorage')->willReturn(true);
2276
-
2277
-		$this->mountManager->method('findIn')->with('path')->willReturn([$mount]);
2278
-
2279
-		self::invokePrivate($this->manager, 'pathCreateChecks', [$path]);
2280
-	}
2281
-
2282
-	public function testPathCreateChecksContainsNoSharedMount(): void {
2283
-		$path = $this->createMock(Folder::class);
2284
-		$path->method('getPath')->willReturn('path');
2272
+        $mount = $this->createMock(IMountPoint::class);
2273
+        $storage = $this->createMock(IStorage::class);
2274
+        $mount->method('getStorage')->willReturn($storage);
2275
+        $storage->method('instanceOfStorage')->with('\OCA\Files_Sharing\ISharedStorage')->willReturn(true);
2276
+
2277
+        $this->mountManager->method('findIn')->with('path')->willReturn([$mount]);
2278
+
2279
+        self::invokePrivate($this->manager, 'pathCreateChecks', [$path]);
2280
+    }
2281
+
2282
+    public function testPathCreateChecksContainsNoSharedMount(): void {
2283
+        $path = $this->createMock(Folder::class);
2284
+        $path->method('getPath')->willReturn('path');
2285 2285
 
2286
-		$mount = $this->createMock(IMountPoint::class);
2287
-		$storage = $this->createMock(IStorage::class);
2288
-		$mount->method('getStorage')->willReturn($storage);
2289
-		$storage->method('instanceOfStorage')->with('\OCA\Files_Sharing\ISharedStorage')->willReturn(false);
2290
-
2291
-		$this->mountManager->method('findIn')->with('path')->willReturn([$mount]);
2292
-
2293
-		self::invokePrivate($this->manager, 'pathCreateChecks', [$path]);
2294
-		$this->addToAssertionCount(1);
2295
-	}
2296
-
2297
-	public function testPathCreateChecksContainsNoFolder(): void {
2298
-		$path = $this->createMock(File::class);
2299
-
2300
-		self::invokePrivate($this->manager, 'pathCreateChecks', [$path]);
2301
-		$this->addToAssertionCount(1);
2302
-	}
2303
-
2304
-	public function dataIsSharingDisabledForUser() {
2305
-		$data = [];
2306
-
2307
-		// No exclude groups
2308
-		$data[] = ['no', null, null, [], false];
2309
-
2310
-		// empty exclude / allow list, user no groups
2311
-		$data[] = ['yes', '', json_encode(['']), [], false];
2312
-		$data[] = ['allow', '', json_encode(['']), [], true];
2313
-
2314
-		// empty exclude / allow list, user groups
2315
-		$data[] = ['yes', '', json_encode(['']), ['group1', 'group2'], false];
2316
-		$data[] = ['allow', '', json_encode(['']), ['group1', 'group2'], true];
2317
-
2318
-		// Convert old list to json
2319
-		$data[] = ['yes', 'group1,group2', json_encode(['group1', 'group2']), [], false];
2320
-		$data[] = ['allow', 'group1,group2', json_encode(['group1', 'group2']), [], true];
2321
-
2322
-		// Old list partly groups in common
2323
-		$data[] = ['yes', 'group1,group2', json_encode(['group1', 'group2']), ['group1', 'group3'], false];
2324
-		$data[] = ['allow', 'group1,group2', json_encode(['group1', 'group2']), ['group1', 'group3'], false];
2325
-
2326
-		// Old list only groups in common
2327
-		$data[] = ['yes', 'group1,group2', json_encode(['group1', 'group2']), ['group1'], true];
2328
-		$data[] = ['allow', 'group1,group2', json_encode(['group1', 'group2']), ['group1'], false];
2329
-
2330
-		// New list partly in common
2331
-		$data[] = ['yes', json_encode(['group1', 'group2']), null, ['group1', 'group3'], false];
2332
-		$data[] = ['allow', json_encode(['group1', 'group2']), null, ['group1', 'group3'], false];
2333
-
2334
-		// New list only groups in common
2335
-		$data[] = ['yes', json_encode(['group1', 'group2']), null, ['group2'], true];
2336
-		$data[] = ['allow', json_encode(['group1', 'group2']), null, ['group2'], false];
2337
-
2338
-		return $data;
2339
-	}
2286
+        $mount = $this->createMock(IMountPoint::class);
2287
+        $storage = $this->createMock(IStorage::class);
2288
+        $mount->method('getStorage')->willReturn($storage);
2289
+        $storage->method('instanceOfStorage')->with('\OCA\Files_Sharing\ISharedStorage')->willReturn(false);
2290
+
2291
+        $this->mountManager->method('findIn')->with('path')->willReturn([$mount]);
2292
+
2293
+        self::invokePrivate($this->manager, 'pathCreateChecks', [$path]);
2294
+        $this->addToAssertionCount(1);
2295
+    }
2296
+
2297
+    public function testPathCreateChecksContainsNoFolder(): void {
2298
+        $path = $this->createMock(File::class);
2299
+
2300
+        self::invokePrivate($this->manager, 'pathCreateChecks', [$path]);
2301
+        $this->addToAssertionCount(1);
2302
+    }
2303
+
2304
+    public function dataIsSharingDisabledForUser() {
2305
+        $data = [];
2306
+
2307
+        // No exclude groups
2308
+        $data[] = ['no', null, null, [], false];
2309
+
2310
+        // empty exclude / allow list, user no groups
2311
+        $data[] = ['yes', '', json_encode(['']), [], false];
2312
+        $data[] = ['allow', '', json_encode(['']), [], true];
2313
+
2314
+        // empty exclude / allow list, user groups
2315
+        $data[] = ['yes', '', json_encode(['']), ['group1', 'group2'], false];
2316
+        $data[] = ['allow', '', json_encode(['']), ['group1', 'group2'], true];
2317
+
2318
+        // Convert old list to json
2319
+        $data[] = ['yes', 'group1,group2', json_encode(['group1', 'group2']), [], false];
2320
+        $data[] = ['allow', 'group1,group2', json_encode(['group1', 'group2']), [], true];
2321
+
2322
+        // Old list partly groups in common
2323
+        $data[] = ['yes', 'group1,group2', json_encode(['group1', 'group2']), ['group1', 'group3'], false];
2324
+        $data[] = ['allow', 'group1,group2', json_encode(['group1', 'group2']), ['group1', 'group3'], false];
2325
+
2326
+        // Old list only groups in common
2327
+        $data[] = ['yes', 'group1,group2', json_encode(['group1', 'group2']), ['group1'], true];
2328
+        $data[] = ['allow', 'group1,group2', json_encode(['group1', 'group2']), ['group1'], false];
2329
+
2330
+        // New list partly in common
2331
+        $data[] = ['yes', json_encode(['group1', 'group2']), null, ['group1', 'group3'], false];
2332
+        $data[] = ['allow', json_encode(['group1', 'group2']), null, ['group1', 'group3'], false];
2333
+
2334
+        // New list only groups in common
2335
+        $data[] = ['yes', json_encode(['group1', 'group2']), null, ['group2'], true];
2336
+        $data[] = ['allow', json_encode(['group1', 'group2']), null, ['group2'], false];
2337
+
2338
+        return $data;
2339
+    }
2340 2340
 
2341
-	/**
2342
-	 * @dataProvider dataIsSharingDisabledForUser
2343
-	 *
2344
-	 * @param string $excludeGroups
2345
-	 * @param string $groupList
2346
-	 * @param string $setList
2347
-	 * @param string[] $groupIds
2348
-	 * @param bool $expected
2349
-	 */
2350
-	public function testIsSharingDisabledForUser($excludeGroups, $groupList, $setList, $groupIds, $expected): void {
2351
-		$user = $this->createMock(IUser::class);
2341
+    /**
2342
+     * @dataProvider dataIsSharingDisabledForUser
2343
+     *
2344
+     * @param string $excludeGroups
2345
+     * @param string $groupList
2346
+     * @param string $setList
2347
+     * @param string[] $groupIds
2348
+     * @param bool $expected
2349
+     */
2350
+    public function testIsSharingDisabledForUser($excludeGroups, $groupList, $setList, $groupIds, $expected): void {
2351
+        $user = $this->createMock(IUser::class);
2352 2352
 
2353
-		$this->config->method('getAppValue')
2354
-			->willReturnMap([
2355
-				['core', 'shareapi_exclude_groups', 'no', $excludeGroups],
2356
-				['core', 'shareapi_exclude_groups_list', '', $groupList],
2357
-			]);
2353
+        $this->config->method('getAppValue')
2354
+            ->willReturnMap([
2355
+                ['core', 'shareapi_exclude_groups', 'no', $excludeGroups],
2356
+                ['core', 'shareapi_exclude_groups_list', '', $groupList],
2357
+            ]);
2358 2358
 
2359
-		if ($setList !== null) {
2360
-			$this->config->expects($this->once())
2361
-				->method('setAppValue')
2362
-				->with('core', 'shareapi_exclude_groups_list', $setList);
2363
-		} else {
2364
-			$this->config->expects($this->never())
2365
-				->method('setAppValue');
2366
-		}
2359
+        if ($setList !== null) {
2360
+            $this->config->expects($this->once())
2361
+                ->method('setAppValue')
2362
+                ->with('core', 'shareapi_exclude_groups_list', $setList);
2363
+        } else {
2364
+            $this->config->expects($this->never())
2365
+                ->method('setAppValue');
2366
+        }
2367 2367
 
2368
-		$this->groupManager->method('getUserGroupIds')
2369
-			->with($user)
2370
-			->willReturn($groupIds);
2368
+        $this->groupManager->method('getUserGroupIds')
2369
+            ->with($user)
2370
+            ->willReturn($groupIds);
2371 2371
 
2372
-		$this->userManager->method('get')->with('user')->willReturn($user);
2373
-
2374
-		$res = $this->manager->sharingDisabledForUser('user');
2375
-		$this->assertEquals($expected, $res);
2376
-	}
2377
-
2378
-	public function dataCanShare() {
2379
-		$data = [];
2380
-
2381
-		/*
2372
+        $this->userManager->method('get')->with('user')->willReturn($user);
2373
+
2374
+        $res = $this->manager->sharingDisabledForUser('user');
2375
+        $this->assertEquals($expected, $res);
2376
+    }
2377
+
2378
+    public function dataCanShare() {
2379
+        $data = [];
2380
+
2381
+        /*
2382 2382
 		 * [expected, sharing enabled, disabled for user]
2383 2383
 		 */
2384 2384
 
2385
-		$data[] = [false, 'no', false];
2386
-		$data[] = [false, 'no', true];
2387
-		$data[] = [true, 'yes', false];
2388
-		$data[] = [false, 'yes', true];
2389
-
2390
-		return $data;
2391
-	}
2392
-
2393
-	/**
2394
-	 * @dataProvider dataCanShare
2395
-	 *
2396
-	 * @param bool $expected
2397
-	 * @param string $sharingEnabled
2398
-	 * @param bool $disabledForUser
2399
-	 */
2400
-	public function testCanShare($expected, $sharingEnabled, $disabledForUser): void {
2401
-		$this->config->method('getAppValue')
2402
-			->willReturnMap([
2403
-				['core', 'shareapi_enabled', 'yes', $sharingEnabled],
2404
-			]);
2405
-
2406
-		$manager = $this->createManagerMock()
2407
-			->setMethods(['sharingDisabledForUser'])
2408
-			->getMock();
2409
-
2410
-		$manager->method('sharingDisabledForUser')
2411
-			->with('user')
2412
-			->willReturn($disabledForUser);
2413
-
2414
-		$share = $this->manager->newShare();
2415
-		$share->setSharedBy('user');
2416
-
2417
-		$exception = false;
2418
-		try {
2419
-			$res = self::invokePrivate($manager, 'canShare', [$share]);
2420
-		} catch (\Exception $e) {
2421
-			$exception = true;
2422
-		}
2423
-
2424
-		$this->assertEquals($expected, !$exception);
2425
-	}
2426
-
2427
-	public function testCreateShareUser(): void {
2428
-		/** @var Manager&MockObject $manager */
2429
-		$manager = $this->createManagerMock()
2430
-			->onlyMethods(['canShare', 'generalCreateChecks', 'userCreateChecks', 'pathCreateChecks'])
2431
-			->getMock();
2432
-
2433
-		$shareOwner = $this->createMock(IUser::class);
2434
-		$shareOwner->method('getUID')->willReturn('shareOwner');
2435
-
2436
-		$storage = $this->createMock(IStorage::class);
2437
-		$path = $this->createMock(File::class);
2438
-		$path->method('getOwner')->willReturn($shareOwner);
2439
-		$path->method('getName')->willReturn('target');
2440
-		$path->method('getStorage')->willReturn($storage);
2441
-
2442
-		$share = $this->createShare(
2443
-			null,
2444
-			IShare::TYPE_USER,
2445
-			$path,
2446
-			'sharedWith',
2447
-			'sharedBy',
2448
-			null,
2449
-			\OCP\Constants::PERMISSION_ALL);
2450
-
2451
-		$manager->expects($this->once())
2452
-			->method('canShare')
2453
-			->with($share)
2454
-			->willReturn(true);
2455
-		$manager->expects($this->once())
2456
-			->method('generalCreateChecks')
2457
-			->with($share);
2458
-		;
2459
-		$manager->expects($this->once())
2460
-			->method('userCreateChecks')
2461
-			->with($share);
2462
-		;
2463
-		$manager->expects($this->once())
2464
-			->method('pathCreateChecks')
2465
-			->with($path);
2466
-
2467
-		$this->defaultProvider
2468
-			->expects($this->once())
2469
-			->method('create')
2470
-			->with($share)
2471
-			->willReturnArgument(0);
2472
-
2473
-		$share->expects($this->once())
2474
-			->method('setShareOwner')
2475
-			->with('shareOwner');
2476
-		$share->expects($this->once())
2477
-			->method('setTarget')
2478
-			->with('/target');
2479
-
2480
-		$manager->createShare($share);
2481
-	}
2482
-
2483
-	public function testCreateShareGroup(): void {
2484
-		$manager = $this->createManagerMock()
2485
-			->setMethods(['canShare', 'generalCreateChecks', 'groupCreateChecks', 'pathCreateChecks'])
2486
-			->getMock();
2487
-
2488
-		$shareOwner = $this->createMock(IUser::class);
2489
-		$shareOwner->method('getUID')->willReturn('shareOwner');
2490
-
2491
-		$storage = $this->createMock(IStorage::class);
2492
-		$path = $this->createMock(File::class);
2493
-		$path->method('getOwner')->willReturn($shareOwner);
2494
-		$path->method('getName')->willReturn('target');
2495
-		$path->method('getStorage')->willReturn($storage);
2496
-
2497
-		$share = $this->createShare(
2498
-			null,
2499
-			IShare::TYPE_GROUP,
2500
-			$path,
2501
-			'sharedWith',
2502
-			'sharedBy',
2503
-			null,
2504
-			\OCP\Constants::PERMISSION_ALL);
2505
-
2506
-		$manager->expects($this->once())
2507
-			->method('canShare')
2508
-			->with($share)
2509
-			->willReturn(true);
2510
-		$manager->expects($this->once())
2511
-			->method('generalCreateChecks')
2512
-			->with($share);
2513
-		;
2514
-		$manager->expects($this->once())
2515
-			->method('groupCreateChecks')
2516
-			->with($share);
2517
-		;
2518
-		$manager->expects($this->once())
2519
-			->method('pathCreateChecks')
2520
-			->with($path);
2521
-
2522
-		$this->defaultProvider
2523
-			->expects($this->once())
2524
-			->method('create')
2525
-			->with($share)
2526
-			->willReturnArgument(0);
2527
-
2528
-		$share->expects($this->once())
2529
-			->method('setShareOwner')
2530
-			->with('shareOwner');
2531
-		$share->expects($this->once())
2532
-			->method('setTarget')
2533
-			->with('/target');
2534
-
2535
-		$manager->createShare($share);
2536
-	}
2537
-
2538
-	public function testCreateShareLink(): void {
2539
-		$manager = $this->createManagerMock()
2540
-			->setMethods([
2541
-				'canShare',
2542
-				'generalCreateChecks',
2543
-				'linkCreateChecks',
2544
-				'pathCreateChecks',
2545
-				'validateExpirationDateLink',
2546
-				'verifyPassword',
2547
-				'setLinkParent',
2548
-			])
2549
-			->getMock();
2550
-
2551
-		$shareOwner = $this->createMock(IUser::class);
2552
-		$shareOwner->method('getUID')->willReturn('shareOwner');
2553
-
2554
-		$storage = $this->createMock(IStorage::class);
2555
-		$path = $this->createMock(File::class);
2556
-		$path->method('getOwner')->willReturn($shareOwner);
2557
-		$path->method('getName')->willReturn('target');
2558
-		$path->method('getId')->willReturn(1);
2559
-		$path->method('getStorage')->willReturn($storage);
2560
-
2561
-		$date = new \DateTime();
2562
-
2563
-		$share = $this->manager->newShare();
2564
-		$share->setShareType(IShare::TYPE_LINK)
2565
-			->setNode($path)
2566
-			->setSharedBy('sharedBy')
2567
-			->setPermissions(\OCP\Constants::PERMISSION_ALL)
2568
-			->setExpirationDate($date)
2569
-			->setPassword('password');
2570
-
2571
-		$manager->expects($this->once())
2572
-			->method('canShare')
2573
-			->with($share)
2574
-			->willReturn(true);
2575
-		$manager->expects($this->once())
2576
-			->method('generalCreateChecks')
2577
-			->with($share);
2578
-		;
2579
-		$manager->expects($this->once())
2580
-			->method('linkCreateChecks')
2581
-			->with($share);
2582
-		;
2583
-		$manager->expects($this->once())
2584
-			->method('pathCreateChecks')
2585
-			->with($path);
2586
-		$manager->expects($this->once())
2587
-			->method('validateExpirationDateLink')
2588
-			->with($share)
2589
-			->willReturn($share);
2590
-		$manager->expects($this->once())
2591
-			->method('verifyPassword')
2592
-			->with('password');
2593
-		$manager->expects($this->once())
2594
-			->method('setLinkParent')
2595
-			->with($share);
2596
-
2597
-		$this->hasher->expects($this->once())
2598
-			->method('hash')
2599
-			->with('password')
2600
-			->willReturn('hashed');
2601
-
2602
-		$this->secureRandom->method('generate')
2603
-			->willReturn('token');
2604
-
2605
-		$this->defaultProvider
2606
-			->expects($this->once())
2607
-			->method('create')
2608
-			->with($share)
2609
-			->willReturnCallback(function (Share $share) {
2610
-				return $share->setId(42);
2611
-			});
2612
-
2613
-		$this->dispatcher->expects($this->exactly(2))
2614
-			->method('dispatchTyped')
2615
-			->withConsecutive(
2616
-				// Pre share
2617
-				[
2618
-					$this->callback(function (BeforeShareCreatedEvent $e) use ($path, $date) {
2619
-						$share = $e->getShare();
2620
-
2621
-						return $share->getShareType() === IShare::TYPE_LINK &&
2622
-							$share->getNode() === $path &&
2623
-							$share->getSharedBy() === 'sharedBy' &&
2624
-							$share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
2625
-							$share->getExpirationDate() === $date &&
2626
-							$share->getPassword() === 'hashed' &&
2627
-							$share->getToken() === 'token';
2628
-					})
2629
-				],
2630
-				// Post share
2631
-				[
2632
-					$this->callback(function (ShareCreatedEvent $e) use ($path, $date) {
2633
-						$share = $e->getShare();
2634
-
2635
-						return $share->getShareType() === IShare::TYPE_LINK &&
2636
-							$share->getNode() === $path &&
2637
-							$share->getSharedBy() === 'sharedBy' &&
2638
-							$share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
2639
-							$share->getExpirationDate() === $date &&
2640
-							$share->getPassword() === 'hashed' &&
2641
-							$share->getToken() === 'token' &&
2642
-							$share->getId() === '42' &&
2643
-							$share->getTarget() === '/target';
2644
-					})
2645
-				]
2646
-			);
2647
-
2648
-		/** @var IShare $share */
2649
-		$share = $manager->createShare($share);
2650
-
2651
-		$this->assertSame('shareOwner', $share->getShareOwner());
2652
-		$this->assertEquals('/target', $share->getTarget());
2653
-		$this->assertSame($date, $share->getExpirationDate());
2654
-		$this->assertEquals('token', $share->getToken());
2655
-		$this->assertEquals('hashed', $share->getPassword());
2656
-	}
2657
-
2658
-	public function testCreateShareMail(): void {
2659
-		$manager = $this->createManagerMock()
2660
-			->setMethods([
2661
-				'canShare',
2662
-				'generalCreateChecks',
2663
-				'linkCreateChecks',
2664
-				'pathCreateChecks',
2665
-				'validateExpirationDateLink',
2666
-				'verifyPassword',
2667
-				'setLinkParent',
2668
-			])
2669
-			->getMock();
2670
-
2671
-		$shareOwner = $this->createMock(IUser::class);
2672
-		$shareOwner->method('getUID')->willReturn('shareOwner');
2673
-
2674
-		$storage = $this->createMock(IStorage::class);
2675
-		$path = $this->createMock(File::class);
2676
-		$path->method('getOwner')->willReturn($shareOwner);
2677
-		$path->method('getName')->willReturn('target');
2678
-		$path->method('getId')->willReturn(1);
2679
-		$path->method('getStorage')->willReturn($storage);
2680
-
2681
-		$share = $this->manager->newShare();
2682
-		$share->setShareType(IShare::TYPE_EMAIL)
2683
-			->setNode($path)
2684
-			->setSharedBy('sharedBy')
2685
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
2686
-
2687
-		$manager->expects($this->once())
2688
-			->method('canShare')
2689
-			->with($share)
2690
-			->willReturn(true);
2691
-		$manager->expects($this->once())
2692
-			->method('generalCreateChecks')
2693
-			->with($share);
2694
-
2695
-		$manager->expects($this->once())
2696
-			->method('linkCreateChecks');
2697
-		$manager->expects($this->once())
2698
-			->method('pathCreateChecks')
2699
-			->with($path);
2700
-		$manager->expects($this->once())
2701
-			->method('validateExpirationDateLink')
2702
-			->with($share)
2703
-			->willReturn($share);
2704
-		$manager->expects($this->once())
2705
-			->method('verifyPassword');
2706
-		$manager->expects($this->once())
2707
-			->method('setLinkParent');
2708
-
2709
-		$this->secureRandom->method('generate')
2710
-			->willReturn('token');
2711
-
2712
-		$this->defaultProvider
2713
-			->expects($this->once())
2714
-			->method('create')
2715
-			->with($share)
2716
-			->willReturnCallback(function (Share $share) {
2717
-				return $share->setId(42);
2718
-			});
2719
-
2720
-		$this->dispatcher->expects($this->exactly(2))
2721
-			->method('dispatchTyped')
2722
-			->withConsecutive(
2723
-				[
2724
-					$this->callback(function (BeforeShareCreatedEvent $e) use ($path) {
2725
-						$share = $e->getShare();
2726
-
2727
-						return $share->getShareType() === IShare::TYPE_EMAIL &&
2728
-							$share->getNode() === $path &&
2729
-							$share->getSharedBy() === 'sharedBy' &&
2730
-							$share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
2731
-							$share->getExpirationDate() === null &&
2732
-							$share->getPassword() === null &&
2733
-							$share->getToken() === 'token';
2734
-					})
2735
-				],
2736
-				[
2737
-					$this->callback(function (ShareCreatedEvent $e) use ($path) {
2738
-						$share = $e->getShare();
2739
-
2740
-						return $share->getShareType() === IShare::TYPE_EMAIL &&
2741
-							$share->getNode() === $path &&
2742
-							$share->getSharedBy() === 'sharedBy' &&
2743
-							$share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
2744
-							$share->getExpirationDate() === null &&
2745
-							$share->getPassword() === null &&
2746
-							$share->getToken() === 'token' &&
2747
-							$share->getId() === '42' &&
2748
-							$share->getTarget() === '/target';
2749
-					})
2750
-				],
2751
-			);
2752
-
2753
-		/** @var IShare $share */
2754
-		$share = $manager->createShare($share);
2755
-
2756
-		$this->assertSame('shareOwner', $share->getShareOwner());
2757
-		$this->assertEquals('/target', $share->getTarget());
2758
-		$this->assertEquals('token', $share->getToken());
2759
-	}
2760
-
2761
-
2762
-	public function testCreateShareHookError(): void {
2763
-		$this->expectException(\Exception::class);
2764
-		$this->expectExceptionMessage('I won\'t let you share');
2765
-
2766
-		$manager = $this->createManagerMock()
2767
-			->setMethods([
2768
-				'canShare',
2769
-				'generalCreateChecks',
2770
-				'userCreateChecks',
2771
-				'pathCreateChecks',
2772
-			])
2773
-			->getMock();
2774
-
2775
-		$shareOwner = $this->createMock(IUser::class);
2776
-		$shareOwner->method('getUID')->willReturn('shareOwner');
2777
-
2778
-		$storage = $this->createMock(IStorage::class);
2779
-		$path = $this->createMock(File::class);
2780
-		$path->method('getOwner')->willReturn($shareOwner);
2781
-		$path->method('getName')->willReturn('target');
2782
-		$path->method('getStorage')->willReturn($storage);
2783
-
2784
-		$share = $this->createShare(
2785
-			null,
2786
-			IShare::TYPE_USER,
2787
-			$path,
2788
-			'sharedWith',
2789
-			'sharedBy',
2790
-			null,
2791
-			\OCP\Constants::PERMISSION_ALL);
2792
-
2793
-		$manager->expects($this->once())
2794
-			->method('canShare')
2795
-			->with($share)
2796
-			->willReturn(true);
2797
-		$manager->expects($this->once())
2798
-			->method('generalCreateChecks')
2799
-			->with($share);
2800
-		;
2801
-		$manager->expects($this->once())
2802
-			->method('userCreateChecks')
2803
-			->with($share);
2804
-		;
2805
-		$manager->expects($this->once())
2806
-			->method('pathCreateChecks')
2807
-			->with($path);
2808
-
2809
-		$share->expects($this->once())
2810
-			->method('setShareOwner')
2811
-			->with('shareOwner');
2812
-		$share->expects($this->once())
2813
-			->method('setTarget')
2814
-			->with('/target');
2815
-
2816
-		// Pre share
2817
-		$this->dispatcher->expects($this->once())
2818
-			->method('dispatchTyped')
2819
-			->with(
2820
-				$this->isInstanceOf(BeforeShareCreatedEvent::class)
2821
-			)->willReturnCallback(function (BeforeShareCreatedEvent $e) {
2822
-				$e->setError('I won\'t let you share!');
2823
-				$e->stopPropagation();
2824
-			}
2825
-			);
2826
-
2827
-		$manager->createShare($share);
2828
-	}
2829
-
2830
-	public function testCreateShareOfIncomingFederatedShare(): void {
2831
-		$manager = $this->createManagerMock()
2832
-			->setMethods(['canShare', 'generalCreateChecks', 'userCreateChecks', 'pathCreateChecks'])
2833
-			->getMock();
2834
-
2835
-		$shareOwner = $this->createMock(IUser::class);
2836
-		$shareOwner->method('getUID')->willReturn('shareOwner');
2837
-
2838
-		$storage = $this->createMock(IStorage::class);
2839
-		$storage->method('instanceOfStorage')
2840
-			->with('OCA\Files_Sharing\External\Storage')
2841
-			->willReturn(true);
2842
-
2843
-		$storage2 = $this->createMock(IStorage::class);
2844
-		$storage2->method('instanceOfStorage')
2845
-			->with('OCA\Files_Sharing\External\Storage')
2846
-			->willReturn(false);
2847
-
2848
-		$path = $this->createMock(File::class);
2849
-		$path->expects($this->never())->method('getOwner');
2850
-		$path->method('getName')->willReturn('target');
2851
-		$path->method('getStorage')->willReturn($storage);
2852
-
2853
-		$parent = $this->createMock(Folder::class);
2854
-		$parent->method('getStorage')->willReturn($storage);
2855
-
2856
-		$parentParent = $this->createMock(Folder::class);
2857
-		$parentParent->method('getStorage')->willReturn($storage2);
2858
-		$parentParent->method('getOwner')->willReturn($shareOwner);
2859
-
2860
-		$path->method('getParent')->willReturn($parent);
2861
-		$parent->method('getParent')->willReturn($parentParent);
2862
-
2863
-		$share = $this->createShare(
2864
-			null,
2865
-			IShare::TYPE_USER,
2866
-			$path,
2867
-			'sharedWith',
2868
-			'sharedBy',
2869
-			null,
2870
-			\OCP\Constants::PERMISSION_ALL);
2871
-
2872
-		$manager->expects($this->once())
2873
-			->method('canShare')
2874
-			->with($share)
2875
-			->willReturn(true);
2876
-		$manager->expects($this->once())
2877
-			->method('generalCreateChecks')
2878
-			->with($share);
2879
-		;
2880
-		$manager->expects($this->once())
2881
-			->method('userCreateChecks')
2882
-			->with($share);
2883
-		;
2884
-		$manager->expects($this->once())
2885
-			->method('pathCreateChecks')
2886
-			->with($path);
2887
-
2888
-		$this->defaultProvider
2889
-			->expects($this->once())
2890
-			->method('create')
2891
-			->with($share)
2892
-			->willReturnArgument(0);
2893
-
2894
-		$share->expects($this->once())
2895
-			->method('setShareOwner')
2896
-			->with('shareOwner');
2897
-		$share->expects($this->once())
2898
-			->method('setTarget')
2899
-			->with('/target');
2900
-
2901
-		$manager->createShare($share);
2902
-	}
2903
-
2904
-	public function testGetSharesBy(): void {
2905
-		$share = $this->manager->newShare();
2906
-
2907
-		$node = $this->createMock(Folder::class);
2908
-
2909
-		$this->defaultProvider->expects($this->once())
2910
-			->method('getSharesBy')
2911
-			->with(
2912
-				$this->equalTo('user'),
2913
-				$this->equalTo(IShare::TYPE_USER),
2914
-				$this->equalTo($node),
2915
-				$this->equalTo(true),
2916
-				$this->equalTo(1),
2917
-				$this->equalTo(1)
2918
-			)->willReturn([$share]);
2919
-
2920
-		$shares = $this->manager->getSharesBy('user', IShare::TYPE_USER, $node, true, 1, 1);
2921
-
2922
-		$this->assertCount(1, $shares);
2923
-		$this->assertSame($share, $shares[0]);
2924
-	}
2925
-
2926
-	public function testGetSharesByOwnerless(): void {
2927
-		$mount = $this->createMock(IShareOwnerlessMount::class);
2928
-
2929
-		$node = $this->createMock(Folder::class);
2930
-		$node
2931
-			->expects($this->once())
2932
-			->method('getMountPoint')
2933
-			->willReturn($mount);
2934
-
2935
-		$share = $this->manager->newShare();
2936
-		$share->setNode($node);
2937
-		$share->setShareType(IShare::TYPE_USER);
2938
-
2939
-		$this->defaultProvider
2940
-			->expects($this->once())
2941
-			->method('getSharesByPath')
2942
-			->with($this->equalTo($node))
2943
-			->willReturn([$share]);
2944
-
2945
-		$shares = $this->manager->getSharesBy('user', IShare::TYPE_USER, $node, true, 1, 1);
2946
-
2947
-		$this->assertCount(1, $shares);
2948
-		$this->assertSame($share, $shares[0]);
2949
-	}
2950
-
2951
-	/**
2952
-	 * Test to ensure we correctly remove expired link shares
2953
-	 *
2954
-	 * We have 8 Shares and we want the 3 first valid shares.
2955
-	 * share 3-6 and 8 are expired. Thus at the end of this test we should
2956
-	 * have received share 1,2 and 7. And from the manager. Share 3-6 should be
2957
-	 * deleted (as they are evaluated). but share 8 should still be there.
2958
-	 */
2959
-	public function testGetSharesByExpiredLinkShares(): void {
2960
-		$manager = $this->createManagerMock()
2961
-			->setMethods(['deleteShare'])
2962
-			->getMock();
2963
-
2964
-		/** @var \OCP\Share\IShare[] $shares */
2965
-		$shares = [];
2966
-
2967
-		/*
2385
+        $data[] = [false, 'no', false];
2386
+        $data[] = [false, 'no', true];
2387
+        $data[] = [true, 'yes', false];
2388
+        $data[] = [false, 'yes', true];
2389
+
2390
+        return $data;
2391
+    }
2392
+
2393
+    /**
2394
+     * @dataProvider dataCanShare
2395
+     *
2396
+     * @param bool $expected
2397
+     * @param string $sharingEnabled
2398
+     * @param bool $disabledForUser
2399
+     */
2400
+    public function testCanShare($expected, $sharingEnabled, $disabledForUser): void {
2401
+        $this->config->method('getAppValue')
2402
+            ->willReturnMap([
2403
+                ['core', 'shareapi_enabled', 'yes', $sharingEnabled],
2404
+            ]);
2405
+
2406
+        $manager = $this->createManagerMock()
2407
+            ->setMethods(['sharingDisabledForUser'])
2408
+            ->getMock();
2409
+
2410
+        $manager->method('sharingDisabledForUser')
2411
+            ->with('user')
2412
+            ->willReturn($disabledForUser);
2413
+
2414
+        $share = $this->manager->newShare();
2415
+        $share->setSharedBy('user');
2416
+
2417
+        $exception = false;
2418
+        try {
2419
+            $res = self::invokePrivate($manager, 'canShare', [$share]);
2420
+        } catch (\Exception $e) {
2421
+            $exception = true;
2422
+        }
2423
+
2424
+        $this->assertEquals($expected, !$exception);
2425
+    }
2426
+
2427
+    public function testCreateShareUser(): void {
2428
+        /** @var Manager&MockObject $manager */
2429
+        $manager = $this->createManagerMock()
2430
+            ->onlyMethods(['canShare', 'generalCreateChecks', 'userCreateChecks', 'pathCreateChecks'])
2431
+            ->getMock();
2432
+
2433
+        $shareOwner = $this->createMock(IUser::class);
2434
+        $shareOwner->method('getUID')->willReturn('shareOwner');
2435
+
2436
+        $storage = $this->createMock(IStorage::class);
2437
+        $path = $this->createMock(File::class);
2438
+        $path->method('getOwner')->willReturn($shareOwner);
2439
+        $path->method('getName')->willReturn('target');
2440
+        $path->method('getStorage')->willReturn($storage);
2441
+
2442
+        $share = $this->createShare(
2443
+            null,
2444
+            IShare::TYPE_USER,
2445
+            $path,
2446
+            'sharedWith',
2447
+            'sharedBy',
2448
+            null,
2449
+            \OCP\Constants::PERMISSION_ALL);
2450
+
2451
+        $manager->expects($this->once())
2452
+            ->method('canShare')
2453
+            ->with($share)
2454
+            ->willReturn(true);
2455
+        $manager->expects($this->once())
2456
+            ->method('generalCreateChecks')
2457
+            ->with($share);
2458
+        ;
2459
+        $manager->expects($this->once())
2460
+            ->method('userCreateChecks')
2461
+            ->with($share);
2462
+        ;
2463
+        $manager->expects($this->once())
2464
+            ->method('pathCreateChecks')
2465
+            ->with($path);
2466
+
2467
+        $this->defaultProvider
2468
+            ->expects($this->once())
2469
+            ->method('create')
2470
+            ->with($share)
2471
+            ->willReturnArgument(0);
2472
+
2473
+        $share->expects($this->once())
2474
+            ->method('setShareOwner')
2475
+            ->with('shareOwner');
2476
+        $share->expects($this->once())
2477
+            ->method('setTarget')
2478
+            ->with('/target');
2479
+
2480
+        $manager->createShare($share);
2481
+    }
2482
+
2483
+    public function testCreateShareGroup(): void {
2484
+        $manager = $this->createManagerMock()
2485
+            ->setMethods(['canShare', 'generalCreateChecks', 'groupCreateChecks', 'pathCreateChecks'])
2486
+            ->getMock();
2487
+
2488
+        $shareOwner = $this->createMock(IUser::class);
2489
+        $shareOwner->method('getUID')->willReturn('shareOwner');
2490
+
2491
+        $storage = $this->createMock(IStorage::class);
2492
+        $path = $this->createMock(File::class);
2493
+        $path->method('getOwner')->willReturn($shareOwner);
2494
+        $path->method('getName')->willReturn('target');
2495
+        $path->method('getStorage')->willReturn($storage);
2496
+
2497
+        $share = $this->createShare(
2498
+            null,
2499
+            IShare::TYPE_GROUP,
2500
+            $path,
2501
+            'sharedWith',
2502
+            'sharedBy',
2503
+            null,
2504
+            \OCP\Constants::PERMISSION_ALL);
2505
+
2506
+        $manager->expects($this->once())
2507
+            ->method('canShare')
2508
+            ->with($share)
2509
+            ->willReturn(true);
2510
+        $manager->expects($this->once())
2511
+            ->method('generalCreateChecks')
2512
+            ->with($share);
2513
+        ;
2514
+        $manager->expects($this->once())
2515
+            ->method('groupCreateChecks')
2516
+            ->with($share);
2517
+        ;
2518
+        $manager->expects($this->once())
2519
+            ->method('pathCreateChecks')
2520
+            ->with($path);
2521
+
2522
+        $this->defaultProvider
2523
+            ->expects($this->once())
2524
+            ->method('create')
2525
+            ->with($share)
2526
+            ->willReturnArgument(0);
2527
+
2528
+        $share->expects($this->once())
2529
+            ->method('setShareOwner')
2530
+            ->with('shareOwner');
2531
+        $share->expects($this->once())
2532
+            ->method('setTarget')
2533
+            ->with('/target');
2534
+
2535
+        $manager->createShare($share);
2536
+    }
2537
+
2538
+    public function testCreateShareLink(): void {
2539
+        $manager = $this->createManagerMock()
2540
+            ->setMethods([
2541
+                'canShare',
2542
+                'generalCreateChecks',
2543
+                'linkCreateChecks',
2544
+                'pathCreateChecks',
2545
+                'validateExpirationDateLink',
2546
+                'verifyPassword',
2547
+                'setLinkParent',
2548
+            ])
2549
+            ->getMock();
2550
+
2551
+        $shareOwner = $this->createMock(IUser::class);
2552
+        $shareOwner->method('getUID')->willReturn('shareOwner');
2553
+
2554
+        $storage = $this->createMock(IStorage::class);
2555
+        $path = $this->createMock(File::class);
2556
+        $path->method('getOwner')->willReturn($shareOwner);
2557
+        $path->method('getName')->willReturn('target');
2558
+        $path->method('getId')->willReturn(1);
2559
+        $path->method('getStorage')->willReturn($storage);
2560
+
2561
+        $date = new \DateTime();
2562
+
2563
+        $share = $this->manager->newShare();
2564
+        $share->setShareType(IShare::TYPE_LINK)
2565
+            ->setNode($path)
2566
+            ->setSharedBy('sharedBy')
2567
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL)
2568
+            ->setExpirationDate($date)
2569
+            ->setPassword('password');
2570
+
2571
+        $manager->expects($this->once())
2572
+            ->method('canShare')
2573
+            ->with($share)
2574
+            ->willReturn(true);
2575
+        $manager->expects($this->once())
2576
+            ->method('generalCreateChecks')
2577
+            ->with($share);
2578
+        ;
2579
+        $manager->expects($this->once())
2580
+            ->method('linkCreateChecks')
2581
+            ->with($share);
2582
+        ;
2583
+        $manager->expects($this->once())
2584
+            ->method('pathCreateChecks')
2585
+            ->with($path);
2586
+        $manager->expects($this->once())
2587
+            ->method('validateExpirationDateLink')
2588
+            ->with($share)
2589
+            ->willReturn($share);
2590
+        $manager->expects($this->once())
2591
+            ->method('verifyPassword')
2592
+            ->with('password');
2593
+        $manager->expects($this->once())
2594
+            ->method('setLinkParent')
2595
+            ->with($share);
2596
+
2597
+        $this->hasher->expects($this->once())
2598
+            ->method('hash')
2599
+            ->with('password')
2600
+            ->willReturn('hashed');
2601
+
2602
+        $this->secureRandom->method('generate')
2603
+            ->willReturn('token');
2604
+
2605
+        $this->defaultProvider
2606
+            ->expects($this->once())
2607
+            ->method('create')
2608
+            ->with($share)
2609
+            ->willReturnCallback(function (Share $share) {
2610
+                return $share->setId(42);
2611
+            });
2612
+
2613
+        $this->dispatcher->expects($this->exactly(2))
2614
+            ->method('dispatchTyped')
2615
+            ->withConsecutive(
2616
+                // Pre share
2617
+                [
2618
+                    $this->callback(function (BeforeShareCreatedEvent $e) use ($path, $date) {
2619
+                        $share = $e->getShare();
2620
+
2621
+                        return $share->getShareType() === IShare::TYPE_LINK &&
2622
+                            $share->getNode() === $path &&
2623
+                            $share->getSharedBy() === 'sharedBy' &&
2624
+                            $share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
2625
+                            $share->getExpirationDate() === $date &&
2626
+                            $share->getPassword() === 'hashed' &&
2627
+                            $share->getToken() === 'token';
2628
+                    })
2629
+                ],
2630
+                // Post share
2631
+                [
2632
+                    $this->callback(function (ShareCreatedEvent $e) use ($path, $date) {
2633
+                        $share = $e->getShare();
2634
+
2635
+                        return $share->getShareType() === IShare::TYPE_LINK &&
2636
+                            $share->getNode() === $path &&
2637
+                            $share->getSharedBy() === 'sharedBy' &&
2638
+                            $share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
2639
+                            $share->getExpirationDate() === $date &&
2640
+                            $share->getPassword() === 'hashed' &&
2641
+                            $share->getToken() === 'token' &&
2642
+                            $share->getId() === '42' &&
2643
+                            $share->getTarget() === '/target';
2644
+                    })
2645
+                ]
2646
+            );
2647
+
2648
+        /** @var IShare $share */
2649
+        $share = $manager->createShare($share);
2650
+
2651
+        $this->assertSame('shareOwner', $share->getShareOwner());
2652
+        $this->assertEquals('/target', $share->getTarget());
2653
+        $this->assertSame($date, $share->getExpirationDate());
2654
+        $this->assertEquals('token', $share->getToken());
2655
+        $this->assertEquals('hashed', $share->getPassword());
2656
+    }
2657
+
2658
+    public function testCreateShareMail(): void {
2659
+        $manager = $this->createManagerMock()
2660
+            ->setMethods([
2661
+                'canShare',
2662
+                'generalCreateChecks',
2663
+                'linkCreateChecks',
2664
+                'pathCreateChecks',
2665
+                'validateExpirationDateLink',
2666
+                'verifyPassword',
2667
+                'setLinkParent',
2668
+            ])
2669
+            ->getMock();
2670
+
2671
+        $shareOwner = $this->createMock(IUser::class);
2672
+        $shareOwner->method('getUID')->willReturn('shareOwner');
2673
+
2674
+        $storage = $this->createMock(IStorage::class);
2675
+        $path = $this->createMock(File::class);
2676
+        $path->method('getOwner')->willReturn($shareOwner);
2677
+        $path->method('getName')->willReturn('target');
2678
+        $path->method('getId')->willReturn(1);
2679
+        $path->method('getStorage')->willReturn($storage);
2680
+
2681
+        $share = $this->manager->newShare();
2682
+        $share->setShareType(IShare::TYPE_EMAIL)
2683
+            ->setNode($path)
2684
+            ->setSharedBy('sharedBy')
2685
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
2686
+
2687
+        $manager->expects($this->once())
2688
+            ->method('canShare')
2689
+            ->with($share)
2690
+            ->willReturn(true);
2691
+        $manager->expects($this->once())
2692
+            ->method('generalCreateChecks')
2693
+            ->with($share);
2694
+
2695
+        $manager->expects($this->once())
2696
+            ->method('linkCreateChecks');
2697
+        $manager->expects($this->once())
2698
+            ->method('pathCreateChecks')
2699
+            ->with($path);
2700
+        $manager->expects($this->once())
2701
+            ->method('validateExpirationDateLink')
2702
+            ->with($share)
2703
+            ->willReturn($share);
2704
+        $manager->expects($this->once())
2705
+            ->method('verifyPassword');
2706
+        $manager->expects($this->once())
2707
+            ->method('setLinkParent');
2708
+
2709
+        $this->secureRandom->method('generate')
2710
+            ->willReturn('token');
2711
+
2712
+        $this->defaultProvider
2713
+            ->expects($this->once())
2714
+            ->method('create')
2715
+            ->with($share)
2716
+            ->willReturnCallback(function (Share $share) {
2717
+                return $share->setId(42);
2718
+            });
2719
+
2720
+        $this->dispatcher->expects($this->exactly(2))
2721
+            ->method('dispatchTyped')
2722
+            ->withConsecutive(
2723
+                [
2724
+                    $this->callback(function (BeforeShareCreatedEvent $e) use ($path) {
2725
+                        $share = $e->getShare();
2726
+
2727
+                        return $share->getShareType() === IShare::TYPE_EMAIL &&
2728
+                            $share->getNode() === $path &&
2729
+                            $share->getSharedBy() === 'sharedBy' &&
2730
+                            $share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
2731
+                            $share->getExpirationDate() === null &&
2732
+                            $share->getPassword() === null &&
2733
+                            $share->getToken() === 'token';
2734
+                    })
2735
+                ],
2736
+                [
2737
+                    $this->callback(function (ShareCreatedEvent $e) use ($path) {
2738
+                        $share = $e->getShare();
2739
+
2740
+                        return $share->getShareType() === IShare::TYPE_EMAIL &&
2741
+                            $share->getNode() === $path &&
2742
+                            $share->getSharedBy() === 'sharedBy' &&
2743
+                            $share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
2744
+                            $share->getExpirationDate() === null &&
2745
+                            $share->getPassword() === null &&
2746
+                            $share->getToken() === 'token' &&
2747
+                            $share->getId() === '42' &&
2748
+                            $share->getTarget() === '/target';
2749
+                    })
2750
+                ],
2751
+            );
2752
+
2753
+        /** @var IShare $share */
2754
+        $share = $manager->createShare($share);
2755
+
2756
+        $this->assertSame('shareOwner', $share->getShareOwner());
2757
+        $this->assertEquals('/target', $share->getTarget());
2758
+        $this->assertEquals('token', $share->getToken());
2759
+    }
2760
+
2761
+
2762
+    public function testCreateShareHookError(): void {
2763
+        $this->expectException(\Exception::class);
2764
+        $this->expectExceptionMessage('I won\'t let you share');
2765
+
2766
+        $manager = $this->createManagerMock()
2767
+            ->setMethods([
2768
+                'canShare',
2769
+                'generalCreateChecks',
2770
+                'userCreateChecks',
2771
+                'pathCreateChecks',
2772
+            ])
2773
+            ->getMock();
2774
+
2775
+        $shareOwner = $this->createMock(IUser::class);
2776
+        $shareOwner->method('getUID')->willReturn('shareOwner');
2777
+
2778
+        $storage = $this->createMock(IStorage::class);
2779
+        $path = $this->createMock(File::class);
2780
+        $path->method('getOwner')->willReturn($shareOwner);
2781
+        $path->method('getName')->willReturn('target');
2782
+        $path->method('getStorage')->willReturn($storage);
2783
+
2784
+        $share = $this->createShare(
2785
+            null,
2786
+            IShare::TYPE_USER,
2787
+            $path,
2788
+            'sharedWith',
2789
+            'sharedBy',
2790
+            null,
2791
+            \OCP\Constants::PERMISSION_ALL);
2792
+
2793
+        $manager->expects($this->once())
2794
+            ->method('canShare')
2795
+            ->with($share)
2796
+            ->willReturn(true);
2797
+        $manager->expects($this->once())
2798
+            ->method('generalCreateChecks')
2799
+            ->with($share);
2800
+        ;
2801
+        $manager->expects($this->once())
2802
+            ->method('userCreateChecks')
2803
+            ->with($share);
2804
+        ;
2805
+        $manager->expects($this->once())
2806
+            ->method('pathCreateChecks')
2807
+            ->with($path);
2808
+
2809
+        $share->expects($this->once())
2810
+            ->method('setShareOwner')
2811
+            ->with('shareOwner');
2812
+        $share->expects($this->once())
2813
+            ->method('setTarget')
2814
+            ->with('/target');
2815
+
2816
+        // Pre share
2817
+        $this->dispatcher->expects($this->once())
2818
+            ->method('dispatchTyped')
2819
+            ->with(
2820
+                $this->isInstanceOf(BeforeShareCreatedEvent::class)
2821
+            )->willReturnCallback(function (BeforeShareCreatedEvent $e) {
2822
+                $e->setError('I won\'t let you share!');
2823
+                $e->stopPropagation();
2824
+            }
2825
+            );
2826
+
2827
+        $manager->createShare($share);
2828
+    }
2829
+
2830
+    public function testCreateShareOfIncomingFederatedShare(): void {
2831
+        $manager = $this->createManagerMock()
2832
+            ->setMethods(['canShare', 'generalCreateChecks', 'userCreateChecks', 'pathCreateChecks'])
2833
+            ->getMock();
2834
+
2835
+        $shareOwner = $this->createMock(IUser::class);
2836
+        $shareOwner->method('getUID')->willReturn('shareOwner');
2837
+
2838
+        $storage = $this->createMock(IStorage::class);
2839
+        $storage->method('instanceOfStorage')
2840
+            ->with('OCA\Files_Sharing\External\Storage')
2841
+            ->willReturn(true);
2842
+
2843
+        $storage2 = $this->createMock(IStorage::class);
2844
+        $storage2->method('instanceOfStorage')
2845
+            ->with('OCA\Files_Sharing\External\Storage')
2846
+            ->willReturn(false);
2847
+
2848
+        $path = $this->createMock(File::class);
2849
+        $path->expects($this->never())->method('getOwner');
2850
+        $path->method('getName')->willReturn('target');
2851
+        $path->method('getStorage')->willReturn($storage);
2852
+
2853
+        $parent = $this->createMock(Folder::class);
2854
+        $parent->method('getStorage')->willReturn($storage);
2855
+
2856
+        $parentParent = $this->createMock(Folder::class);
2857
+        $parentParent->method('getStorage')->willReturn($storage2);
2858
+        $parentParent->method('getOwner')->willReturn($shareOwner);
2859
+
2860
+        $path->method('getParent')->willReturn($parent);
2861
+        $parent->method('getParent')->willReturn($parentParent);
2862
+
2863
+        $share = $this->createShare(
2864
+            null,
2865
+            IShare::TYPE_USER,
2866
+            $path,
2867
+            'sharedWith',
2868
+            'sharedBy',
2869
+            null,
2870
+            \OCP\Constants::PERMISSION_ALL);
2871
+
2872
+        $manager->expects($this->once())
2873
+            ->method('canShare')
2874
+            ->with($share)
2875
+            ->willReturn(true);
2876
+        $manager->expects($this->once())
2877
+            ->method('generalCreateChecks')
2878
+            ->with($share);
2879
+        ;
2880
+        $manager->expects($this->once())
2881
+            ->method('userCreateChecks')
2882
+            ->with($share);
2883
+        ;
2884
+        $manager->expects($this->once())
2885
+            ->method('pathCreateChecks')
2886
+            ->with($path);
2887
+
2888
+        $this->defaultProvider
2889
+            ->expects($this->once())
2890
+            ->method('create')
2891
+            ->with($share)
2892
+            ->willReturnArgument(0);
2893
+
2894
+        $share->expects($this->once())
2895
+            ->method('setShareOwner')
2896
+            ->with('shareOwner');
2897
+        $share->expects($this->once())
2898
+            ->method('setTarget')
2899
+            ->with('/target');
2900
+
2901
+        $manager->createShare($share);
2902
+    }
2903
+
2904
+    public function testGetSharesBy(): void {
2905
+        $share = $this->manager->newShare();
2906
+
2907
+        $node = $this->createMock(Folder::class);
2908
+
2909
+        $this->defaultProvider->expects($this->once())
2910
+            ->method('getSharesBy')
2911
+            ->with(
2912
+                $this->equalTo('user'),
2913
+                $this->equalTo(IShare::TYPE_USER),
2914
+                $this->equalTo($node),
2915
+                $this->equalTo(true),
2916
+                $this->equalTo(1),
2917
+                $this->equalTo(1)
2918
+            )->willReturn([$share]);
2919
+
2920
+        $shares = $this->manager->getSharesBy('user', IShare::TYPE_USER, $node, true, 1, 1);
2921
+
2922
+        $this->assertCount(1, $shares);
2923
+        $this->assertSame($share, $shares[0]);
2924
+    }
2925
+
2926
+    public function testGetSharesByOwnerless(): void {
2927
+        $mount = $this->createMock(IShareOwnerlessMount::class);
2928
+
2929
+        $node = $this->createMock(Folder::class);
2930
+        $node
2931
+            ->expects($this->once())
2932
+            ->method('getMountPoint')
2933
+            ->willReturn($mount);
2934
+
2935
+        $share = $this->manager->newShare();
2936
+        $share->setNode($node);
2937
+        $share->setShareType(IShare::TYPE_USER);
2938
+
2939
+        $this->defaultProvider
2940
+            ->expects($this->once())
2941
+            ->method('getSharesByPath')
2942
+            ->with($this->equalTo($node))
2943
+            ->willReturn([$share]);
2944
+
2945
+        $shares = $this->manager->getSharesBy('user', IShare::TYPE_USER, $node, true, 1, 1);
2946
+
2947
+        $this->assertCount(1, $shares);
2948
+        $this->assertSame($share, $shares[0]);
2949
+    }
2950
+
2951
+    /**
2952
+     * Test to ensure we correctly remove expired link shares
2953
+     *
2954
+     * We have 8 Shares and we want the 3 first valid shares.
2955
+     * share 3-6 and 8 are expired. Thus at the end of this test we should
2956
+     * have received share 1,2 and 7. And from the manager. Share 3-6 should be
2957
+     * deleted (as they are evaluated). but share 8 should still be there.
2958
+     */
2959
+    public function testGetSharesByExpiredLinkShares(): void {
2960
+        $manager = $this->createManagerMock()
2961
+            ->setMethods(['deleteShare'])
2962
+            ->getMock();
2963
+
2964
+        /** @var \OCP\Share\IShare[] $shares */
2965
+        $shares = [];
2966
+
2967
+        /*
2968 2968
 		 * This results in an array of 8 IShare elements
2969 2969
 		 */
2970
-		for ($i = 0; $i < 8; $i++) {
2971
-			$share = $this->manager->newShare();
2972
-			$share->setId($i);
2973
-			$shares[] = $share;
2974
-		}
2970
+        for ($i = 0; $i < 8; $i++) {
2971
+            $share = $this->manager->newShare();
2972
+            $share->setId($i);
2973
+            $shares[] = $share;
2974
+        }
2975 2975
 
2976
-		$today = new \DateTime();
2977
-		$today->setTime(0, 0, 0);
2976
+        $today = new \DateTime();
2977
+        $today->setTime(0, 0, 0);
2978 2978
 
2979
-		/*
2979
+        /*
2980 2980
 		 * Set the expiration date to today for some shares
2981 2981
 		 */
2982
-		$shares[2]->setExpirationDate($today);
2983
-		$shares[3]->setExpirationDate($today);
2984
-		$shares[4]->setExpirationDate($today);
2985
-		$shares[5]->setExpirationDate($today);
2982
+        $shares[2]->setExpirationDate($today);
2983
+        $shares[3]->setExpirationDate($today);
2984
+        $shares[4]->setExpirationDate($today);
2985
+        $shares[5]->setExpirationDate($today);
2986 2986
 
2987
-		/** @var \OCP\Share\IShare[] $i */
2988
-		$shares2 = [];
2989
-		for ($i = 0; $i < 8; $i++) {
2990
-			$shares2[] = clone $shares[$i];
2991
-		}
2987
+        /** @var \OCP\Share\IShare[] $i */
2988
+        $shares2 = [];
2989
+        for ($i = 0; $i < 8; $i++) {
2990
+            $shares2[] = clone $shares[$i];
2991
+        }
2992 2992
 
2993
-		$node = $this->createMock(File::class);
2993
+        $node = $this->createMock(File::class);
2994 2994
 
2995
-		/*
2995
+        /*
2996 2996
 		 * Simulate the getSharesBy call.
2997 2997
 		 */
2998
-		$this->defaultProvider
2999
-			->method('getSharesBy')
3000
-			->willReturnCallback(function ($uid, $type, $node, $reshares, $limit, $offset) use (&$shares2) {
3001
-				return array_slice($shares2, $offset, $limit);
3002
-			});
2998
+        $this->defaultProvider
2999
+            ->method('getSharesBy')
3000
+            ->willReturnCallback(function ($uid, $type, $node, $reshares, $limit, $offset) use (&$shares2) {
3001
+                return array_slice($shares2, $offset, $limit);
3002
+            });
3003 3003
 
3004
-		/*
3004
+        /*
3005 3005
 		 * Simulate the deleteShare call.
3006 3006
 		 */
3007
-		$manager->method('deleteShare')
3008
-			->willReturnCallback(function ($share) use (&$shares2) {
3009
-				for ($i = 0; $i < count($shares2); $i++) {
3010
-					if ($shares2[$i]->getId() === $share->getId()) {
3011
-						array_splice($shares2, $i, 1);
3012
-						break;
3013
-					}
3014
-				}
3015
-			});
3016
-
3017
-		$res = $manager->getSharesBy('user', IShare::TYPE_LINK, $node, true, 3, 0);
3018
-
3019
-		$this->assertCount(3, $res);
3020
-		$this->assertEquals($shares[0]->getId(), $res[0]->getId());
3021
-		$this->assertEquals($shares[1]->getId(), $res[1]->getId());
3022
-		$this->assertEquals($shares[6]->getId(), $res[2]->getId());
3023
-
3024
-		$this->assertCount(4, $shares2);
3025
-		$this->assertEquals(0, $shares2[0]->getId());
3026
-		$this->assertEquals(1, $shares2[1]->getId());
3027
-		$this->assertEquals(6, $shares2[2]->getId());
3028
-		$this->assertEquals(7, $shares2[3]->getId());
3029
-		$this->assertSame($today, $shares[3]->getExpirationDate());
3030
-	}
3031
-
3032
-	public function testGetShareByToken(): void {
3033
-		$this->config
3034
-			->expects($this->exactly(2))
3035
-			->method('getAppValue')
3036
-			->willReturnMap([
3037
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
3038
-				['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3039
-			]);
3040
-
3041
-		$factory = $this->createMock(IProviderFactory::class);
3042
-
3043
-		$manager = $this->createManager($factory);
3044
-
3045
-		$share = $this->createMock(IShare::class);
3046
-
3047
-		$factory->expects($this->once())
3048
-			->method('getProviderForType')
3049
-			->with(IShare::TYPE_LINK)
3050
-			->willReturn($this->defaultProvider);
3051
-
3052
-		$this->defaultProvider->expects($this->once())
3053
-			->method('getShareByToken')
3054
-			->with('token')
3055
-			->willReturn($share);
3056
-
3057
-		$ret = $manager->getShareByToken('token');
3058
-		$this->assertSame($share, $ret);
3059
-	}
3060
-
3061
-	public function testGetShareByTokenRoom(): void {
3062
-		$this->config
3063
-			->expects($this->exactly(2))
3064
-			->method('getAppValue')
3065
-			->willReturnMap([
3066
-				['core', 'shareapi_allow_links', 'yes', 'no'],
3067
-				['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3068
-			]);
3069
-
3070
-		$factory = $this->createMock(IProviderFactory::class);
3071
-
3072
-		$manager = $this->createManager($factory);
3073
-
3074
-		$share = $this->createMock(IShare::class);
3075
-
3076
-		$roomShareProvider = $this->createMock(IShareProvider::class);
3077
-
3078
-		$factory->expects($this->any())
3079
-			->method('getProviderForType')
3080
-			->willReturnCallback(function ($shareType) use ($roomShareProvider) {
3081
-				if ($shareType !== IShare::TYPE_ROOM) {
3082
-					throw new Exception\ProviderException();
3083
-				}
3084
-
3085
-				return $roomShareProvider;
3086
-			});
3087
-
3088
-		$roomShareProvider->expects($this->once())
3089
-			->method('getShareByToken')
3090
-			->with('token')
3091
-			->willReturn($share);
3092
-
3093
-		$ret = $manager->getShareByToken('token');
3094
-		$this->assertSame($share, $ret);
3095
-	}
3096
-
3097
-	public function testGetShareByTokenWithException(): void {
3098
-		$this->config
3099
-			->expects($this->exactly(2))
3100
-			->method('getAppValue')
3101
-			->willReturnMap([
3102
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
3103
-				['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3104
-			]);
3105
-
3106
-		$factory = $this->createMock(IProviderFactory::class);
3107
-
3108
-		$manager = $this->createManager($factory);
3109
-
3110
-		$share = $this->createMock(IShare::class);
3111
-
3112
-		$factory->expects($this->exactly(2))
3113
-			->method('getProviderForType')
3114
-			->withConsecutive(
3115
-				[IShare::TYPE_LINK],
3116
-				[IShare::TYPE_REMOTE]
3117
-			)
3118
-			->willReturn($this->defaultProvider);
3119
-
3120
-		$this->defaultProvider->expects($this->exactly(2))
3121
-			->method('getShareByToken')
3122
-			->with('token')
3123
-			->willReturnOnConsecutiveCalls(
3124
-				$this->throwException(new ShareNotFound()),
3125
-				$share
3126
-			);
3127
-
3128
-		$ret = $manager->getShareByToken('token');
3129
-		$this->assertSame($share, $ret);
3130
-	}
3131
-
3132
-
3133
-	public function testGetShareByTokenHideDisabledUser(): void {
3134
-		$this->expectException(\OCP\Share\Exceptions\ShareNotFound::class);
3135
-		$this->expectExceptionMessage('The requested share comes from a disabled user');
3136
-
3137
-		$this->config
3138
-			->expects($this->exactly(2))
3139
-			->method('getAppValue')
3140
-			->willReturnMap([
3141
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
3142
-				['files_sharing', 'hide_disabled_user_shares', 'no', 'yes'],
3143
-			]);
3144
-
3145
-		$this->l->expects($this->once())
3146
-			->method('t')
3147
-			->willReturnArgument(0);
3148
-
3149
-		$manager = $this->createManagerMock()
3150
-			->setMethods(['deleteShare'])
3151
-			->getMock();
3152
-
3153
-		$date = new \DateTime();
3154
-		$date->setTime(0, 0, 0);
3155
-		$date->add(new \DateInterval('P2D'));
3156
-		$share = $this->manager->newShare();
3157
-		$share->setExpirationDate($date);
3158
-		$share->setShareOwner('owner');
3159
-		$share->setSharedBy('sharedBy');
3160
-
3161
-		$sharedBy = $this->createMock(IUser::class);
3162
-		$owner = $this->createMock(IUser::class);
3163
-
3164
-		$this->userManager->method('get')->willReturnMap([
3165
-			['sharedBy', $sharedBy],
3166
-			['owner', $owner],
3167
-		]);
3168
-
3169
-		$owner->expects($this->once())
3170
-			->method('isEnabled')
3171
-			->willReturn(true);
3172
-		$sharedBy->expects($this->once())
3173
-			->method('isEnabled')
3174
-			->willReturn(false);
3175
-
3176
-		$this->defaultProvider->expects($this->once())
3177
-			->method('getShareByToken')
3178
-			->with('expiredToken')
3179
-			->willReturn($share);
3180
-
3181
-		$manager->expects($this->never())
3182
-			->method('deleteShare');
3183
-
3184
-		$manager->getShareByToken('expiredToken');
3185
-	}
3186
-
3187
-
3188
-	public function testGetShareByTokenExpired(): void {
3189
-		$this->expectException(\OCP\Share\Exceptions\ShareNotFound::class);
3190
-		$this->expectExceptionMessage('The requested share does not exist anymore');
3191
-
3192
-		$this->config
3193
-			->expects($this->once())
3194
-			->method('getAppValue')
3195
-			->with('core', 'shareapi_allow_links', 'yes')
3196
-			->willReturn('yes');
3197
-
3198
-		$this->l->expects($this->once())
3199
-			->method('t')
3200
-			->willReturnArgument(0);
3201
-
3202
-		$manager = $this->createManagerMock()
3203
-			->setMethods(['deleteShare'])
3204
-			->getMock();
3205
-
3206
-		$date = new \DateTime();
3207
-		$date->setTime(0, 0, 0);
3208
-		$share = $this->manager->newShare();
3209
-		$share->setExpirationDate($date);
3210
-
3211
-		$this->defaultProvider->expects($this->once())
3212
-			->method('getShareByToken')
3213
-			->with('expiredToken')
3214
-			->willReturn($share);
3215
-
3216
-		$manager->expects($this->once())
3217
-			->method('deleteShare')
3218
-			->with($this->equalTo($share));
3219
-
3220
-		$manager->getShareByToken('expiredToken');
3221
-	}
3222
-
3223
-	public function testGetShareByTokenNotExpired(): void {
3224
-		$this->config
3225
-			->expects($this->exactly(2))
3226
-			->method('getAppValue')
3227
-			->willReturnMap([
3228
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
3229
-				['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3230
-			]);
3231
-
3232
-		$date = new \DateTime();
3233
-		$date->setTime(0, 0, 0);
3234
-		$date->add(new \DateInterval('P2D'));
3235
-		$share = $this->manager->newShare();
3236
-		$share->setExpirationDate($date);
3237
-
3238
-		$this->defaultProvider->expects($this->once())
3239
-			->method('getShareByToken')
3240
-			->with('expiredToken')
3241
-			->willReturn($share);
3242
-
3243
-		$res = $this->manager->getShareByToken('expiredToken');
3244
-
3245
-		$this->assertSame($share, $res);
3246
-	}
3247
-
3248
-
3249
-	public function testGetShareByTokenWithPublicLinksDisabled(): void {
3250
-		$this->expectException(\OCP\Share\Exceptions\ShareNotFound::class);
3251
-
3252
-		$this->config
3253
-			->expects($this->once())
3254
-			->method('getAppValue')
3255
-			->with('core', 'shareapi_allow_links', 'yes')
3256
-			->willReturn('no');
3257
-		$this->manager->getShareByToken('validToken');
3258
-	}
3259
-
3260
-	public function testGetShareByTokenPublicUploadDisabled(): void {
3261
-		$this->config
3262
-			->expects($this->exactly(3))
3263
-			->method('getAppValue')
3264
-			->willReturnMap([
3265
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
3266
-				['core', 'shareapi_allow_public_upload', 'yes', 'no'],
3267
-				['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3268
-			]);
3269
-
3270
-		$share = $this->manager->newShare();
3271
-		$share->setShareType(IShare::TYPE_LINK)
3272
-			->setPermissions(\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE);
3273
-		$share->setSharedWith('sharedWith');
3274
-		$folder = $this->createMock(\OC\Files\Node\Folder::class);
3275
-		$share->setNode($folder);
3276
-
3277
-		$this->defaultProvider->expects($this->once())
3278
-			->method('getShareByToken')
3279
-			->willReturn('validToken')
3280
-			->willReturn($share);
3281
-
3282
-		$res = $this->manager->getShareByToken('validToken');
3283
-
3284
-		$this->assertSame(\OCP\Constants::PERMISSION_READ, $res->getPermissions());
3285
-	}
3286
-
3287
-	public function testCheckPasswordNoLinkShare(): void {
3288
-		$share = $this->createMock(IShare::class);
3289
-		$share->method('getShareType')->willReturn(IShare::TYPE_USER);
3290
-		$this->assertFalse($this->manager->checkPassword($share, 'password'));
3291
-	}
3292
-
3293
-	public function testCheckPasswordNoPassword(): void {
3294
-		$share = $this->createMock(IShare::class);
3295
-		$share->method('getShareType')->willReturn(IShare::TYPE_LINK);
3296
-		$this->assertFalse($this->manager->checkPassword($share, 'password'));
3297
-
3298
-		$share->method('getPassword')->willReturn('password');
3299
-		$this->assertFalse($this->manager->checkPassword($share, null));
3300
-	}
3301
-
3302
-	public function testCheckPasswordInvalidPassword(): void {
3303
-		$share = $this->createMock(IShare::class);
3304
-		$share->method('getShareType')->willReturn(IShare::TYPE_LINK);
3305
-		$share->method('getPassword')->willReturn('password');
3306
-
3307
-		$this->hasher->method('verify')->with('invalidpassword', 'password', '')->willReturn(false);
3308
-
3309
-		$this->assertFalse($this->manager->checkPassword($share, 'invalidpassword'));
3310
-	}
3311
-
3312
-	public function testCheckPasswordValidPassword(): void {
3313
-		$share = $this->createMock(IShare::class);
3314
-		$share->method('getShareType')->willReturn(IShare::TYPE_LINK);
3315
-		$share->method('getPassword')->willReturn('passwordHash');
3316
-
3317
-		$this->hasher->method('verify')->with('password', 'passwordHash', '')->willReturn(true);
3318
-
3319
-		$this->assertTrue($this->manager->checkPassword($share, 'password'));
3320
-	}
3321
-
3322
-	public function testCheckPasswordUpdateShare(): void {
3323
-		$share = $this->manager->newShare();
3324
-		$share->setShareType(IShare::TYPE_LINK)
3325
-			->setPassword('passwordHash');
3326
-
3327
-		$this->hasher->method('verify')->with('password', 'passwordHash', '')
3328
-			->willReturnCallback(function ($pass, $hash, &$newHash) {
3329
-				$newHash = 'newHash';
3330
-
3331
-				return true;
3332
-			});
3333
-
3334
-		$this->defaultProvider->expects($this->once())
3335
-			->method('update')
3336
-			->with($this->callback(function (\OCP\Share\IShare $share) {
3337
-				return $share->getPassword() === 'newHash';
3338
-			}));
3339
-
3340
-		$this->assertTrue($this->manager->checkPassword($share, 'password'));
3341
-	}
3342
-
3343
-
3344
-	public function testUpdateShareCantChangeShareType(): void {
3345
-		$this->expectException(\Exception::class);
3346
-		$this->expectExceptionMessage('Cannot change share type');
3347
-
3348
-		$manager = $this->createManagerMock()
3349
-			->setMethods([
3350
-				'canShare',
3351
-				'getShareById'
3352
-			])
3353
-			->getMock();
3354
-
3355
-		$originalShare = $this->manager->newShare();
3356
-		$originalShare->setShareType(IShare::TYPE_GROUP);
3357
-
3358
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3359
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3360
-
3361
-		$share = $this->manager->newShare();
3362
-		$attrs = $this->manager->newShare()->newAttributes();
3363
-		$attrs->setAttribute('app1', 'perm1', true);
3364
-		$share->setProviderId('foo')
3365
-			->setId('42')
3366
-			->setShareType(IShare::TYPE_USER);
3367
-
3368
-		$manager->updateShare($share);
3369
-	}
3370
-
3371
-
3372
-	public function testUpdateShareCantChangeRecipientForGroupShare(): void {
3373
-		$this->expectException(\Exception::class);
3374
-		$this->expectExceptionMessage('Can only update recipient on user shares');
3375
-
3376
-		$manager = $this->createManagerMock()
3377
-			->setMethods([
3378
-				'canShare',
3379
-				'getShareById'
3380
-			])
3381
-			->getMock();
3382
-
3383
-		$originalShare = $this->manager->newShare();
3384
-		$originalShare->setShareType(IShare::TYPE_GROUP)
3385
-			->setSharedWith('origGroup');
3386
-
3387
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3388
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3389
-
3390
-		$share = $this->manager->newShare();
3391
-		$share->setProviderId('foo')
3392
-			->setId('42')
3393
-			->setShareType(IShare::TYPE_GROUP)
3394
-			->setSharedWith('newGroup');
3395
-
3396
-		$manager->updateShare($share);
3397
-	}
3398
-
3399
-
3400
-	public function testUpdateShareCantShareWithOwner(): void {
3401
-		$this->expectException(\Exception::class);
3402
-		$this->expectExceptionMessage('Cannot share with the share owner');
3403
-
3404
-		$manager = $this->createManagerMock()
3405
-			->setMethods([
3406
-				'canShare',
3407
-				'getShareById'
3408
-			])
3409
-			->getMock();
3410
-
3411
-		$originalShare = $this->manager->newShare();
3412
-		$originalShare->setShareType(IShare::TYPE_USER)
3413
-			->setSharedWith('sharedWith');
3414
-
3415
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3416
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3417
-
3418
-		$share = $this->manager->newShare();
3419
-		$share->setProviderId('foo')
3420
-			->setId('42')
3421
-			->setShareType(IShare::TYPE_USER)
3422
-			->setSharedWith('newUser')
3423
-			->setShareOwner('newUser');
3424
-
3425
-		$manager->updateShare($share);
3426
-	}
3427
-
3428
-	public function testUpdateShareUser(): void {
3429
-		$this->userManager->expects($this->any())->method('userExists')->willReturn(true);
3430
-
3431
-		$manager = $this->createManagerMock()
3432
-			->setMethods([
3433
-				'canShare',
3434
-				'getShareById',
3435
-				'generalCreateChecks',
3436
-				'userCreateChecks',
3437
-				'pathCreateChecks',
3438
-			])
3439
-			->getMock();
3440
-
3441
-		$originalShare = $this->manager->newShare();
3442
-		$originalShare->setShareType(IShare::TYPE_USER)
3443
-			->setSharedWith('origUser')
3444
-			->setPermissions(1);
3445
-
3446
-		$node = $this->createMock(File::class);
3447
-		$node->method('getId')->willReturn(100);
3448
-		$node->method('getPath')->willReturn('/newUser/files/myPath');
3449
-
3450
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3451
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3452
-
3453
-		$share = $this->manager->newShare();
3454
-		$attrs = $this->manager->newShare()->newAttributes();
3455
-		$attrs->setAttribute('app1', 'perm1', true);
3456
-		$share->setProviderId('foo')
3457
-			->setId('42')
3458
-			->setShareType(IShare::TYPE_USER)
3459
-			->setSharedWith('origUser')
3460
-			->setShareOwner('newUser')
3461
-			->setSharedBy('sharer')
3462
-			->setPermissions(31)
3463
-			->setAttributes($attrs)
3464
-			->setNode($node);
3465
-
3466
-		$this->defaultProvider->expects($this->once())
3467
-			->method('update')
3468
-			->with($share)
3469
-			->willReturn($share);
3470
-
3471
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3472
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3473
-		$hookListener->expects($this->never())->method('post');
3474
-
3475
-		$this->rootFolder->method('getUserFolder')->with('newUser')->willReturnSelf();
3476
-		$this->rootFolder->method('getRelativePath')->with('/newUser/files/myPath')->willReturn('/myPath');
3477
-
3478
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3479
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener2, 'post');
3480
-		$hookListener2->expects($this->once())->method('post')->with([
3481
-			'itemType' => 'file',
3482
-			'itemSource' => 100,
3483
-			'shareType' => IShare::TYPE_USER,
3484
-			'shareWith' => 'origUser',
3485
-			'uidOwner' => 'sharer',
3486
-			'permissions' => 31,
3487
-			'path' => '/myPath',
3488
-			'attributes' => $attrs->toArray(),
3489
-		]);
3490
-
3491
-		$manager->updateShare($share);
3492
-	}
3493
-
3494
-	public function testUpdateShareGroup(): void {
3495
-		$manager = $this->createManagerMock()
3496
-			->setMethods([
3497
-				'canShare',
3498
-				'getShareById',
3499
-				'generalCreateChecks',
3500
-				'groupCreateChecks',
3501
-				'pathCreateChecks',
3502
-			])
3503
-			->getMock();
3504
-
3505
-		$originalShare = $this->manager->newShare();
3506
-		$originalShare->setShareType(IShare::TYPE_GROUP)
3507
-			->setSharedWith('origUser')
3508
-			->setPermissions(31);
3509
-
3510
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3511
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3512
-
3513
-		$node = $this->createMock(File::class);
3514
-
3515
-		$share = $this->manager->newShare();
3516
-		$share->setProviderId('foo')
3517
-			->setId('42')
3518
-			->setShareType(IShare::TYPE_GROUP)
3519
-			->setSharedWith('origUser')
3520
-			->setShareOwner('owner')
3521
-			->setNode($node)
3522
-			->setPermissions(31);
3523
-
3524
-		$this->defaultProvider->expects($this->once())
3525
-			->method('update')
3526
-			->with($share)
3527
-			->willReturn($share);
3528
-
3529
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3530
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3531
-		$hookListener->expects($this->never())->method('post');
3532
-
3533
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3534
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener2, 'post');
3535
-		$hookListener2->expects($this->never())->method('post');
3536
-
3537
-		$manager->updateShare($share);
3538
-	}
3539
-
3540
-	public function testUpdateShareLink(): void {
3541
-		$manager = $this->createManagerMock()
3542
-			->setMethods([
3543
-				'canShare',
3544
-				'getShareById',
3545
-				'generalCreateChecks',
3546
-				'linkCreateChecks',
3547
-				'pathCreateChecks',
3548
-				'verifyPassword',
3549
-				'validateExpirationDateLink',
3550
-			])
3551
-			->getMock();
3552
-
3553
-		$originalShare = $this->manager->newShare();
3554
-		$originalShare->setShareType(IShare::TYPE_LINK)
3555
-			->setPermissions(15);
3556
-
3557
-		$tomorrow = new \DateTime();
3558
-		$tomorrow->setTime(0, 0, 0);
3559
-		$tomorrow->add(new \DateInterval('P1D'));
3560
-
3561
-		$file = $this->createMock(File::class);
3562
-		$file->method('getId')->willReturn(100);
3563
-
3564
-		$share = $this->manager->newShare();
3565
-		$share->setProviderId('foo')
3566
-			->setId('42')
3567
-			->setShareType(IShare::TYPE_LINK)
3568
-			->setToken('token')
3569
-			->setSharedBy('owner')
3570
-			->setShareOwner('owner')
3571
-			->setPassword('password')
3572
-			->setExpirationDate($tomorrow)
3573
-			->setNode($file)
3574
-			->setPermissions(15);
3575
-
3576
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3577
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3578
-		$manager->expects($this->once())->method('validateExpirationDateLink')->with($share);
3579
-		$manager->expects($this->once())->method('verifyPassword')->with('password');
3580
-
3581
-		$this->hasher->expects($this->once())
3582
-			->method('hash')
3583
-			->with('password')
3584
-			->willReturn('hashed');
3585
-
3586
-		$this->defaultProvider->expects($this->once())
3587
-			->method('update')
3588
-			->with($share)
3589
-			->willReturn($share);
3590
-
3591
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3592
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3593
-		$hookListener->expects($this->once())->method('post')->with([
3594
-			'itemType' => 'file',
3595
-			'itemSource' => 100,
3596
-			'date' => $tomorrow,
3597
-			'uidOwner' => 'owner',
3598
-		]);
3599
-
3600
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3601
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3602
-		$hookListener2->expects($this->once())->method('post')->with([
3603
-			'itemType' => 'file',
3604
-			'itemSource' => 100,
3605
-			'uidOwner' => 'owner',
3606
-			'token' => 'token',
3607
-			'disabled' => false,
3608
-		]);
3609
-
3610
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3611
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3612
-		$hookListener3->expects($this->never())->method('post');
3613
-
3614
-
3615
-		$manager->updateShare($share);
3616
-	}
3617
-
3618
-	public function testUpdateShareLinkEnableSendPasswordByTalkWithNoPassword(): void {
3619
-		$this->expectException(\InvalidArgumentException::class);
3620
-		$this->expectExceptionMessage('Cannot enable sending the password by Talk with an empty password');
3621
-
3622
-		$manager = $this->createManagerMock()
3623
-			->setMethods([
3624
-				'canShare',
3625
-				'getShareById',
3626
-				'generalCreateChecks',
3627
-				'linkCreateChecks',
3628
-				'pathCreateChecks',
3629
-				'verifyPassword',
3630
-				'validateExpirationDateLink',
3631
-			])
3632
-			->getMock();
3633
-
3634
-		$originalShare = $this->manager->newShare();
3635
-		$originalShare->setShareType(IShare::TYPE_LINK)
3636
-			->setPermissions(15);
3637
-
3638
-		$tomorrow = new \DateTime();
3639
-		$tomorrow->setTime(0, 0, 0);
3640
-		$tomorrow->add(new \DateInterval('P1D'));
3641
-
3642
-		$file = $this->createMock(File::class);
3643
-		$file->method('getId')->willReturn(100);
3644
-
3645
-		$share = $this->manager->newShare();
3646
-		$share->setProviderId('foo')
3647
-			->setId('42')
3648
-			->setShareType(IShare::TYPE_LINK)
3649
-			->setToken('token')
3650
-			->setSharedBy('owner')
3651
-			->setShareOwner('owner')
3652
-			->setPassword(null)
3653
-			->setSendPasswordByTalk(true)
3654
-			->setExpirationDate($tomorrow)
3655
-			->setNode($file)
3656
-			->setPermissions(15);
3657
-
3658
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3659
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3660
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
3661
-		$manager->expects($this->once())->method('linkCreateChecks')->with($share);
3662
-		$manager->expects($this->never())->method('verifyPassword');
3663
-		$manager->expects($this->never())->method('pathCreateChecks');
3664
-		$manager->expects($this->never())->method('validateExpirationDateLink');
3665
-
3666
-		$this->hasher->expects($this->never())
3667
-			->method('hash');
3668
-
3669
-		$this->defaultProvider->expects($this->never())
3670
-			->method('update');
3671
-
3672
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3673
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3674
-		$hookListener->expects($this->never())->method('post');
3675
-
3676
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3677
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3678
-		$hookListener2->expects($this->never())->method('post');
3679
-
3680
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3681
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3682
-		$hookListener3->expects($this->never())->method('post');
3683
-
3684
-		$manager->updateShare($share);
3685
-	}
3686
-
3687
-	public function testUpdateShareMail(): void {
3688
-		$manager = $this->createManagerMock()
3689
-			->setMethods([
3690
-				'canShare',
3691
-				'getShareById',
3692
-				'generalCreateChecks',
3693
-				'verifyPassword',
3694
-				'pathCreateChecks',
3695
-				'linkCreateChecks',
3696
-				'validateExpirationDateLink',
3697
-			])
3698
-			->getMock();
3699
-
3700
-		$originalShare = $this->manager->newShare();
3701
-		$originalShare->setShareType(IShare::TYPE_EMAIL)
3702
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
3703
-
3704
-		$tomorrow = new \DateTime();
3705
-		$tomorrow->setTime(0, 0, 0);
3706
-		$tomorrow->add(new \DateInterval('P1D'));
3707
-
3708
-		$file = $this->createMock(File::class);
3709
-		$file->method('getId')->willReturn(100);
3710
-
3711
-		$share = $this->manager->newShare();
3712
-		$share->setProviderId('foo')
3713
-			->setId('42')
3714
-			->setShareType(IShare::TYPE_EMAIL)
3715
-			->setToken('token')
3716
-			->setSharedBy('owner')
3717
-			->setShareOwner('owner')
3718
-			->setPassword('password')
3719
-			->setExpirationDate($tomorrow)
3720
-			->setNode($file)
3721
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
3722
-
3723
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3724
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3725
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
3726
-		$manager->expects($this->once())->method('verifyPassword')->with('password');
3727
-		$manager->expects($this->once())->method('pathCreateChecks')->with($file);
3728
-		$manager->expects($this->once())->method('linkCreateChecks');
3729
-		$manager->expects($this->once())->method('validateExpirationDateLink');
3730
-
3731
-		$this->hasher->expects($this->once())
3732
-			->method('hash')
3733
-			->with('password')
3734
-			->willReturn('hashed');
3735
-
3736
-		$this->defaultProvider->expects($this->once())
3737
-			->method('update')
3738
-			->with($share, 'password')
3739
-			->willReturn($share);
3740
-
3741
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3742
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3743
-		$hookListener->expects($this->once())->method('post')->with([
3744
-			'itemType' => 'file',
3745
-			'itemSource' => 100,
3746
-			'date' => $tomorrow,
3747
-			'uidOwner' => 'owner',
3748
-		]);
3749
-
3750
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3751
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3752
-		$hookListener2->expects($this->once())->method('post')->with([
3753
-			'itemType' => 'file',
3754
-			'itemSource' => 100,
3755
-			'uidOwner' => 'owner',
3756
-			'token' => 'token',
3757
-			'disabled' => false,
3758
-		]);
3759
-
3760
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3761
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3762
-		$hookListener3->expects($this->never())->method('post');
3763
-
3764
-		$manager->updateShare($share);
3765
-	}
3766
-
3767
-	public function testUpdateShareMailEnableSendPasswordByTalk(): void {
3768
-		$manager = $this->createManagerMock()
3769
-			->setMethods([
3770
-				'canShare',
3771
-				'getShareById',
3772
-				'generalCreateChecks',
3773
-				'verifyPassword',
3774
-				'pathCreateChecks',
3775
-				'linkCreateChecks',
3776
-				'validateExpirationDateLink',
3777
-			])
3778
-			->getMock();
3779
-
3780
-		$originalShare = $this->manager->newShare();
3781
-		$originalShare->setShareType(IShare::TYPE_EMAIL)
3782
-			->setPermissions(\OCP\Constants::PERMISSION_ALL)
3783
-			->setPassword(null)
3784
-			->setSendPasswordByTalk(false);
3785
-
3786
-		$tomorrow = new \DateTime();
3787
-		$tomorrow->setTime(0, 0, 0);
3788
-		$tomorrow->add(new \DateInterval('P1D'));
3789
-
3790
-		$file = $this->createMock(File::class);
3791
-		$file->method('getId')->willReturn(100);
3792
-
3793
-		$share = $this->manager->newShare();
3794
-		$share->setProviderId('foo')
3795
-			->setId('42')
3796
-			->setShareType(IShare::TYPE_EMAIL)
3797
-			->setToken('token')
3798
-			->setSharedBy('owner')
3799
-			->setShareOwner('owner')
3800
-			->setPassword('password')
3801
-			->setSendPasswordByTalk(true)
3802
-			->setExpirationDate($tomorrow)
3803
-			->setNode($file)
3804
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
3805
-
3806
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3807
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3808
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
3809
-		$manager->expects($this->once())->method('verifyPassword')->with('password');
3810
-		$manager->expects($this->once())->method('pathCreateChecks')->with($file);
3811
-		$manager->expects($this->once())->method('linkCreateChecks');
3812
-		$manager->expects($this->once())->method('validateExpirationDateLink');
3813
-
3814
-		$this->hasher->expects($this->once())
3815
-			->method('hash')
3816
-			->with('password')
3817
-			->willReturn('hashed');
3818
-
3819
-		$this->defaultProvider->expects($this->once())
3820
-			->method('update')
3821
-			->with($share, 'password')
3822
-			->willReturn($share);
3823
-
3824
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3825
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3826
-		$hookListener->expects($this->once())->method('post')->with([
3827
-			'itemType' => 'file',
3828
-			'itemSource' => 100,
3829
-			'date' => $tomorrow,
3830
-			'uidOwner' => 'owner',
3831
-		]);
3832
-
3833
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3834
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3835
-		$hookListener2->expects($this->once())->method('post')->with([
3836
-			'itemType' => 'file',
3837
-			'itemSource' => 100,
3838
-			'uidOwner' => 'owner',
3839
-			'token' => 'token',
3840
-			'disabled' => false,
3841
-		]);
3842
-
3843
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3844
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3845
-		$hookListener3->expects($this->never())->method('post');
3846
-
3847
-		$manager->updateShare($share);
3848
-	}
3849
-
3850
-	public function testUpdateShareMailEnableSendPasswordByTalkWithDifferentPassword(): void {
3851
-		$manager = $this->createManagerMock()
3852
-			->setMethods([
3853
-				'canShare',
3854
-				'getShareById',
3855
-				'generalCreateChecks',
3856
-				'verifyPassword',
3857
-				'pathCreateChecks',
3858
-				'linkCreateChecks',
3859
-				'validateExpirationDateLink',
3860
-			])
3861
-			->getMock();
3862
-
3863
-		$originalShare = $this->manager->newShare();
3864
-		$originalShare->setShareType(IShare::TYPE_EMAIL)
3865
-			->setPermissions(\OCP\Constants::PERMISSION_ALL)
3866
-			->setPassword('anotherPasswordHash')
3867
-			->setSendPasswordByTalk(false);
3868
-
3869
-		$tomorrow = new \DateTime();
3870
-		$tomorrow->setTime(0, 0, 0);
3871
-		$tomorrow->add(new \DateInterval('P1D'));
3872
-
3873
-		$file = $this->createMock(File::class);
3874
-		$file->method('getId')->willReturn(100);
3875
-
3876
-		$share = $this->manager->newShare();
3877
-		$share->setProviderId('foo')
3878
-			->setId('42')
3879
-			->setShareType(IShare::TYPE_EMAIL)
3880
-			->setToken('token')
3881
-			->setSharedBy('owner')
3882
-			->setShareOwner('owner')
3883
-			->setPassword('password')
3884
-			->setSendPasswordByTalk(true)
3885
-			->setExpirationDate($tomorrow)
3886
-			->setNode($file)
3887
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
3888
-
3889
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3890
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3891
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
3892
-		$manager->expects($this->once())->method('verifyPassword')->with('password');
3893
-		$manager->expects($this->once())->method('pathCreateChecks')->with($file);
3894
-		$manager->expects($this->once())->method('linkCreateChecks');
3895
-		$manager->expects($this->once())->method('validateExpirationDateLink');
3896
-
3897
-		$this->hasher->expects($this->once())
3898
-			->method('verify')
3899
-			->with('password', 'anotherPasswordHash')
3900
-			->willReturn(false);
3901
-
3902
-		$this->hasher->expects($this->once())
3903
-			->method('hash')
3904
-			->with('password')
3905
-			->willReturn('hashed');
3906
-
3907
-		$this->defaultProvider->expects($this->once())
3908
-			->method('update')
3909
-			->with($share, 'password')
3910
-			->willReturn($share);
3911
-
3912
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3913
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3914
-		$hookListener->expects($this->once())->method('post')->with([
3915
-			'itemType' => 'file',
3916
-			'itemSource' => 100,
3917
-			'date' => $tomorrow,
3918
-			'uidOwner' => 'owner',
3919
-		]);
3920
-
3921
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3922
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3923
-		$hookListener2->expects($this->once())->method('post')->with([
3924
-			'itemType' => 'file',
3925
-			'itemSource' => 100,
3926
-			'uidOwner' => 'owner',
3927
-			'token' => 'token',
3928
-			'disabled' => false,
3929
-		]);
3930
-
3931
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3932
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3933
-		$hookListener3->expects($this->never())->method('post');
3934
-
3935
-		$manager->updateShare($share);
3936
-	}
3937
-
3938
-	public function testUpdateShareMailEnableSendPasswordByTalkWithNoPassword(): void {
3939
-		$this->expectException(\InvalidArgumentException::class);
3940
-		$this->expectExceptionMessage('Cannot enable sending the password by Talk with an empty password');
3941
-
3942
-		$manager = $this->createManagerMock()
3943
-			->setMethods([
3944
-				'canShare',
3945
-				'getShareById',
3946
-				'generalCreateChecks',
3947
-				'verifyPassword',
3948
-				'pathCreateChecks',
3949
-				'linkCreateChecks',
3950
-				'validateExpirationDateLink',
3951
-			])
3952
-			->getMock();
3953
-
3954
-		$originalShare = $this->manager->newShare();
3955
-		$originalShare->setShareType(IShare::TYPE_EMAIL)
3956
-			->setPermissions(\OCP\Constants::PERMISSION_ALL)
3957
-			->setPassword(null)
3958
-			->setSendPasswordByTalk(false);
3959
-
3960
-		$tomorrow = new \DateTime();
3961
-		$tomorrow->setTime(0, 0, 0);
3962
-		$tomorrow->add(new \DateInterval('P1D'));
3963
-
3964
-		$file = $this->createMock(File::class);
3965
-		$file->method('getId')->willReturn(100);
3966
-
3967
-		$share = $this->manager->newShare();
3968
-		$share->setProviderId('foo')
3969
-			->setId('42')
3970
-			->setShareType(IShare::TYPE_EMAIL)
3971
-			->setToken('token')
3972
-			->setSharedBy('owner')
3973
-			->setShareOwner('owner')
3974
-			->setPassword(null)
3975
-			->setSendPasswordByTalk(true)
3976
-			->setExpirationDate($tomorrow)
3977
-			->setNode($file)
3978
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
3979
-
3980
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
3981
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3982
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
3983
-		$manager->expects($this->never())->method('verifyPassword');
3984
-		$manager->expects($this->never())->method('pathCreateChecks');
3985
-		$manager->expects($this->once())->method('linkCreateChecks');
3986
-		$manager->expects($this->never())->method('validateExpirationDateLink');
3987
-
3988
-		// If the password is empty, we have nothing to hash
3989
-		$this->hasher->expects($this->never())
3990
-			->method('hash');
3991
-
3992
-		$this->defaultProvider->expects($this->never())
3993
-			->method('update');
3994
-
3995
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3996
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3997
-		$hookListener->expects($this->never())->method('post');
3998
-
3999
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4000
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4001
-		$hookListener2->expects($this->never())->method('post');
4002
-
4003
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4004
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4005
-		$hookListener3->expects($this->never())->method('post');
4006
-
4007
-		$manager->updateShare($share);
4008
-	}
4009
-
4010
-
4011
-	public function testUpdateShareMailEnableSendPasswordByTalkRemovingPassword(): void {
4012
-		$this->expectException(\InvalidArgumentException::class);
4013
-		$this->expectExceptionMessage('Cannot enable sending the password by Talk with an empty password');
4014
-
4015
-		$manager = $this->createManagerMock()
4016
-			->setMethods([
4017
-				'canShare',
4018
-				'getShareById',
4019
-				'generalCreateChecks',
4020
-				'verifyPassword',
4021
-				'pathCreateChecks',
4022
-				'linkCreateChecks',
4023
-				'validateExpirationDateLink',
4024
-			])
4025
-			->getMock();
4026
-
4027
-		$originalShare = $this->manager->newShare();
4028
-		$originalShare->setShareType(IShare::TYPE_EMAIL)
4029
-			->setPermissions(\OCP\Constants::PERMISSION_ALL)
4030
-			->setPassword('passwordHash')
4031
-			->setSendPasswordByTalk(false);
4032
-
4033
-		$tomorrow = new \DateTime();
4034
-		$tomorrow->setTime(0, 0, 0);
4035
-		$tomorrow->add(new \DateInterval('P1D'));
4036
-
4037
-		$file = $this->createMock(File::class);
4038
-		$file->method('getId')->willReturn(100);
4039
-
4040
-		$share = $this->manager->newShare();
4041
-		$share->setProviderId('foo')
4042
-			->setId('42')
4043
-			->setShareType(IShare::TYPE_EMAIL)
4044
-			->setToken('token')
4045
-			->setSharedBy('owner')
4046
-			->setShareOwner('owner')
4047
-			->setPassword(null)
4048
-			->setSendPasswordByTalk(true)
4049
-			->setExpirationDate($tomorrow)
4050
-			->setNode($file)
4051
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
4052
-
4053
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
4054
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4055
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
4056
-		$manager->expects($this->once())->method('verifyPassword');
4057
-		$manager->expects($this->never())->method('pathCreateChecks');
4058
-		$manager->expects($this->once())->method('linkCreateChecks');
4059
-		$manager->expects($this->never())->method('validateExpirationDateLink');
4060
-
4061
-		// If the password is empty, we have nothing to hash
4062
-		$this->hasher->expects($this->never())
4063
-			->method('hash');
4064
-
4065
-		$this->defaultProvider->expects($this->never())
4066
-			->method('update');
4067
-
4068
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4069
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4070
-		$hookListener->expects($this->never())->method('post');
4071
-
4072
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4073
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4074
-		$hookListener2->expects($this->never())->method('post');
4075
-
4076
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4077
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4078
-		$hookListener3->expects($this->never())->method('post');
4079
-
4080
-		$manager->updateShare($share);
4081
-	}
4082
-
4083
-
4084
-	public function testUpdateShareMailEnableSendPasswordByTalkRemovingPasswordWithEmptyString(): void {
4085
-		$this->expectException(\InvalidArgumentException::class);
4086
-		$this->expectExceptionMessage('Cannot enable sending the password by Talk with an empty password');
4087
-
4088
-		$manager = $this->createManagerMock()
4089
-			->setMethods([
4090
-				'canShare',
4091
-				'getShareById',
4092
-				'generalCreateChecks',
4093
-				'verifyPassword',
4094
-				'pathCreateChecks',
4095
-				'linkCreateChecks',
4096
-				'validateExpirationDateLink',
4097
-			])
4098
-			->getMock();
4099
-
4100
-		$originalShare = $this->manager->newShare();
4101
-		$originalShare->setShareType(IShare::TYPE_EMAIL)
4102
-			->setPermissions(\OCP\Constants::PERMISSION_ALL)
4103
-			->setPassword('passwordHash')
4104
-			->setSendPasswordByTalk(false);
4105
-
4106
-		$tomorrow = new \DateTime();
4107
-		$tomorrow->setTime(0, 0, 0);
4108
-		$tomorrow->add(new \DateInterval('P1D'));
4109
-
4110
-		$file = $this->createMock(File::class);
4111
-		$file->method('getId')->willReturn(100);
4112
-
4113
-		$share = $this->manager->newShare();
4114
-		$share->setProviderId('foo')
4115
-			->setId('42')
4116
-			->setShareType(IShare::TYPE_EMAIL)
4117
-			->setToken('token')
4118
-			->setSharedBy('owner')
4119
-			->setShareOwner('owner')
4120
-			->setPassword('')
4121
-			->setSendPasswordByTalk(true)
4122
-			->setExpirationDate($tomorrow)
4123
-			->setNode($file)
4124
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
4125
-
4126
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
4127
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4128
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
4129
-		$manager->expects($this->once())->method('verifyPassword');
4130
-		$manager->expects($this->never())->method('pathCreateChecks');
4131
-		$manager->expects($this->once())->method('linkCreateChecks');
4132
-		$manager->expects($this->never())->method('validateExpirationDateLink');
4133
-
4134
-		// If the password is empty, we have nothing to hash
4135
-		$this->hasher->expects($this->never())
4136
-			->method('hash');
4137
-
4138
-		$this->defaultProvider->expects($this->never())
4139
-			->method('update');
4140
-
4141
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4142
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4143
-		$hookListener->expects($this->never())->method('post');
4144
-
4145
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4146
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4147
-		$hookListener2->expects($this->never())->method('post');
4148
-
4149
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4150
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4151
-		$hookListener3->expects($this->never())->method('post');
4152
-
4153
-		$manager->updateShare($share);
4154
-	}
4155
-
4156
-
4157
-	public function testUpdateShareMailEnableSendPasswordByTalkWithPreviousPassword(): void {
4158
-		$this->expectException(\InvalidArgumentException::class);
4159
-		$this->expectExceptionMessage('Cannot enable sending the password by Talk without setting a new password');
4160
-
4161
-		$manager = $this->createManagerMock()
4162
-			->setMethods([
4163
-				'canShare',
4164
-				'getShareById',
4165
-				'generalCreateChecks',
4166
-				'verifyPassword',
4167
-				'pathCreateChecks',
4168
-				'linkCreateChecks',
4169
-				'validateExpirationDateLink',
4170
-			])
4171
-			->getMock();
4172
-
4173
-		$originalShare = $this->manager->newShare();
4174
-		$originalShare->setShareType(IShare::TYPE_EMAIL)
4175
-			->setPermissions(\OCP\Constants::PERMISSION_ALL)
4176
-			->setPassword('password')
4177
-			->setSendPasswordByTalk(false);
4178
-
4179
-		$tomorrow = new \DateTime();
4180
-		$tomorrow->setTime(0, 0, 0);
4181
-		$tomorrow->add(new \DateInterval('P1D'));
4182
-
4183
-		$file = $this->createMock(File::class);
4184
-		$file->method('getId')->willReturn(100);
4185
-
4186
-		$share = $this->manager->newShare();
4187
-		$share->setProviderId('foo')
4188
-			->setId('42')
4189
-			->setShareType(IShare::TYPE_EMAIL)
4190
-			->setToken('token')
4191
-			->setSharedBy('owner')
4192
-			->setShareOwner('owner')
4193
-			->setPassword('password')
4194
-			->setSendPasswordByTalk(true)
4195
-			->setExpirationDate($tomorrow)
4196
-			->setNode($file)
4197
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
4198
-
4199
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
4200
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4201
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
4202
-		$manager->expects($this->never())->method('verifyPassword');
4203
-		$manager->expects($this->never())->method('pathCreateChecks');
4204
-		$manager->expects($this->once())->method('linkCreateChecks');
4205
-		$manager->expects($this->never())->method('validateExpirationDateLink');
4206
-
4207
-		// If the old & new passwords are the same, we don't do anything
4208
-		$this->hasher->expects($this->never())
4209
-			->method('verify');
4210
-		$this->hasher->expects($this->never())
4211
-			->method('hash');
4212
-
4213
-		$this->defaultProvider->expects($this->never())
4214
-			->method('update');
4215
-
4216
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4217
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4218
-		$hookListener->expects($this->never())->method('post');
4219
-
4220
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4221
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4222
-		$hookListener2->expects($this->never())->method('post');
4223
-
4224
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4225
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4226
-		$hookListener3->expects($this->never())->method('post');
4227
-
4228
-		$manager->updateShare($share);
4229
-	}
4230
-
4231
-	public function testUpdateShareMailDisableSendPasswordByTalkWithPreviousPassword(): void {
4232
-		$this->expectException(\InvalidArgumentException::class);
4233
-		$this->expectExceptionMessage('Cannot disable sending the password by Talk without setting a new password');
4234
-
4235
-		$manager = $this->createManagerMock()
4236
-			->setMethods([
4237
-				'canShare',
4238
-				'getShareById',
4239
-				'generalCreateChecks',
4240
-				'verifyPassword',
4241
-				'pathCreateChecks',
4242
-				'linkCreateChecks',
4243
-				'validateExpirationDateLink',
4244
-			])
4245
-			->getMock();
4246
-
4247
-		$originalShare = $this->manager->newShare();
4248
-		$originalShare->setShareType(IShare::TYPE_EMAIL)
4249
-			->setPermissions(\OCP\Constants::PERMISSION_ALL)
4250
-			->setPassword('passwordHash')
4251
-			->setSendPasswordByTalk(true);
4252
-
4253
-		$tomorrow = new \DateTime();
4254
-		$tomorrow->setTime(0, 0, 0);
4255
-		$tomorrow->add(new \DateInterval('P1D'));
4256
-
4257
-		$file = $this->createMock(File::class);
4258
-		$file->method('getId')->willReturn(100);
4259
-
4260
-		$share = $this->manager->newShare();
4261
-		$share->setProviderId('foo')
4262
-			->setId('42')
4263
-			->setShareType(IShare::TYPE_EMAIL)
4264
-			->setToken('token')
4265
-			->setSharedBy('owner')
4266
-			->setShareOwner('owner')
4267
-			->setPassword('passwordHash')
4268
-			->setSendPasswordByTalk(false)
4269
-			->setExpirationDate($tomorrow)
4270
-			->setNode($file)
4271
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
4272
-
4273
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
4274
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4275
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
4276
-		$manager->expects($this->never())->method('verifyPassword');
4277
-		$manager->expects($this->never())->method('pathCreateChecks');
4278
-		$manager->expects($this->once())->method('linkCreateChecks');
4279
-		$manager->expects($this->never())->method('validateExpirationDateLink');
4280
-
4281
-		// If the old & new passwords are the same, we don't do anything
4282
-		$this->hasher->expects($this->never())
4283
-			->method('verify');
4284
-		$this->hasher->expects($this->never())
4285
-			->method('hash');
4286
-
4287
-		$this->defaultProvider->expects($this->never())
4288
-			->method('update');
4289
-
4290
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4291
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4292
-		$hookListener->expects($this->never())->method('post');
4293
-
4294
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4295
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4296
-		$hookListener2->expects($this->never())->method('post');
4297
-
4298
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4299
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4300
-		$hookListener3->expects($this->never())->method('post');
4301
-
4302
-		$manager->updateShare($share);
4303
-	}
4304
-
4305
-	public function testUpdateShareMailDisableSendPasswordByTalkWithoutChangingPassword(): void {
4306
-		$this->expectException(\InvalidArgumentException::class);
4307
-		$this->expectExceptionMessage('Cannot disable sending the password by Talk without setting a new password');
4308
-
4309
-		$manager = $this->createManagerMock()
4310
-			->setMethods([
4311
-				'canShare',
4312
-				'getShareById',
4313
-				'generalCreateChecks',
4314
-				'verifyPassword',
4315
-				'pathCreateChecks',
4316
-				'linkCreateChecks',
4317
-				'validateExpirationDateLink',
4318
-			])
4319
-			->getMock();
4320
-
4321
-		$originalShare = $this->manager->newShare();
4322
-		$originalShare->setShareType(IShare::TYPE_EMAIL)
4323
-			->setPermissions(\OCP\Constants::PERMISSION_ALL)
4324
-			->setPassword('passwordHash')
4325
-			->setSendPasswordByTalk(true);
4326
-
4327
-		$tomorrow = new \DateTime();
4328
-		$tomorrow->setTime(0, 0, 0);
4329
-		$tomorrow->add(new \DateInterval('P1D'));
4330
-
4331
-		$file = $this->createMock(File::class);
4332
-		$file->method('getId')->willReturn(100);
4333
-
4334
-		$share = $this->manager->newShare();
4335
-		$share->setProviderId('foo')
4336
-			->setId('42')
4337
-			->setShareType(IShare::TYPE_EMAIL)
4338
-			->setToken('token')
4339
-			->setSharedBy('owner')
4340
-			->setShareOwner('owner')
4341
-			->setPassword('passwordHash')
4342
-			->setSendPasswordByTalk(false)
4343
-			->setExpirationDate($tomorrow)
4344
-			->setNode($file)
4345
-			->setPermissions(\OCP\Constants::PERMISSION_ALL);
4346
-
4347
-		$manager->expects($this->once())->method('canShare')->willReturn(true);
4348
-		$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4349
-		$manager->expects($this->once())->method('generalCreateChecks')->with($share);
4350
-		$manager->expects($this->never())->method('verifyPassword');
4351
-		$manager->expects($this->never())->method('pathCreateChecks');
4352
-		$manager->expects($this->once())->method('linkCreateChecks');
4353
-		$manager->expects($this->never())->method('validateExpirationDateLink');
4354
-
4355
-		// If the old & new passwords are the same, we don't do anything
4356
-		$this->hasher->expects($this->never())
4357
-			->method('verify');
4358
-		$this->hasher->expects($this->never())
4359
-			->method('hash');
4360
-
4361
-		$this->defaultProvider->expects($this->never())
4362
-			->method('update');
4363
-
4364
-		$hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4365
-		\OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4366
-		$hookListener->expects($this->never())->method('post');
3007
+        $manager->method('deleteShare')
3008
+            ->willReturnCallback(function ($share) use (&$shares2) {
3009
+                for ($i = 0; $i < count($shares2); $i++) {
3010
+                    if ($shares2[$i]->getId() === $share->getId()) {
3011
+                        array_splice($shares2, $i, 1);
3012
+                        break;
3013
+                    }
3014
+                }
3015
+            });
3016
+
3017
+        $res = $manager->getSharesBy('user', IShare::TYPE_LINK, $node, true, 3, 0);
3018
+
3019
+        $this->assertCount(3, $res);
3020
+        $this->assertEquals($shares[0]->getId(), $res[0]->getId());
3021
+        $this->assertEquals($shares[1]->getId(), $res[1]->getId());
3022
+        $this->assertEquals($shares[6]->getId(), $res[2]->getId());
3023
+
3024
+        $this->assertCount(4, $shares2);
3025
+        $this->assertEquals(0, $shares2[0]->getId());
3026
+        $this->assertEquals(1, $shares2[1]->getId());
3027
+        $this->assertEquals(6, $shares2[2]->getId());
3028
+        $this->assertEquals(7, $shares2[3]->getId());
3029
+        $this->assertSame($today, $shares[3]->getExpirationDate());
3030
+    }
3031
+
3032
+    public function testGetShareByToken(): void {
3033
+        $this->config
3034
+            ->expects($this->exactly(2))
3035
+            ->method('getAppValue')
3036
+            ->willReturnMap([
3037
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
3038
+                ['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3039
+            ]);
3040
+
3041
+        $factory = $this->createMock(IProviderFactory::class);
3042
+
3043
+        $manager = $this->createManager($factory);
3044
+
3045
+        $share = $this->createMock(IShare::class);
3046
+
3047
+        $factory->expects($this->once())
3048
+            ->method('getProviderForType')
3049
+            ->with(IShare::TYPE_LINK)
3050
+            ->willReturn($this->defaultProvider);
3051
+
3052
+        $this->defaultProvider->expects($this->once())
3053
+            ->method('getShareByToken')
3054
+            ->with('token')
3055
+            ->willReturn($share);
3056
+
3057
+        $ret = $manager->getShareByToken('token');
3058
+        $this->assertSame($share, $ret);
3059
+    }
3060
+
3061
+    public function testGetShareByTokenRoom(): void {
3062
+        $this->config
3063
+            ->expects($this->exactly(2))
3064
+            ->method('getAppValue')
3065
+            ->willReturnMap([
3066
+                ['core', 'shareapi_allow_links', 'yes', 'no'],
3067
+                ['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3068
+            ]);
3069
+
3070
+        $factory = $this->createMock(IProviderFactory::class);
3071
+
3072
+        $manager = $this->createManager($factory);
3073
+
3074
+        $share = $this->createMock(IShare::class);
3075
+
3076
+        $roomShareProvider = $this->createMock(IShareProvider::class);
3077
+
3078
+        $factory->expects($this->any())
3079
+            ->method('getProviderForType')
3080
+            ->willReturnCallback(function ($shareType) use ($roomShareProvider) {
3081
+                if ($shareType !== IShare::TYPE_ROOM) {
3082
+                    throw new Exception\ProviderException();
3083
+                }
3084
+
3085
+                return $roomShareProvider;
3086
+            });
3087
+
3088
+        $roomShareProvider->expects($this->once())
3089
+            ->method('getShareByToken')
3090
+            ->with('token')
3091
+            ->willReturn($share);
3092
+
3093
+        $ret = $manager->getShareByToken('token');
3094
+        $this->assertSame($share, $ret);
3095
+    }
3096
+
3097
+    public function testGetShareByTokenWithException(): void {
3098
+        $this->config
3099
+            ->expects($this->exactly(2))
3100
+            ->method('getAppValue')
3101
+            ->willReturnMap([
3102
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
3103
+                ['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3104
+            ]);
3105
+
3106
+        $factory = $this->createMock(IProviderFactory::class);
3107
+
3108
+        $manager = $this->createManager($factory);
3109
+
3110
+        $share = $this->createMock(IShare::class);
3111
+
3112
+        $factory->expects($this->exactly(2))
3113
+            ->method('getProviderForType')
3114
+            ->withConsecutive(
3115
+                [IShare::TYPE_LINK],
3116
+                [IShare::TYPE_REMOTE]
3117
+            )
3118
+            ->willReturn($this->defaultProvider);
3119
+
3120
+        $this->defaultProvider->expects($this->exactly(2))
3121
+            ->method('getShareByToken')
3122
+            ->with('token')
3123
+            ->willReturnOnConsecutiveCalls(
3124
+                $this->throwException(new ShareNotFound()),
3125
+                $share
3126
+            );
3127
+
3128
+        $ret = $manager->getShareByToken('token');
3129
+        $this->assertSame($share, $ret);
3130
+    }
3131
+
3132
+
3133
+    public function testGetShareByTokenHideDisabledUser(): void {
3134
+        $this->expectException(\OCP\Share\Exceptions\ShareNotFound::class);
3135
+        $this->expectExceptionMessage('The requested share comes from a disabled user');
3136
+
3137
+        $this->config
3138
+            ->expects($this->exactly(2))
3139
+            ->method('getAppValue')
3140
+            ->willReturnMap([
3141
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
3142
+                ['files_sharing', 'hide_disabled_user_shares', 'no', 'yes'],
3143
+            ]);
3144
+
3145
+        $this->l->expects($this->once())
3146
+            ->method('t')
3147
+            ->willReturnArgument(0);
3148
+
3149
+        $manager = $this->createManagerMock()
3150
+            ->setMethods(['deleteShare'])
3151
+            ->getMock();
3152
+
3153
+        $date = new \DateTime();
3154
+        $date->setTime(0, 0, 0);
3155
+        $date->add(new \DateInterval('P2D'));
3156
+        $share = $this->manager->newShare();
3157
+        $share->setExpirationDate($date);
3158
+        $share->setShareOwner('owner');
3159
+        $share->setSharedBy('sharedBy');
3160
+
3161
+        $sharedBy = $this->createMock(IUser::class);
3162
+        $owner = $this->createMock(IUser::class);
3163
+
3164
+        $this->userManager->method('get')->willReturnMap([
3165
+            ['sharedBy', $sharedBy],
3166
+            ['owner', $owner],
3167
+        ]);
3168
+
3169
+        $owner->expects($this->once())
3170
+            ->method('isEnabled')
3171
+            ->willReturn(true);
3172
+        $sharedBy->expects($this->once())
3173
+            ->method('isEnabled')
3174
+            ->willReturn(false);
3175
+
3176
+        $this->defaultProvider->expects($this->once())
3177
+            ->method('getShareByToken')
3178
+            ->with('expiredToken')
3179
+            ->willReturn($share);
3180
+
3181
+        $manager->expects($this->never())
3182
+            ->method('deleteShare');
3183
+
3184
+        $manager->getShareByToken('expiredToken');
3185
+    }
3186
+
3187
+
3188
+    public function testGetShareByTokenExpired(): void {
3189
+        $this->expectException(\OCP\Share\Exceptions\ShareNotFound::class);
3190
+        $this->expectExceptionMessage('The requested share does not exist anymore');
3191
+
3192
+        $this->config
3193
+            ->expects($this->once())
3194
+            ->method('getAppValue')
3195
+            ->with('core', 'shareapi_allow_links', 'yes')
3196
+            ->willReturn('yes');
3197
+
3198
+        $this->l->expects($this->once())
3199
+            ->method('t')
3200
+            ->willReturnArgument(0);
3201
+
3202
+        $manager = $this->createManagerMock()
3203
+            ->setMethods(['deleteShare'])
3204
+            ->getMock();
3205
+
3206
+        $date = new \DateTime();
3207
+        $date->setTime(0, 0, 0);
3208
+        $share = $this->manager->newShare();
3209
+        $share->setExpirationDate($date);
3210
+
3211
+        $this->defaultProvider->expects($this->once())
3212
+            ->method('getShareByToken')
3213
+            ->with('expiredToken')
3214
+            ->willReturn($share);
3215
+
3216
+        $manager->expects($this->once())
3217
+            ->method('deleteShare')
3218
+            ->with($this->equalTo($share));
3219
+
3220
+        $manager->getShareByToken('expiredToken');
3221
+    }
3222
+
3223
+    public function testGetShareByTokenNotExpired(): void {
3224
+        $this->config
3225
+            ->expects($this->exactly(2))
3226
+            ->method('getAppValue')
3227
+            ->willReturnMap([
3228
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
3229
+                ['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3230
+            ]);
3231
+
3232
+        $date = new \DateTime();
3233
+        $date->setTime(0, 0, 0);
3234
+        $date->add(new \DateInterval('P2D'));
3235
+        $share = $this->manager->newShare();
3236
+        $share->setExpirationDate($date);
3237
+
3238
+        $this->defaultProvider->expects($this->once())
3239
+            ->method('getShareByToken')
3240
+            ->with('expiredToken')
3241
+            ->willReturn($share);
3242
+
3243
+        $res = $this->manager->getShareByToken('expiredToken');
3244
+
3245
+        $this->assertSame($share, $res);
3246
+    }
3247
+
3248
+
3249
+    public function testGetShareByTokenWithPublicLinksDisabled(): void {
3250
+        $this->expectException(\OCP\Share\Exceptions\ShareNotFound::class);
3251
+
3252
+        $this->config
3253
+            ->expects($this->once())
3254
+            ->method('getAppValue')
3255
+            ->with('core', 'shareapi_allow_links', 'yes')
3256
+            ->willReturn('no');
3257
+        $this->manager->getShareByToken('validToken');
3258
+    }
3259
+
3260
+    public function testGetShareByTokenPublicUploadDisabled(): void {
3261
+        $this->config
3262
+            ->expects($this->exactly(3))
3263
+            ->method('getAppValue')
3264
+            ->willReturnMap([
3265
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
3266
+                ['core', 'shareapi_allow_public_upload', 'yes', 'no'],
3267
+                ['files_sharing', 'hide_disabled_user_shares', 'no', 'no'],
3268
+            ]);
3269
+
3270
+        $share = $this->manager->newShare();
3271
+        $share->setShareType(IShare::TYPE_LINK)
3272
+            ->setPermissions(\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE);
3273
+        $share->setSharedWith('sharedWith');
3274
+        $folder = $this->createMock(\OC\Files\Node\Folder::class);
3275
+        $share->setNode($folder);
3276
+
3277
+        $this->defaultProvider->expects($this->once())
3278
+            ->method('getShareByToken')
3279
+            ->willReturn('validToken')
3280
+            ->willReturn($share);
3281
+
3282
+        $res = $this->manager->getShareByToken('validToken');
3283
+
3284
+        $this->assertSame(\OCP\Constants::PERMISSION_READ, $res->getPermissions());
3285
+    }
3286
+
3287
+    public function testCheckPasswordNoLinkShare(): void {
3288
+        $share = $this->createMock(IShare::class);
3289
+        $share->method('getShareType')->willReturn(IShare::TYPE_USER);
3290
+        $this->assertFalse($this->manager->checkPassword($share, 'password'));
3291
+    }
3292
+
3293
+    public function testCheckPasswordNoPassword(): void {
3294
+        $share = $this->createMock(IShare::class);
3295
+        $share->method('getShareType')->willReturn(IShare::TYPE_LINK);
3296
+        $this->assertFalse($this->manager->checkPassword($share, 'password'));
3297
+
3298
+        $share->method('getPassword')->willReturn('password');
3299
+        $this->assertFalse($this->manager->checkPassword($share, null));
3300
+    }
3301
+
3302
+    public function testCheckPasswordInvalidPassword(): void {
3303
+        $share = $this->createMock(IShare::class);
3304
+        $share->method('getShareType')->willReturn(IShare::TYPE_LINK);
3305
+        $share->method('getPassword')->willReturn('password');
3306
+
3307
+        $this->hasher->method('verify')->with('invalidpassword', 'password', '')->willReturn(false);
3308
+
3309
+        $this->assertFalse($this->manager->checkPassword($share, 'invalidpassword'));
3310
+    }
3311
+
3312
+    public function testCheckPasswordValidPassword(): void {
3313
+        $share = $this->createMock(IShare::class);
3314
+        $share->method('getShareType')->willReturn(IShare::TYPE_LINK);
3315
+        $share->method('getPassword')->willReturn('passwordHash');
3316
+
3317
+        $this->hasher->method('verify')->with('password', 'passwordHash', '')->willReturn(true);
3318
+
3319
+        $this->assertTrue($this->manager->checkPassword($share, 'password'));
3320
+    }
3321
+
3322
+    public function testCheckPasswordUpdateShare(): void {
3323
+        $share = $this->manager->newShare();
3324
+        $share->setShareType(IShare::TYPE_LINK)
3325
+            ->setPassword('passwordHash');
3326
+
3327
+        $this->hasher->method('verify')->with('password', 'passwordHash', '')
3328
+            ->willReturnCallback(function ($pass, $hash, &$newHash) {
3329
+                $newHash = 'newHash';
3330
+
3331
+                return true;
3332
+            });
3333
+
3334
+        $this->defaultProvider->expects($this->once())
3335
+            ->method('update')
3336
+            ->with($this->callback(function (\OCP\Share\IShare $share) {
3337
+                return $share->getPassword() === 'newHash';
3338
+            }));
3339
+
3340
+        $this->assertTrue($this->manager->checkPassword($share, 'password'));
3341
+    }
3342
+
3343
+
3344
+    public function testUpdateShareCantChangeShareType(): void {
3345
+        $this->expectException(\Exception::class);
3346
+        $this->expectExceptionMessage('Cannot change share type');
3347
+
3348
+        $manager = $this->createManagerMock()
3349
+            ->setMethods([
3350
+                'canShare',
3351
+                'getShareById'
3352
+            ])
3353
+            ->getMock();
3354
+
3355
+        $originalShare = $this->manager->newShare();
3356
+        $originalShare->setShareType(IShare::TYPE_GROUP);
3357
+
3358
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3359
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3360
+
3361
+        $share = $this->manager->newShare();
3362
+        $attrs = $this->manager->newShare()->newAttributes();
3363
+        $attrs->setAttribute('app1', 'perm1', true);
3364
+        $share->setProviderId('foo')
3365
+            ->setId('42')
3366
+            ->setShareType(IShare::TYPE_USER);
3367
+
3368
+        $manager->updateShare($share);
3369
+    }
3370
+
3371
+
3372
+    public function testUpdateShareCantChangeRecipientForGroupShare(): void {
3373
+        $this->expectException(\Exception::class);
3374
+        $this->expectExceptionMessage('Can only update recipient on user shares');
3375
+
3376
+        $manager = $this->createManagerMock()
3377
+            ->setMethods([
3378
+                'canShare',
3379
+                'getShareById'
3380
+            ])
3381
+            ->getMock();
3382
+
3383
+        $originalShare = $this->manager->newShare();
3384
+        $originalShare->setShareType(IShare::TYPE_GROUP)
3385
+            ->setSharedWith('origGroup');
3386
+
3387
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3388
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3389
+
3390
+        $share = $this->manager->newShare();
3391
+        $share->setProviderId('foo')
3392
+            ->setId('42')
3393
+            ->setShareType(IShare::TYPE_GROUP)
3394
+            ->setSharedWith('newGroup');
3395
+
3396
+        $manager->updateShare($share);
3397
+    }
3398
+
3399
+
3400
+    public function testUpdateShareCantShareWithOwner(): void {
3401
+        $this->expectException(\Exception::class);
3402
+        $this->expectExceptionMessage('Cannot share with the share owner');
3403
+
3404
+        $manager = $this->createManagerMock()
3405
+            ->setMethods([
3406
+                'canShare',
3407
+                'getShareById'
3408
+            ])
3409
+            ->getMock();
3410
+
3411
+        $originalShare = $this->manager->newShare();
3412
+        $originalShare->setShareType(IShare::TYPE_USER)
3413
+            ->setSharedWith('sharedWith');
3414
+
3415
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3416
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3417
+
3418
+        $share = $this->manager->newShare();
3419
+        $share->setProviderId('foo')
3420
+            ->setId('42')
3421
+            ->setShareType(IShare::TYPE_USER)
3422
+            ->setSharedWith('newUser')
3423
+            ->setShareOwner('newUser');
3424
+
3425
+        $manager->updateShare($share);
3426
+    }
3427
+
3428
+    public function testUpdateShareUser(): void {
3429
+        $this->userManager->expects($this->any())->method('userExists')->willReturn(true);
3430
+
3431
+        $manager = $this->createManagerMock()
3432
+            ->setMethods([
3433
+                'canShare',
3434
+                'getShareById',
3435
+                'generalCreateChecks',
3436
+                'userCreateChecks',
3437
+                'pathCreateChecks',
3438
+            ])
3439
+            ->getMock();
3440
+
3441
+        $originalShare = $this->manager->newShare();
3442
+        $originalShare->setShareType(IShare::TYPE_USER)
3443
+            ->setSharedWith('origUser')
3444
+            ->setPermissions(1);
3445
+
3446
+        $node = $this->createMock(File::class);
3447
+        $node->method('getId')->willReturn(100);
3448
+        $node->method('getPath')->willReturn('/newUser/files/myPath');
3449
+
3450
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3451
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3452
+
3453
+        $share = $this->manager->newShare();
3454
+        $attrs = $this->manager->newShare()->newAttributes();
3455
+        $attrs->setAttribute('app1', 'perm1', true);
3456
+        $share->setProviderId('foo')
3457
+            ->setId('42')
3458
+            ->setShareType(IShare::TYPE_USER)
3459
+            ->setSharedWith('origUser')
3460
+            ->setShareOwner('newUser')
3461
+            ->setSharedBy('sharer')
3462
+            ->setPermissions(31)
3463
+            ->setAttributes($attrs)
3464
+            ->setNode($node);
3465
+
3466
+        $this->defaultProvider->expects($this->once())
3467
+            ->method('update')
3468
+            ->with($share)
3469
+            ->willReturn($share);
3470
+
3471
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3472
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3473
+        $hookListener->expects($this->never())->method('post');
3474
+
3475
+        $this->rootFolder->method('getUserFolder')->with('newUser')->willReturnSelf();
3476
+        $this->rootFolder->method('getRelativePath')->with('/newUser/files/myPath')->willReturn('/myPath');
3477
+
3478
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3479
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener2, 'post');
3480
+        $hookListener2->expects($this->once())->method('post')->with([
3481
+            'itemType' => 'file',
3482
+            'itemSource' => 100,
3483
+            'shareType' => IShare::TYPE_USER,
3484
+            'shareWith' => 'origUser',
3485
+            'uidOwner' => 'sharer',
3486
+            'permissions' => 31,
3487
+            'path' => '/myPath',
3488
+            'attributes' => $attrs->toArray(),
3489
+        ]);
3490
+
3491
+        $manager->updateShare($share);
3492
+    }
3493
+
3494
+    public function testUpdateShareGroup(): void {
3495
+        $manager = $this->createManagerMock()
3496
+            ->setMethods([
3497
+                'canShare',
3498
+                'getShareById',
3499
+                'generalCreateChecks',
3500
+                'groupCreateChecks',
3501
+                'pathCreateChecks',
3502
+            ])
3503
+            ->getMock();
3504
+
3505
+        $originalShare = $this->manager->newShare();
3506
+        $originalShare->setShareType(IShare::TYPE_GROUP)
3507
+            ->setSharedWith('origUser')
3508
+            ->setPermissions(31);
3509
+
3510
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3511
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3512
+
3513
+        $node = $this->createMock(File::class);
3514
+
3515
+        $share = $this->manager->newShare();
3516
+        $share->setProviderId('foo')
3517
+            ->setId('42')
3518
+            ->setShareType(IShare::TYPE_GROUP)
3519
+            ->setSharedWith('origUser')
3520
+            ->setShareOwner('owner')
3521
+            ->setNode($node)
3522
+            ->setPermissions(31);
3523
+
3524
+        $this->defaultProvider->expects($this->once())
3525
+            ->method('update')
3526
+            ->with($share)
3527
+            ->willReturn($share);
3528
+
3529
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3530
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3531
+        $hookListener->expects($this->never())->method('post');
3532
+
3533
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3534
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener2, 'post');
3535
+        $hookListener2->expects($this->never())->method('post');
3536
+
3537
+        $manager->updateShare($share);
3538
+    }
3539
+
3540
+    public function testUpdateShareLink(): void {
3541
+        $manager = $this->createManagerMock()
3542
+            ->setMethods([
3543
+                'canShare',
3544
+                'getShareById',
3545
+                'generalCreateChecks',
3546
+                'linkCreateChecks',
3547
+                'pathCreateChecks',
3548
+                'verifyPassword',
3549
+                'validateExpirationDateLink',
3550
+            ])
3551
+            ->getMock();
3552
+
3553
+        $originalShare = $this->manager->newShare();
3554
+        $originalShare->setShareType(IShare::TYPE_LINK)
3555
+            ->setPermissions(15);
3556
+
3557
+        $tomorrow = new \DateTime();
3558
+        $tomorrow->setTime(0, 0, 0);
3559
+        $tomorrow->add(new \DateInterval('P1D'));
3560
+
3561
+        $file = $this->createMock(File::class);
3562
+        $file->method('getId')->willReturn(100);
3563
+
3564
+        $share = $this->manager->newShare();
3565
+        $share->setProviderId('foo')
3566
+            ->setId('42')
3567
+            ->setShareType(IShare::TYPE_LINK)
3568
+            ->setToken('token')
3569
+            ->setSharedBy('owner')
3570
+            ->setShareOwner('owner')
3571
+            ->setPassword('password')
3572
+            ->setExpirationDate($tomorrow)
3573
+            ->setNode($file)
3574
+            ->setPermissions(15);
3575
+
3576
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3577
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3578
+        $manager->expects($this->once())->method('validateExpirationDateLink')->with($share);
3579
+        $manager->expects($this->once())->method('verifyPassword')->with('password');
3580
+
3581
+        $this->hasher->expects($this->once())
3582
+            ->method('hash')
3583
+            ->with('password')
3584
+            ->willReturn('hashed');
3585
+
3586
+        $this->defaultProvider->expects($this->once())
3587
+            ->method('update')
3588
+            ->with($share)
3589
+            ->willReturn($share);
3590
+
3591
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3592
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3593
+        $hookListener->expects($this->once())->method('post')->with([
3594
+            'itemType' => 'file',
3595
+            'itemSource' => 100,
3596
+            'date' => $tomorrow,
3597
+            'uidOwner' => 'owner',
3598
+        ]);
3599
+
3600
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3601
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3602
+        $hookListener2->expects($this->once())->method('post')->with([
3603
+            'itemType' => 'file',
3604
+            'itemSource' => 100,
3605
+            'uidOwner' => 'owner',
3606
+            'token' => 'token',
3607
+            'disabled' => false,
3608
+        ]);
3609
+
3610
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3611
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3612
+        $hookListener3->expects($this->never())->method('post');
3613
+
3614
+
3615
+        $manager->updateShare($share);
3616
+    }
3617
+
3618
+    public function testUpdateShareLinkEnableSendPasswordByTalkWithNoPassword(): void {
3619
+        $this->expectException(\InvalidArgumentException::class);
3620
+        $this->expectExceptionMessage('Cannot enable sending the password by Talk with an empty password');
3621
+
3622
+        $manager = $this->createManagerMock()
3623
+            ->setMethods([
3624
+                'canShare',
3625
+                'getShareById',
3626
+                'generalCreateChecks',
3627
+                'linkCreateChecks',
3628
+                'pathCreateChecks',
3629
+                'verifyPassword',
3630
+                'validateExpirationDateLink',
3631
+            ])
3632
+            ->getMock();
3633
+
3634
+        $originalShare = $this->manager->newShare();
3635
+        $originalShare->setShareType(IShare::TYPE_LINK)
3636
+            ->setPermissions(15);
3637
+
3638
+        $tomorrow = new \DateTime();
3639
+        $tomorrow->setTime(0, 0, 0);
3640
+        $tomorrow->add(new \DateInterval('P1D'));
3641
+
3642
+        $file = $this->createMock(File::class);
3643
+        $file->method('getId')->willReturn(100);
3644
+
3645
+        $share = $this->manager->newShare();
3646
+        $share->setProviderId('foo')
3647
+            ->setId('42')
3648
+            ->setShareType(IShare::TYPE_LINK)
3649
+            ->setToken('token')
3650
+            ->setSharedBy('owner')
3651
+            ->setShareOwner('owner')
3652
+            ->setPassword(null)
3653
+            ->setSendPasswordByTalk(true)
3654
+            ->setExpirationDate($tomorrow)
3655
+            ->setNode($file)
3656
+            ->setPermissions(15);
3657
+
3658
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3659
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3660
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
3661
+        $manager->expects($this->once())->method('linkCreateChecks')->with($share);
3662
+        $manager->expects($this->never())->method('verifyPassword');
3663
+        $manager->expects($this->never())->method('pathCreateChecks');
3664
+        $manager->expects($this->never())->method('validateExpirationDateLink');
3665
+
3666
+        $this->hasher->expects($this->never())
3667
+            ->method('hash');
3668
+
3669
+        $this->defaultProvider->expects($this->never())
3670
+            ->method('update');
3671
+
3672
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3673
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3674
+        $hookListener->expects($this->never())->method('post');
3675
+
3676
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3677
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3678
+        $hookListener2->expects($this->never())->method('post');
3679
+
3680
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3681
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3682
+        $hookListener3->expects($this->never())->method('post');
3683
+
3684
+        $manager->updateShare($share);
3685
+    }
3686
+
3687
+    public function testUpdateShareMail(): void {
3688
+        $manager = $this->createManagerMock()
3689
+            ->setMethods([
3690
+                'canShare',
3691
+                'getShareById',
3692
+                'generalCreateChecks',
3693
+                'verifyPassword',
3694
+                'pathCreateChecks',
3695
+                'linkCreateChecks',
3696
+                'validateExpirationDateLink',
3697
+            ])
3698
+            ->getMock();
3699
+
3700
+        $originalShare = $this->manager->newShare();
3701
+        $originalShare->setShareType(IShare::TYPE_EMAIL)
3702
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
3703
+
3704
+        $tomorrow = new \DateTime();
3705
+        $tomorrow->setTime(0, 0, 0);
3706
+        $tomorrow->add(new \DateInterval('P1D'));
3707
+
3708
+        $file = $this->createMock(File::class);
3709
+        $file->method('getId')->willReturn(100);
3710
+
3711
+        $share = $this->manager->newShare();
3712
+        $share->setProviderId('foo')
3713
+            ->setId('42')
3714
+            ->setShareType(IShare::TYPE_EMAIL)
3715
+            ->setToken('token')
3716
+            ->setSharedBy('owner')
3717
+            ->setShareOwner('owner')
3718
+            ->setPassword('password')
3719
+            ->setExpirationDate($tomorrow)
3720
+            ->setNode($file)
3721
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
3722
+
3723
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3724
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3725
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
3726
+        $manager->expects($this->once())->method('verifyPassword')->with('password');
3727
+        $manager->expects($this->once())->method('pathCreateChecks')->with($file);
3728
+        $manager->expects($this->once())->method('linkCreateChecks');
3729
+        $manager->expects($this->once())->method('validateExpirationDateLink');
3730
+
3731
+        $this->hasher->expects($this->once())
3732
+            ->method('hash')
3733
+            ->with('password')
3734
+            ->willReturn('hashed');
3735
+
3736
+        $this->defaultProvider->expects($this->once())
3737
+            ->method('update')
3738
+            ->with($share, 'password')
3739
+            ->willReturn($share);
3740
+
3741
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3742
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3743
+        $hookListener->expects($this->once())->method('post')->with([
3744
+            'itemType' => 'file',
3745
+            'itemSource' => 100,
3746
+            'date' => $tomorrow,
3747
+            'uidOwner' => 'owner',
3748
+        ]);
3749
+
3750
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3751
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3752
+        $hookListener2->expects($this->once())->method('post')->with([
3753
+            'itemType' => 'file',
3754
+            'itemSource' => 100,
3755
+            'uidOwner' => 'owner',
3756
+            'token' => 'token',
3757
+            'disabled' => false,
3758
+        ]);
3759
+
3760
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3761
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3762
+        $hookListener3->expects($this->never())->method('post');
3763
+
3764
+        $manager->updateShare($share);
3765
+    }
3766
+
3767
+    public function testUpdateShareMailEnableSendPasswordByTalk(): void {
3768
+        $manager = $this->createManagerMock()
3769
+            ->setMethods([
3770
+                'canShare',
3771
+                'getShareById',
3772
+                'generalCreateChecks',
3773
+                'verifyPassword',
3774
+                'pathCreateChecks',
3775
+                'linkCreateChecks',
3776
+                'validateExpirationDateLink',
3777
+            ])
3778
+            ->getMock();
3779
+
3780
+        $originalShare = $this->manager->newShare();
3781
+        $originalShare->setShareType(IShare::TYPE_EMAIL)
3782
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL)
3783
+            ->setPassword(null)
3784
+            ->setSendPasswordByTalk(false);
3785
+
3786
+        $tomorrow = new \DateTime();
3787
+        $tomorrow->setTime(0, 0, 0);
3788
+        $tomorrow->add(new \DateInterval('P1D'));
3789
+
3790
+        $file = $this->createMock(File::class);
3791
+        $file->method('getId')->willReturn(100);
3792
+
3793
+        $share = $this->manager->newShare();
3794
+        $share->setProviderId('foo')
3795
+            ->setId('42')
3796
+            ->setShareType(IShare::TYPE_EMAIL)
3797
+            ->setToken('token')
3798
+            ->setSharedBy('owner')
3799
+            ->setShareOwner('owner')
3800
+            ->setPassword('password')
3801
+            ->setSendPasswordByTalk(true)
3802
+            ->setExpirationDate($tomorrow)
3803
+            ->setNode($file)
3804
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
3805
+
3806
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3807
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3808
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
3809
+        $manager->expects($this->once())->method('verifyPassword')->with('password');
3810
+        $manager->expects($this->once())->method('pathCreateChecks')->with($file);
3811
+        $manager->expects($this->once())->method('linkCreateChecks');
3812
+        $manager->expects($this->once())->method('validateExpirationDateLink');
3813
+
3814
+        $this->hasher->expects($this->once())
3815
+            ->method('hash')
3816
+            ->with('password')
3817
+            ->willReturn('hashed');
3818
+
3819
+        $this->defaultProvider->expects($this->once())
3820
+            ->method('update')
3821
+            ->with($share, 'password')
3822
+            ->willReturn($share);
3823
+
3824
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3825
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3826
+        $hookListener->expects($this->once())->method('post')->with([
3827
+            'itemType' => 'file',
3828
+            'itemSource' => 100,
3829
+            'date' => $tomorrow,
3830
+            'uidOwner' => 'owner',
3831
+        ]);
3832
+
3833
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3834
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3835
+        $hookListener2->expects($this->once())->method('post')->with([
3836
+            'itemType' => 'file',
3837
+            'itemSource' => 100,
3838
+            'uidOwner' => 'owner',
3839
+            'token' => 'token',
3840
+            'disabled' => false,
3841
+        ]);
3842
+
3843
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3844
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3845
+        $hookListener3->expects($this->never())->method('post');
3846
+
3847
+        $manager->updateShare($share);
3848
+    }
3849
+
3850
+    public function testUpdateShareMailEnableSendPasswordByTalkWithDifferentPassword(): void {
3851
+        $manager = $this->createManagerMock()
3852
+            ->setMethods([
3853
+                'canShare',
3854
+                'getShareById',
3855
+                'generalCreateChecks',
3856
+                'verifyPassword',
3857
+                'pathCreateChecks',
3858
+                'linkCreateChecks',
3859
+                'validateExpirationDateLink',
3860
+            ])
3861
+            ->getMock();
3862
+
3863
+        $originalShare = $this->manager->newShare();
3864
+        $originalShare->setShareType(IShare::TYPE_EMAIL)
3865
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL)
3866
+            ->setPassword('anotherPasswordHash')
3867
+            ->setSendPasswordByTalk(false);
3868
+
3869
+        $tomorrow = new \DateTime();
3870
+        $tomorrow->setTime(0, 0, 0);
3871
+        $tomorrow->add(new \DateInterval('P1D'));
3872
+
3873
+        $file = $this->createMock(File::class);
3874
+        $file->method('getId')->willReturn(100);
3875
+
3876
+        $share = $this->manager->newShare();
3877
+        $share->setProviderId('foo')
3878
+            ->setId('42')
3879
+            ->setShareType(IShare::TYPE_EMAIL)
3880
+            ->setToken('token')
3881
+            ->setSharedBy('owner')
3882
+            ->setShareOwner('owner')
3883
+            ->setPassword('password')
3884
+            ->setSendPasswordByTalk(true)
3885
+            ->setExpirationDate($tomorrow)
3886
+            ->setNode($file)
3887
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
3888
+
3889
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3890
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3891
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
3892
+        $manager->expects($this->once())->method('verifyPassword')->with('password');
3893
+        $manager->expects($this->once())->method('pathCreateChecks')->with($file);
3894
+        $manager->expects($this->once())->method('linkCreateChecks');
3895
+        $manager->expects($this->once())->method('validateExpirationDateLink');
3896
+
3897
+        $this->hasher->expects($this->once())
3898
+            ->method('verify')
3899
+            ->with('password', 'anotherPasswordHash')
3900
+            ->willReturn(false);
3901
+
3902
+        $this->hasher->expects($this->once())
3903
+            ->method('hash')
3904
+            ->with('password')
3905
+            ->willReturn('hashed');
3906
+
3907
+        $this->defaultProvider->expects($this->once())
3908
+            ->method('update')
3909
+            ->with($share, 'password')
3910
+            ->willReturn($share);
3911
+
3912
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3913
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3914
+        $hookListener->expects($this->once())->method('post')->with([
3915
+            'itemType' => 'file',
3916
+            'itemSource' => 100,
3917
+            'date' => $tomorrow,
3918
+            'uidOwner' => 'owner',
3919
+        ]);
3920
+
3921
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3922
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
3923
+        $hookListener2->expects($this->once())->method('post')->with([
3924
+            'itemType' => 'file',
3925
+            'itemSource' => 100,
3926
+            'uidOwner' => 'owner',
3927
+            'token' => 'token',
3928
+            'disabled' => false,
3929
+        ]);
3930
+
3931
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3932
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
3933
+        $hookListener3->expects($this->never())->method('post');
3934
+
3935
+        $manager->updateShare($share);
3936
+    }
3937
+
3938
+    public function testUpdateShareMailEnableSendPasswordByTalkWithNoPassword(): void {
3939
+        $this->expectException(\InvalidArgumentException::class);
3940
+        $this->expectExceptionMessage('Cannot enable sending the password by Talk with an empty password');
3941
+
3942
+        $manager = $this->createManagerMock()
3943
+            ->setMethods([
3944
+                'canShare',
3945
+                'getShareById',
3946
+                'generalCreateChecks',
3947
+                'verifyPassword',
3948
+                'pathCreateChecks',
3949
+                'linkCreateChecks',
3950
+                'validateExpirationDateLink',
3951
+            ])
3952
+            ->getMock();
3953
+
3954
+        $originalShare = $this->manager->newShare();
3955
+        $originalShare->setShareType(IShare::TYPE_EMAIL)
3956
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL)
3957
+            ->setPassword(null)
3958
+            ->setSendPasswordByTalk(false);
3959
+
3960
+        $tomorrow = new \DateTime();
3961
+        $tomorrow->setTime(0, 0, 0);
3962
+        $tomorrow->add(new \DateInterval('P1D'));
3963
+
3964
+        $file = $this->createMock(File::class);
3965
+        $file->method('getId')->willReturn(100);
3966
+
3967
+        $share = $this->manager->newShare();
3968
+        $share->setProviderId('foo')
3969
+            ->setId('42')
3970
+            ->setShareType(IShare::TYPE_EMAIL)
3971
+            ->setToken('token')
3972
+            ->setSharedBy('owner')
3973
+            ->setShareOwner('owner')
3974
+            ->setPassword(null)
3975
+            ->setSendPasswordByTalk(true)
3976
+            ->setExpirationDate($tomorrow)
3977
+            ->setNode($file)
3978
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
3979
+
3980
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
3981
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
3982
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
3983
+        $manager->expects($this->never())->method('verifyPassword');
3984
+        $manager->expects($this->never())->method('pathCreateChecks');
3985
+        $manager->expects($this->once())->method('linkCreateChecks');
3986
+        $manager->expects($this->never())->method('validateExpirationDateLink');
3987
+
3988
+        // If the password is empty, we have nothing to hash
3989
+        $this->hasher->expects($this->never())
3990
+            ->method('hash');
3991
+
3992
+        $this->defaultProvider->expects($this->never())
3993
+            ->method('update');
3994
+
3995
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
3996
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
3997
+        $hookListener->expects($this->never())->method('post');
3998
+
3999
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4000
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4001
+        $hookListener2->expects($this->never())->method('post');
4002
+
4003
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4004
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4005
+        $hookListener3->expects($this->never())->method('post');
4006
+
4007
+        $manager->updateShare($share);
4008
+    }
4009
+
4010
+
4011
+    public function testUpdateShareMailEnableSendPasswordByTalkRemovingPassword(): void {
4012
+        $this->expectException(\InvalidArgumentException::class);
4013
+        $this->expectExceptionMessage('Cannot enable sending the password by Talk with an empty password');
4014
+
4015
+        $manager = $this->createManagerMock()
4016
+            ->setMethods([
4017
+                'canShare',
4018
+                'getShareById',
4019
+                'generalCreateChecks',
4020
+                'verifyPassword',
4021
+                'pathCreateChecks',
4022
+                'linkCreateChecks',
4023
+                'validateExpirationDateLink',
4024
+            ])
4025
+            ->getMock();
4026
+
4027
+        $originalShare = $this->manager->newShare();
4028
+        $originalShare->setShareType(IShare::TYPE_EMAIL)
4029
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL)
4030
+            ->setPassword('passwordHash')
4031
+            ->setSendPasswordByTalk(false);
4032
+
4033
+        $tomorrow = new \DateTime();
4034
+        $tomorrow->setTime(0, 0, 0);
4035
+        $tomorrow->add(new \DateInterval('P1D'));
4036
+
4037
+        $file = $this->createMock(File::class);
4038
+        $file->method('getId')->willReturn(100);
4039
+
4040
+        $share = $this->manager->newShare();
4041
+        $share->setProviderId('foo')
4042
+            ->setId('42')
4043
+            ->setShareType(IShare::TYPE_EMAIL)
4044
+            ->setToken('token')
4045
+            ->setSharedBy('owner')
4046
+            ->setShareOwner('owner')
4047
+            ->setPassword(null)
4048
+            ->setSendPasswordByTalk(true)
4049
+            ->setExpirationDate($tomorrow)
4050
+            ->setNode($file)
4051
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
4052
+
4053
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
4054
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4055
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
4056
+        $manager->expects($this->once())->method('verifyPassword');
4057
+        $manager->expects($this->never())->method('pathCreateChecks');
4058
+        $manager->expects($this->once())->method('linkCreateChecks');
4059
+        $manager->expects($this->never())->method('validateExpirationDateLink');
4060
+
4061
+        // If the password is empty, we have nothing to hash
4062
+        $this->hasher->expects($this->never())
4063
+            ->method('hash');
4064
+
4065
+        $this->defaultProvider->expects($this->never())
4066
+            ->method('update');
4067
+
4068
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4069
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4070
+        $hookListener->expects($this->never())->method('post');
4071
+
4072
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4073
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4074
+        $hookListener2->expects($this->never())->method('post');
4075
+
4076
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4077
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4078
+        $hookListener3->expects($this->never())->method('post');
4079
+
4080
+        $manager->updateShare($share);
4081
+    }
4082
+
4083
+
4084
+    public function testUpdateShareMailEnableSendPasswordByTalkRemovingPasswordWithEmptyString(): void {
4085
+        $this->expectException(\InvalidArgumentException::class);
4086
+        $this->expectExceptionMessage('Cannot enable sending the password by Talk with an empty password');
4087
+
4088
+        $manager = $this->createManagerMock()
4089
+            ->setMethods([
4090
+                'canShare',
4091
+                'getShareById',
4092
+                'generalCreateChecks',
4093
+                'verifyPassword',
4094
+                'pathCreateChecks',
4095
+                'linkCreateChecks',
4096
+                'validateExpirationDateLink',
4097
+            ])
4098
+            ->getMock();
4099
+
4100
+        $originalShare = $this->manager->newShare();
4101
+        $originalShare->setShareType(IShare::TYPE_EMAIL)
4102
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL)
4103
+            ->setPassword('passwordHash')
4104
+            ->setSendPasswordByTalk(false);
4105
+
4106
+        $tomorrow = new \DateTime();
4107
+        $tomorrow->setTime(0, 0, 0);
4108
+        $tomorrow->add(new \DateInterval('P1D'));
4109
+
4110
+        $file = $this->createMock(File::class);
4111
+        $file->method('getId')->willReturn(100);
4112
+
4113
+        $share = $this->manager->newShare();
4114
+        $share->setProviderId('foo')
4115
+            ->setId('42')
4116
+            ->setShareType(IShare::TYPE_EMAIL)
4117
+            ->setToken('token')
4118
+            ->setSharedBy('owner')
4119
+            ->setShareOwner('owner')
4120
+            ->setPassword('')
4121
+            ->setSendPasswordByTalk(true)
4122
+            ->setExpirationDate($tomorrow)
4123
+            ->setNode($file)
4124
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
4125
+
4126
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
4127
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4128
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
4129
+        $manager->expects($this->once())->method('verifyPassword');
4130
+        $manager->expects($this->never())->method('pathCreateChecks');
4131
+        $manager->expects($this->once())->method('linkCreateChecks');
4132
+        $manager->expects($this->never())->method('validateExpirationDateLink');
4133
+
4134
+        // If the password is empty, we have nothing to hash
4135
+        $this->hasher->expects($this->never())
4136
+            ->method('hash');
4137
+
4138
+        $this->defaultProvider->expects($this->never())
4139
+            ->method('update');
4140
+
4141
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4142
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4143
+        $hookListener->expects($this->never())->method('post');
4144
+
4145
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4146
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4147
+        $hookListener2->expects($this->never())->method('post');
4148
+
4149
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4150
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4151
+        $hookListener3->expects($this->never())->method('post');
4152
+
4153
+        $manager->updateShare($share);
4154
+    }
4155
+
4156
+
4157
+    public function testUpdateShareMailEnableSendPasswordByTalkWithPreviousPassword(): void {
4158
+        $this->expectException(\InvalidArgumentException::class);
4159
+        $this->expectExceptionMessage('Cannot enable sending the password by Talk without setting a new password');
4160
+
4161
+        $manager = $this->createManagerMock()
4162
+            ->setMethods([
4163
+                'canShare',
4164
+                'getShareById',
4165
+                'generalCreateChecks',
4166
+                'verifyPassword',
4167
+                'pathCreateChecks',
4168
+                'linkCreateChecks',
4169
+                'validateExpirationDateLink',
4170
+            ])
4171
+            ->getMock();
4172
+
4173
+        $originalShare = $this->manager->newShare();
4174
+        $originalShare->setShareType(IShare::TYPE_EMAIL)
4175
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL)
4176
+            ->setPassword('password')
4177
+            ->setSendPasswordByTalk(false);
4178
+
4179
+        $tomorrow = new \DateTime();
4180
+        $tomorrow->setTime(0, 0, 0);
4181
+        $tomorrow->add(new \DateInterval('P1D'));
4182
+
4183
+        $file = $this->createMock(File::class);
4184
+        $file->method('getId')->willReturn(100);
4185
+
4186
+        $share = $this->manager->newShare();
4187
+        $share->setProviderId('foo')
4188
+            ->setId('42')
4189
+            ->setShareType(IShare::TYPE_EMAIL)
4190
+            ->setToken('token')
4191
+            ->setSharedBy('owner')
4192
+            ->setShareOwner('owner')
4193
+            ->setPassword('password')
4194
+            ->setSendPasswordByTalk(true)
4195
+            ->setExpirationDate($tomorrow)
4196
+            ->setNode($file)
4197
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
4198
+
4199
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
4200
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4201
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
4202
+        $manager->expects($this->never())->method('verifyPassword');
4203
+        $manager->expects($this->never())->method('pathCreateChecks');
4204
+        $manager->expects($this->once())->method('linkCreateChecks');
4205
+        $manager->expects($this->never())->method('validateExpirationDateLink');
4206
+
4207
+        // If the old & new passwords are the same, we don't do anything
4208
+        $this->hasher->expects($this->never())
4209
+            ->method('verify');
4210
+        $this->hasher->expects($this->never())
4211
+            ->method('hash');
4212
+
4213
+        $this->defaultProvider->expects($this->never())
4214
+            ->method('update');
4215
+
4216
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4217
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4218
+        $hookListener->expects($this->never())->method('post');
4219
+
4220
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4221
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4222
+        $hookListener2->expects($this->never())->method('post');
4223
+
4224
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4225
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4226
+        $hookListener3->expects($this->never())->method('post');
4227
+
4228
+        $manager->updateShare($share);
4229
+    }
4230
+
4231
+    public function testUpdateShareMailDisableSendPasswordByTalkWithPreviousPassword(): void {
4232
+        $this->expectException(\InvalidArgumentException::class);
4233
+        $this->expectExceptionMessage('Cannot disable sending the password by Talk without setting a new password');
4234
+
4235
+        $manager = $this->createManagerMock()
4236
+            ->setMethods([
4237
+                'canShare',
4238
+                'getShareById',
4239
+                'generalCreateChecks',
4240
+                'verifyPassword',
4241
+                'pathCreateChecks',
4242
+                'linkCreateChecks',
4243
+                'validateExpirationDateLink',
4244
+            ])
4245
+            ->getMock();
4246
+
4247
+        $originalShare = $this->manager->newShare();
4248
+        $originalShare->setShareType(IShare::TYPE_EMAIL)
4249
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL)
4250
+            ->setPassword('passwordHash')
4251
+            ->setSendPasswordByTalk(true);
4252
+
4253
+        $tomorrow = new \DateTime();
4254
+        $tomorrow->setTime(0, 0, 0);
4255
+        $tomorrow->add(new \DateInterval('P1D'));
4256
+
4257
+        $file = $this->createMock(File::class);
4258
+        $file->method('getId')->willReturn(100);
4259
+
4260
+        $share = $this->manager->newShare();
4261
+        $share->setProviderId('foo')
4262
+            ->setId('42')
4263
+            ->setShareType(IShare::TYPE_EMAIL)
4264
+            ->setToken('token')
4265
+            ->setSharedBy('owner')
4266
+            ->setShareOwner('owner')
4267
+            ->setPassword('passwordHash')
4268
+            ->setSendPasswordByTalk(false)
4269
+            ->setExpirationDate($tomorrow)
4270
+            ->setNode($file)
4271
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
4272
+
4273
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
4274
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4275
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
4276
+        $manager->expects($this->never())->method('verifyPassword');
4277
+        $manager->expects($this->never())->method('pathCreateChecks');
4278
+        $manager->expects($this->once())->method('linkCreateChecks');
4279
+        $manager->expects($this->never())->method('validateExpirationDateLink');
4280
+
4281
+        // If the old & new passwords are the same, we don't do anything
4282
+        $this->hasher->expects($this->never())
4283
+            ->method('verify');
4284
+        $this->hasher->expects($this->never())
4285
+            ->method('hash');
4286
+
4287
+        $this->defaultProvider->expects($this->never())
4288
+            ->method('update');
4289
+
4290
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4291
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4292
+        $hookListener->expects($this->never())->method('post');
4293
+
4294
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4295
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4296
+        $hookListener2->expects($this->never())->method('post');
4297
+
4298
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4299
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4300
+        $hookListener3->expects($this->never())->method('post');
4301
+
4302
+        $manager->updateShare($share);
4303
+    }
4304
+
4305
+    public function testUpdateShareMailDisableSendPasswordByTalkWithoutChangingPassword(): void {
4306
+        $this->expectException(\InvalidArgumentException::class);
4307
+        $this->expectExceptionMessage('Cannot disable sending the password by Talk without setting a new password');
4308
+
4309
+        $manager = $this->createManagerMock()
4310
+            ->setMethods([
4311
+                'canShare',
4312
+                'getShareById',
4313
+                'generalCreateChecks',
4314
+                'verifyPassword',
4315
+                'pathCreateChecks',
4316
+                'linkCreateChecks',
4317
+                'validateExpirationDateLink',
4318
+            ])
4319
+            ->getMock();
4320
+
4321
+        $originalShare = $this->manager->newShare();
4322
+        $originalShare->setShareType(IShare::TYPE_EMAIL)
4323
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL)
4324
+            ->setPassword('passwordHash')
4325
+            ->setSendPasswordByTalk(true);
4326
+
4327
+        $tomorrow = new \DateTime();
4328
+        $tomorrow->setTime(0, 0, 0);
4329
+        $tomorrow->add(new \DateInterval('P1D'));
4330
+
4331
+        $file = $this->createMock(File::class);
4332
+        $file->method('getId')->willReturn(100);
4333
+
4334
+        $share = $this->manager->newShare();
4335
+        $share->setProviderId('foo')
4336
+            ->setId('42')
4337
+            ->setShareType(IShare::TYPE_EMAIL)
4338
+            ->setToken('token')
4339
+            ->setSharedBy('owner')
4340
+            ->setShareOwner('owner')
4341
+            ->setPassword('passwordHash')
4342
+            ->setSendPasswordByTalk(false)
4343
+            ->setExpirationDate($tomorrow)
4344
+            ->setNode($file)
4345
+            ->setPermissions(\OCP\Constants::PERMISSION_ALL);
4346
+
4347
+        $manager->expects($this->once())->method('canShare')->willReturn(true);
4348
+        $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
4349
+        $manager->expects($this->once())->method('generalCreateChecks')->with($share);
4350
+        $manager->expects($this->never())->method('verifyPassword');
4351
+        $manager->expects($this->never())->method('pathCreateChecks');
4352
+        $manager->expects($this->once())->method('linkCreateChecks');
4353
+        $manager->expects($this->never())->method('validateExpirationDateLink');
4354
+
4355
+        // If the old & new passwords are the same, we don't do anything
4356
+        $this->hasher->expects($this->never())
4357
+            ->method('verify');
4358
+        $this->hasher->expects($this->never())
4359
+            ->method('hash');
4360
+
4361
+        $this->defaultProvider->expects($this->never())
4362
+            ->method('update');
4363
+
4364
+        $hookListener = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4365
+        \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListener, 'post');
4366
+        $hookListener->expects($this->never())->method('post');
4367 4367
 
4368
-		$hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4369
-		\OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4370
-		$hookListener2->expects($this->never())->method('post');
4368
+        $hookListener2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4369
+        \OCP\Util::connectHook('OCP\Share', 'post_update_password', $hookListener2, 'post');
4370
+        $hookListener2->expects($this->never())->method('post');
4371 4371
 
4372
-		$hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4373
-		\OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4374
-		$hookListener3->expects($this->never())->method('post');
4372
+        $hookListener3 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock();
4373
+        \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListener3, 'post');
4374
+        $hookListener3->expects($this->never())->method('post');
4375 4375
 
4376
-		$manager->updateShare($share);
4377
-	}
4376
+        $manager->updateShare($share);
4377
+    }
4378 4378
 
4379
-	public function testMoveShareLink(): void {
4380
-		$this->expectException(\InvalidArgumentException::class);
4381
-		$this->expectExceptionMessage('Cannot change target of link share');
4379
+    public function testMoveShareLink(): void {
4380
+        $this->expectException(\InvalidArgumentException::class);
4381
+        $this->expectExceptionMessage('Cannot change target of link share');
4382 4382
 
4383
-		$share = $this->manager->newShare();
4384
-		$share->setShareType(IShare::TYPE_LINK);
4383
+        $share = $this->manager->newShare();
4384
+        $share->setShareType(IShare::TYPE_LINK);
4385 4385
 
4386
-		$recipient = $this->createMock(IUser::class);
4386
+        $recipient = $this->createMock(IUser::class);
4387 4387
 
4388
-		$this->manager->moveShare($share, $recipient);
4389
-	}
4388
+        $this->manager->moveShare($share, $recipient);
4389
+    }
4390 4390
 
4391 4391
 
4392
-	public function testMoveShareUserNotRecipient(): void {
4393
-		$this->expectException(\InvalidArgumentException::class);
4394
-		$this->expectExceptionMessage('Invalid share recipient');
4392
+    public function testMoveShareUserNotRecipient(): void {
4393
+        $this->expectException(\InvalidArgumentException::class);
4394
+        $this->expectExceptionMessage('Invalid share recipient');
4395 4395
 
4396
-		$share = $this->manager->newShare();
4397
-		$share->setShareType(IShare::TYPE_USER);
4398
-
4399
-		$share->setSharedWith('sharedWith');
4400
-
4401
-		$this->manager->moveShare($share, 'recipient');
4402
-	}
4403
-
4404
-	public function testMoveShareUser(): void {
4405
-		$share = $this->manager->newShare();
4406
-		$share->setShareType(IShare::TYPE_USER)
4407
-			->setId('42')
4408
-			->setProviderId('foo');
4409
-
4410
-		$share->setSharedWith('recipient');
4396
+        $share = $this->manager->newShare();
4397
+        $share->setShareType(IShare::TYPE_USER);
4398
+
4399
+        $share->setSharedWith('sharedWith');
4400
+
4401
+        $this->manager->moveShare($share, 'recipient');
4402
+    }
4403
+
4404
+    public function testMoveShareUser(): void {
4405
+        $share = $this->manager->newShare();
4406
+        $share->setShareType(IShare::TYPE_USER)
4407
+            ->setId('42')
4408
+            ->setProviderId('foo');
4409
+
4410
+        $share->setSharedWith('recipient');
4411 4411
 
4412
-		$this->defaultProvider->method('move')->with($share, 'recipient')->willReturnArgument(0);
4412
+        $this->defaultProvider->method('move')->with($share, 'recipient')->willReturnArgument(0);
4413 4413
 
4414
-		$this->manager->moveShare($share, 'recipient');
4415
-		$this->addToAssertionCount(1);
4416
-	}
4414
+        $this->manager->moveShare($share, 'recipient');
4415
+        $this->addToAssertionCount(1);
4416
+    }
4417 4417
 
4418 4418
 
4419
-	public function testMoveShareGroupNotRecipient(): void {
4420
-		$this->expectException(\InvalidArgumentException::class);
4421
-		$this->expectExceptionMessage('Invalid share recipient');
4419
+    public function testMoveShareGroupNotRecipient(): void {
4420
+        $this->expectException(\InvalidArgumentException::class);
4421
+        $this->expectExceptionMessage('Invalid share recipient');
4422 4422
 
4423
-		$share = $this->manager->newShare();
4424
-		$share->setShareType(IShare::TYPE_GROUP);
4423
+        $share = $this->manager->newShare();
4424
+        $share->setShareType(IShare::TYPE_GROUP);
4425 4425
 
4426
-		$sharedWith = $this->createMock(IGroup::class);
4427
-		$share->setSharedWith('shareWith');
4426
+        $sharedWith = $this->createMock(IGroup::class);
4427
+        $share->setSharedWith('shareWith');
4428 4428
 
4429
-		$recipient = $this->createMock(IUser::class);
4430
-		$sharedWith->method('inGroup')->with($recipient)->willReturn(false);
4429
+        $recipient = $this->createMock(IUser::class);
4430
+        $sharedWith->method('inGroup')->with($recipient)->willReturn(false);
4431 4431
 
4432
-		$this->groupManager->method('get')->with('shareWith')->willReturn($sharedWith);
4433
-		$this->userManager->method('get')->with('recipient')->willReturn($recipient);
4434
-
4435
-		$this->manager->moveShare($share, 'recipient');
4436
-	}
4432
+        $this->groupManager->method('get')->with('shareWith')->willReturn($sharedWith);
4433
+        $this->userManager->method('get')->with('recipient')->willReturn($recipient);
4434
+
4435
+        $this->manager->moveShare($share, 'recipient');
4436
+    }
4437 4437
 
4438 4438
 
4439
-	public function testMoveShareGroupNull(): void {
4440
-		$this->expectException(\InvalidArgumentException::class);
4441
-		$this->expectExceptionMessage('Group "shareWith" does not exist');
4439
+    public function testMoveShareGroupNull(): void {
4440
+        $this->expectException(\InvalidArgumentException::class);
4441
+        $this->expectExceptionMessage('Group "shareWith" does not exist');
4442 4442
 
4443
-		$share = $this->manager->newShare();
4444
-		$share->setShareType(IShare::TYPE_GROUP);
4445
-		$share->setSharedWith('shareWith');
4443
+        $share = $this->manager->newShare();
4444
+        $share->setShareType(IShare::TYPE_GROUP);
4445
+        $share->setSharedWith('shareWith');
4446 4446
 
4447
-		$recipient = $this->createMock(IUser::class);
4447
+        $recipient = $this->createMock(IUser::class);
4448 4448
 
4449
-		$this->groupManager->method('get')->with('shareWith')->willReturn(null);
4450
-		$this->userManager->method('get')->with('recipient')->willReturn($recipient);
4449
+        $this->groupManager->method('get')->with('shareWith')->willReturn(null);
4450
+        $this->userManager->method('get')->with('recipient')->willReturn($recipient);
4451 4451
 
4452
-		$this->manager->moveShare($share, 'recipient');
4453
-	}
4452
+        $this->manager->moveShare($share, 'recipient');
4453
+    }
4454 4454
 
4455
-	public function testMoveShareGroup(): void {
4456
-		$share = $this->manager->newShare();
4457
-		$share->setShareType(IShare::TYPE_GROUP)
4458
-			->setId('42')
4459
-			->setProviderId('foo');
4455
+    public function testMoveShareGroup(): void {
4456
+        $share = $this->manager->newShare();
4457
+        $share->setShareType(IShare::TYPE_GROUP)
4458
+            ->setId('42')
4459
+            ->setProviderId('foo');
4460 4460
 
4461
-		$group = $this->createMock(IGroup::class);
4462
-		$share->setSharedWith('group');
4461
+        $group = $this->createMock(IGroup::class);
4462
+        $share->setSharedWith('group');
4463 4463
 
4464
-		$recipient = $this->createMock(IUser::class);
4465
-		$group->method('inGroup')->with($recipient)->willReturn(true);
4464
+        $recipient = $this->createMock(IUser::class);
4465
+        $group->method('inGroup')->with($recipient)->willReturn(true);
4466 4466
 
4467
-		$this->groupManager->method('get')->with('group')->willReturn($group);
4468
-		$this->userManager->method('get')->with('recipient')->willReturn($recipient);
4467
+        $this->groupManager->method('get')->with('group')->willReturn($group);
4468
+        $this->userManager->method('get')->with('recipient')->willReturn($recipient);
4469 4469
 
4470
-		$this->defaultProvider->method('move')->with($share, 'recipient')->willReturnArgument(0);
4470
+        $this->defaultProvider->method('move')->with($share, 'recipient')->willReturnArgument(0);
4471 4471
 
4472
-		$this->manager->moveShare($share, 'recipient');
4473
-		$this->addToAssertionCount(1);
4474
-	}
4472
+        $this->manager->moveShare($share, 'recipient');
4473
+        $this->addToAssertionCount(1);
4474
+    }
4475 4475
 
4476
-	/**
4477
-	 * @dataProvider dataTestShareProviderExists
4478
-	 */
4479
-	public function testShareProviderExists($shareType, $expected): void {
4480
-		$factory = $this->getMockBuilder('OCP\Share\IProviderFactory')->getMock();
4481
-		$factory->expects($this->any())->method('getProviderForType')
4482
-			->willReturnCallback(function ($id) {
4483
-				if ($id === IShare::TYPE_USER) {
4484
-					return true;
4485
-				}
4486
-				throw new Exception\ProviderException();
4487
-			});
4476
+    /**
4477
+     * @dataProvider dataTestShareProviderExists
4478
+     */
4479
+    public function testShareProviderExists($shareType, $expected): void {
4480
+        $factory = $this->getMockBuilder('OCP\Share\IProviderFactory')->getMock();
4481
+        $factory->expects($this->any())->method('getProviderForType')
4482
+            ->willReturnCallback(function ($id) {
4483
+                if ($id === IShare::TYPE_USER) {
4484
+                    return true;
4485
+                }
4486
+                throw new Exception\ProviderException();
4487
+            });
4488 4488
 
4489
-		$manager = $this->createManager($factory);
4490
-		$this->assertSame($expected,
4491
-			$manager->shareProviderExists($shareType)
4492
-		);
4493
-	}
4489
+        $manager = $this->createManager($factory);
4490
+        $this->assertSame($expected,
4491
+            $manager->shareProviderExists($shareType)
4492
+        );
4493
+    }
4494 4494
 
4495
-	public function dataTestShareProviderExists() {
4496
-		return [
4497
-			[IShare::TYPE_USER, true],
4498
-			[42, false],
4499
-		];
4500
-	}
4495
+    public function dataTestShareProviderExists() {
4496
+        return [
4497
+            [IShare::TYPE_USER, true],
4498
+            [42, false],
4499
+        ];
4500
+    }
4501 4501
 
4502
-	public function testGetSharesInFolder(): void {
4503
-		$factory = new DummyFactory2($this->createMock(IServerContainer::class));
4502
+    public function testGetSharesInFolder(): void {
4503
+        $factory = new DummyFactory2($this->createMock(IServerContainer::class));
4504 4504
 
4505
-		$manager = $this->createManager($factory);
4505
+        $manager = $this->createManager($factory);
4506 4506
 
4507
-		$factory->setProvider($this->defaultProvider);
4508
-		$extraProvider = $this->createMock(IShareProvider::class);
4509
-		$factory->setSecondProvider($extraProvider);
4507
+        $factory->setProvider($this->defaultProvider);
4508
+        $extraProvider = $this->createMock(IShareProvider::class);
4509
+        $factory->setSecondProvider($extraProvider);
4510 4510
 
4511
-		$share1 = $this->createMock(IShare::class);
4512
-		$share2 = $this->createMock(IShare::class);
4513
-		$share3 = $this->createMock(IShare::class);
4514
-		$share4 = $this->createMock(IShare::class);
4515
-
4516
-		$folder = $this->createMock(Folder::class);
4511
+        $share1 = $this->createMock(IShare::class);
4512
+        $share2 = $this->createMock(IShare::class);
4513
+        $share3 = $this->createMock(IShare::class);
4514
+        $share4 = $this->createMock(IShare::class);
4515
+
4516
+        $folder = $this->createMock(Folder::class);
4517 4517
 
4518
-		$this->defaultProvider->method('getSharesInFolder')
4519
-			->with(
4520
-				$this->equalTo('user'),
4521
-				$this->equalTo($folder),
4522
-				$this->equalTo(false)
4523
-			)->willReturn([
4524
-				1 => [$share1],
4525
-				2 => [$share2],
4526
-			]);
4527
-
4528
-		$extraProvider->method('getSharesInFolder')
4529
-			->with(
4530
-				$this->equalTo('user'),
4531
-				$this->equalTo($folder),
4532
-				$this->equalTo(false)
4533
-			)->willReturn([
4534
-				2 => [$share3],
4535
-				3 => [$share4],
4536
-			]);
4537
-
4538
-		$result = $manager->getSharesInFolder('user', $folder, false);
4539
-
4540
-		$expects = [
4541
-			1 => [$share1],
4542
-			2 => [$share2, $share3],
4543
-			3 => [$share4],
4544
-		];
4545
-
4546
-		$this->assertSame($expects, $result);
4547
-	}
4548
-
4549
-	public function testGetSharesInFolderOwnerless(): void {
4550
-		$factory = new DummyFactory2($this->createMock(IServerContainer::class));
4551
-
4552
-		$manager = $this->createManager($factory);
4553
-
4554
-		$factory->setProvider($this->defaultProvider);
4555
-		$extraProvider = $this->createMock(IShareProvider::class);
4556
-		$factory->setSecondProvider($extraProvider);
4557
-
4558
-		$share1 = $this->createMock(IShare::class);
4559
-		$share2 = $this->createMock(IShare::class);
4560
-
4561
-		$mount = $this->createMock(IShareOwnerlessMount::class);
4562
-
4563
-		$file = $this->createMock(File::class);
4564
-		$file
4565
-			->method('getId')
4566
-			->willReturn(1);
4567
-
4568
-		$folder = $this->createMock(Folder::class);
4569
-		$folder
4570
-			->method('getMountPoint')
4571
-			->willReturn($mount);
4572
-		$folder
4573
-			->method('getDirectoryListing')
4574
-			->willReturn([$file]);
4575
-
4576
-		$this->defaultProvider
4577
-			->method('getSharesByPath')
4578
-			->with($file)
4579
-			->willReturn([$share1]);
4580
-
4581
-		$extraProvider
4582
-			->method('getSharesByPath')
4583
-			->with($file)
4584
-			->willReturn([$share2]);
4585
-
4586
-		$this->assertSame([
4587
-			1 => [$share1, $share2],
4588
-		], $manager->getSharesInFolder('user', $folder));
4589
-	}
4590
-
4591
-
4592
-	public function testGetAccessList(): void {
4593
-		$factory = new DummyFactory2($this->createMock(IServerContainer::class));
4594
-
4595
-		$manager = $this->createManager($factory);
4596
-
4597
-		$factory->setProvider($this->defaultProvider);
4598
-		$extraProvider = $this->createMock(IShareProvider::class);
4599
-		$factory->setSecondProvider($extraProvider);
4600
-
4601
-		$nodeOwner = $this->createMock(IUser::class);
4602
-		$nodeOwner->expects($this->once())
4603
-			->method('getUID')
4604
-			->willReturn('user1');
4605
-
4606
-		$node = $this->createMock(Node::class);
4607
-		$node->expects($this->once())
4608
-			->method('getOwner')
4609
-			->willReturn($nodeOwner);
4610
-		$node->method('getId')
4611
-			->willReturn(42);
4612
-
4613
-		$userFolder = $this->createMock(Folder::class);
4614
-		$file = $this->createMock(File::class);
4615
-		$folder = $this->createMock(Folder::class);
4616
-
4617
-		$owner = $this->createMock(IUser::class);
4618
-		$owner->expects($this->once())
4619
-			->method('getUID')
4620
-			->willReturn('owner');
4621
-
4622
-		$file->method('getParent')
4623
-			->willReturn($folder);
4624
-		$file->method('getPath')
4625
-			->willReturn('/owner/files/folder/file');
4626
-		$file->method('getOwner')
4627
-			->willReturn($owner);
4628
-		$file->method('getId')
4629
-			->willReturn(23);
4630
-		$folder->method('getParent')
4631
-			->willReturn($userFolder);
4632
-		$folder->method('getPath')
4633
-			->willReturn('/owner/files/folder');
4634
-		$userFolder->method('getFirstNodeById')
4635
-			->with($this->equalTo(42))
4636
-			->willReturn($file);
4637
-		$userFolder->method('getPath')
4638
-			->willReturn('/user1/files');
4639
-
4640
-		$this->userManager->method('userExists')
4641
-			->with($this->equalTo('user1'))
4642
-			->willReturn(true);
4643
-
4644
-		$this->defaultProvider->method('getAccessList')
4645
-			->with(
4646
-				$this->equalTo([$file, $folder]),
4647
-				false
4648
-			)
4649
-			->willReturn([
4650
-				'users' => [
4651
-					'user1',
4652
-					'user2',
4653
-					'user3',
4654
-					'123456',
4655
-				],
4656
-				'public' => true,
4657
-			]);
4658
-
4659
-		$extraProvider->method('getAccessList')
4660
-			->with(
4661
-				$this->equalTo([$file, $folder]),
4662
-				false
4663
-			)
4664
-			->willReturn([
4665
-				'users' => [
4666
-					'user3',
4667
-					'user4',
4668
-					'user5',
4669
-					'234567',
4670
-				],
4671
-				'remote' => true,
4672
-			]);
4673
-
4674
-		$this->rootFolder->method('getUserFolder')
4675
-			->with($this->equalTo('user1'))
4676
-			->willReturn($userFolder);
4677
-
4678
-		$expected = [
4679
-			'users' => ['owner', 'user1', 'user2', 'user3', '123456','user4', 'user5', '234567'],
4680
-			'remote' => true,
4681
-			'public' => true,
4682
-		];
4683
-
4684
-		$result = $manager->getAccessList($node, true, false);
4685
-
4686
-		$this->assertSame($expected['public'], $result['public']);
4687
-		$this->assertSame($expected['remote'], $result['remote']);
4688
-		$this->assertSame($expected['users'], $result['users']);
4689
-	}
4690
-
4691
-	public function testGetAccessListWithCurrentAccess(): void {
4692
-		$factory = new DummyFactory2($this->createMock(IServerContainer::class));
4693
-
4694
-		$manager = $this->createManager($factory);
4695
-
4696
-		$factory->setProvider($this->defaultProvider);
4697
-		$extraProvider = $this->createMock(IShareProvider::class);
4698
-		$factory->setSecondProvider($extraProvider);
4699
-
4700
-		$nodeOwner = $this->createMock(IUser::class);
4701
-		$nodeOwner->expects($this->once())
4702
-			->method('getUID')
4703
-			->willReturn('user1');
4704
-
4705
-		$node = $this->createMock(Node::class);
4706
-		$node->expects($this->once())
4707
-			->method('getOwner')
4708
-			->willReturn($nodeOwner);
4709
-		$node->method('getId')
4710
-			->willReturn(42);
4711
-
4712
-		$userFolder = $this->createMock(Folder::class);
4713
-		$file = $this->createMock(File::class);
4714
-
4715
-		$owner = $this->createMock(IUser::class);
4716
-		$owner->expects($this->once())
4717
-			->method('getUID')
4718
-			->willReturn('owner');
4719
-		$folder = $this->createMock(Folder::class);
4720
-
4721
-		$file->method('getParent')
4722
-			->willReturn($folder);
4723
-		$file->method('getPath')
4724
-			->willReturn('/owner/files/folder/file');
4725
-		$file->method('getOwner')
4726
-			->willReturn($owner);
4727
-		$file->method('getId')
4728
-			->willReturn(23);
4729
-		$folder->method('getParent')
4730
-			->willReturn($userFolder);
4731
-		$folder->method('getPath')
4732
-			->willReturn('/owner/files/folder');
4733
-		$userFolder->method('getFirstNodeById')
4734
-			->with($this->equalTo(42))
4735
-			->willReturn($file);
4736
-		$userFolder->method('getPath')
4737
-			->willReturn('/user1/files');
4738
-
4739
-		$this->userManager->method('userExists')
4740
-			->with($this->equalTo('user1'))
4741
-			->willReturn(true);
4742
-
4743
-		$this->defaultProvider->method('getAccessList')
4744
-			->with(
4745
-				$this->equalTo([$file, $folder]),
4746
-				true
4747
-			)
4748
-			->willReturn([
4749
-				'users' => [
4750
-					'user1' => [],
4751
-					'user2' => [],
4752
-					'user3' => [],
4753
-					'123456' => [],
4754
-				],
4755
-				'public' => true,
4756
-			]);
4757
-
4758
-		$extraProvider->method('getAccessList')
4759
-			->with(
4760
-				$this->equalTo([$file, $folder]),
4761
-				true
4762
-			)
4763
-			->willReturn([
4764
-				'users' => [
4765
-					'user3' => [],
4766
-					'user4' => [],
4767
-					'user5' => [],
4768
-					'234567' => [],
4769
-				],
4770
-				'remote' => [
4771
-					'remote1',
4772
-				],
4773
-			]);
4774
-
4775
-		$this->rootFolder->method('getUserFolder')
4776
-			->with($this->equalTo('user1'))
4777
-			->willReturn($userFolder);
4778
-
4779
-		$expected = [
4780
-			'users' => [
4781
-				'owner' => [
4782
-					'node_id' => 23,
4783
-					'node_path' => '/folder/file'
4784
-				]
4785
-				, 'user1' => [], 'user2' => [], 'user3' => [], '123456' => [], 'user4' => [], 'user5' => [], '234567' => []],
4786
-			'remote' => [
4787
-				'remote1',
4788
-			],
4789
-			'public' => true,
4790
-		];
4791
-
4792
-		$result = $manager->getAccessList($node, true, true);
4793
-
4794
-		$this->assertSame($expected['public'], $result['public']);
4795
-		$this->assertSame($expected['remote'], $result['remote']);
4796
-		$this->assertSame($expected['users'], $result['users']);
4797
-	}
4798
-
4799
-	public function testGetAllShares(): void {
4800
-		$factory = new DummyFactory2($this->createMock(IServerContainer::class));
4801
-
4802
-		$manager = $this->createManager($factory);
4803
-
4804
-		$factory->setProvider($this->defaultProvider);
4805
-		$extraProvider = $this->createMock(IShareProvider::class);
4806
-		$factory->setSecondProvider($extraProvider);
4807
-
4808
-		$share1 = $this->createMock(IShare::class);
4809
-		$share2 = $this->createMock(IShare::class);
4810
-		$share3 = $this->createMock(IShare::class);
4811
-		$share4 = $this->createMock(IShare::class);
4812
-
4813
-		$this->defaultProvider->method('getAllShares')
4814
-			->willReturnCallback(function () use ($share1, $share2) {
4815
-				yield $share1;
4816
-				yield $share2;
4817
-			});
4818
-		$extraProvider->method('getAllShares')
4819
-			->willReturnCallback(function () use ($share3, $share4) {
4820
-				yield $share3;
4821
-				yield $share4;
4822
-			});
4823
-
4824
-		// "yield from", used in "getAllShares()", does not reset the keys, so
4825
-		// "use_keys" has to be disabled to collect all the values while
4826
-		// ignoring the keys returned by the generator.
4827
-		$result = iterator_to_array($manager->getAllShares(), $use_keys = false);
4828
-
4829
-		$expects = [$share1, $share2, $share3, $share4];
4830
-
4831
-		$this->assertSame($expects, $result);
4832
-	}
4833
-
4834
-	public function dataCurrentUserCanEnumerateTargetUser(): array {
4835
-		return [
4836
-			'Full match guest' => [true, true, false, false, false, false, false, true],
4837
-			'Full match user' => [false, true, false, false, false, false, false, true],
4838
-			'Enumeration off guest' => [true, false, false, false, false, false, false, false],
4839
-			'Enumeration off user' => [false, false, false, false, false, false, false, false],
4840
-			'Enumeration guest' => [true, false, true, false, false, false, false, true],
4841
-			'Enumeration user' => [false, false, true, false, false, false, false, true],
4842
-
4843
-			// Restricted enumerations guests never works
4844
-			'Guest phone' => [true, false, true, true, false, false, false, false],
4845
-			'Guest group' => [true, false, true, false, true, false, false, false],
4846
-			'Guest both' => [true, false, true, true, true, false, false, false],
4847
-
4848
-			// Restricted enumerations users
4849
-			'User phone but not known' => [false, false, true, true, false, false, false, false],
4850
-			'User phone known' => [false, false, true, true, false, true, false, true],
4851
-			'User group but no match' => [false, false, true, false, true, false, false, false],
4852
-			'User group with match' => [false, false, true, false, true, false, true, true],
4853
-		];
4854
-	}
4855
-
4856
-	/**
4857
-	 * @dataProvider dataCurrentUserCanEnumerateTargetUser
4858
-	 * @param bool $expected
4859
-	 */
4860
-	public function testCurrentUserCanEnumerateTargetUser(bool $currentUserIsGuest, bool $allowEnumerationFullMatch, bool $allowEnumeration, bool $limitEnumerationToPhone, bool $limitEnumerationToGroups, bool $isKnownToUser, bool $haveCommonGroup, bool $expected): void {
4861
-		/** @var IManager|MockObject $manager */
4862
-		$manager = $this->createManagerMock()
4863
-			->setMethods([
4864
-				'allowEnumerationFullMatch',
4865
-				'allowEnumeration',
4866
-				'limitEnumerationToPhone',
4867
-				'limitEnumerationToGroups',
4868
-			])
4869
-			->getMock();
4870
-
4871
-		$manager->method('allowEnumerationFullMatch')
4872
-			->willReturn($allowEnumerationFullMatch);
4873
-		$manager->method('allowEnumeration')
4874
-			->willReturn($allowEnumeration);
4875
-		$manager->method('limitEnumerationToPhone')
4876
-			->willReturn($limitEnumerationToPhone);
4877
-		$manager->method('limitEnumerationToGroups')
4878
-			->willReturn($limitEnumerationToGroups);
4879
-
4880
-		$this->knownUserService->method('isKnownToUser')
4881
-			->with('current', 'target')
4882
-			->willReturn($isKnownToUser);
4883
-
4884
-		$currentUser = null;
4885
-		if (!$currentUserIsGuest) {
4886
-			$currentUser = $this->createMock(IUser::class);
4887
-			$currentUser->method('getUID')
4888
-				->willReturn('current');
4889
-		}
4890
-		$targetUser = $this->createMock(IUser::class);
4891
-		$targetUser->method('getUID')
4892
-			->willReturn('target');
4893
-
4894
-		if ($haveCommonGroup) {
4895
-			$this->groupManager->method('getUserGroupIds')
4896
-				->willReturnMap([
4897
-					[$targetUser, ['gid1', 'gid2']],
4898
-					[$currentUser, ['gid2', 'gid3']],
4899
-				]);
4900
-		} else {
4901
-			$this->groupManager->method('getUserGroupIds')
4902
-				->willReturnMap([
4903
-					[$targetUser, ['gid1', 'gid2']],
4904
-					[$currentUser, ['gid3', 'gid4']],
4905
-				]);
4906
-		}
4907
-
4908
-		$this->assertSame($expected, $manager->currentUserCanEnumerateTargetUser($currentUser, $targetUser));
4909
-	}
4518
+        $this->defaultProvider->method('getSharesInFolder')
4519
+            ->with(
4520
+                $this->equalTo('user'),
4521
+                $this->equalTo($folder),
4522
+                $this->equalTo(false)
4523
+            )->willReturn([
4524
+                1 => [$share1],
4525
+                2 => [$share2],
4526
+            ]);
4527
+
4528
+        $extraProvider->method('getSharesInFolder')
4529
+            ->with(
4530
+                $this->equalTo('user'),
4531
+                $this->equalTo($folder),
4532
+                $this->equalTo(false)
4533
+            )->willReturn([
4534
+                2 => [$share3],
4535
+                3 => [$share4],
4536
+            ]);
4537
+
4538
+        $result = $manager->getSharesInFolder('user', $folder, false);
4539
+
4540
+        $expects = [
4541
+            1 => [$share1],
4542
+            2 => [$share2, $share3],
4543
+            3 => [$share4],
4544
+        ];
4545
+
4546
+        $this->assertSame($expects, $result);
4547
+    }
4548
+
4549
+    public function testGetSharesInFolderOwnerless(): void {
4550
+        $factory = new DummyFactory2($this->createMock(IServerContainer::class));
4551
+
4552
+        $manager = $this->createManager($factory);
4553
+
4554
+        $factory->setProvider($this->defaultProvider);
4555
+        $extraProvider = $this->createMock(IShareProvider::class);
4556
+        $factory->setSecondProvider($extraProvider);
4557
+
4558
+        $share1 = $this->createMock(IShare::class);
4559
+        $share2 = $this->createMock(IShare::class);
4560
+
4561
+        $mount = $this->createMock(IShareOwnerlessMount::class);
4562
+
4563
+        $file = $this->createMock(File::class);
4564
+        $file
4565
+            ->method('getId')
4566
+            ->willReturn(1);
4567
+
4568
+        $folder = $this->createMock(Folder::class);
4569
+        $folder
4570
+            ->method('getMountPoint')
4571
+            ->willReturn($mount);
4572
+        $folder
4573
+            ->method('getDirectoryListing')
4574
+            ->willReturn([$file]);
4575
+
4576
+        $this->defaultProvider
4577
+            ->method('getSharesByPath')
4578
+            ->with($file)
4579
+            ->willReturn([$share1]);
4580
+
4581
+        $extraProvider
4582
+            ->method('getSharesByPath')
4583
+            ->with($file)
4584
+            ->willReturn([$share2]);
4585
+
4586
+        $this->assertSame([
4587
+            1 => [$share1, $share2],
4588
+        ], $manager->getSharesInFolder('user', $folder));
4589
+    }
4590
+
4591
+
4592
+    public function testGetAccessList(): void {
4593
+        $factory = new DummyFactory2($this->createMock(IServerContainer::class));
4594
+
4595
+        $manager = $this->createManager($factory);
4596
+
4597
+        $factory->setProvider($this->defaultProvider);
4598
+        $extraProvider = $this->createMock(IShareProvider::class);
4599
+        $factory->setSecondProvider($extraProvider);
4600
+
4601
+        $nodeOwner = $this->createMock(IUser::class);
4602
+        $nodeOwner->expects($this->once())
4603
+            ->method('getUID')
4604
+            ->willReturn('user1');
4605
+
4606
+        $node = $this->createMock(Node::class);
4607
+        $node->expects($this->once())
4608
+            ->method('getOwner')
4609
+            ->willReturn($nodeOwner);
4610
+        $node->method('getId')
4611
+            ->willReturn(42);
4612
+
4613
+        $userFolder = $this->createMock(Folder::class);
4614
+        $file = $this->createMock(File::class);
4615
+        $folder = $this->createMock(Folder::class);
4616
+
4617
+        $owner = $this->createMock(IUser::class);
4618
+        $owner->expects($this->once())
4619
+            ->method('getUID')
4620
+            ->willReturn('owner');
4621
+
4622
+        $file->method('getParent')
4623
+            ->willReturn($folder);
4624
+        $file->method('getPath')
4625
+            ->willReturn('/owner/files/folder/file');
4626
+        $file->method('getOwner')
4627
+            ->willReturn($owner);
4628
+        $file->method('getId')
4629
+            ->willReturn(23);
4630
+        $folder->method('getParent')
4631
+            ->willReturn($userFolder);
4632
+        $folder->method('getPath')
4633
+            ->willReturn('/owner/files/folder');
4634
+        $userFolder->method('getFirstNodeById')
4635
+            ->with($this->equalTo(42))
4636
+            ->willReturn($file);
4637
+        $userFolder->method('getPath')
4638
+            ->willReturn('/user1/files');
4639
+
4640
+        $this->userManager->method('userExists')
4641
+            ->with($this->equalTo('user1'))
4642
+            ->willReturn(true);
4643
+
4644
+        $this->defaultProvider->method('getAccessList')
4645
+            ->with(
4646
+                $this->equalTo([$file, $folder]),
4647
+                false
4648
+            )
4649
+            ->willReturn([
4650
+                'users' => [
4651
+                    'user1',
4652
+                    'user2',
4653
+                    'user3',
4654
+                    '123456',
4655
+                ],
4656
+                'public' => true,
4657
+            ]);
4658
+
4659
+        $extraProvider->method('getAccessList')
4660
+            ->with(
4661
+                $this->equalTo([$file, $folder]),
4662
+                false
4663
+            )
4664
+            ->willReturn([
4665
+                'users' => [
4666
+                    'user3',
4667
+                    'user4',
4668
+                    'user5',
4669
+                    '234567',
4670
+                ],
4671
+                'remote' => true,
4672
+            ]);
4673
+
4674
+        $this->rootFolder->method('getUserFolder')
4675
+            ->with($this->equalTo('user1'))
4676
+            ->willReturn($userFolder);
4677
+
4678
+        $expected = [
4679
+            'users' => ['owner', 'user1', 'user2', 'user3', '123456','user4', 'user5', '234567'],
4680
+            'remote' => true,
4681
+            'public' => true,
4682
+        ];
4683
+
4684
+        $result = $manager->getAccessList($node, true, false);
4685
+
4686
+        $this->assertSame($expected['public'], $result['public']);
4687
+        $this->assertSame($expected['remote'], $result['remote']);
4688
+        $this->assertSame($expected['users'], $result['users']);
4689
+    }
4690
+
4691
+    public function testGetAccessListWithCurrentAccess(): void {
4692
+        $factory = new DummyFactory2($this->createMock(IServerContainer::class));
4693
+
4694
+        $manager = $this->createManager($factory);
4695
+
4696
+        $factory->setProvider($this->defaultProvider);
4697
+        $extraProvider = $this->createMock(IShareProvider::class);
4698
+        $factory->setSecondProvider($extraProvider);
4699
+
4700
+        $nodeOwner = $this->createMock(IUser::class);
4701
+        $nodeOwner->expects($this->once())
4702
+            ->method('getUID')
4703
+            ->willReturn('user1');
4704
+
4705
+        $node = $this->createMock(Node::class);
4706
+        $node->expects($this->once())
4707
+            ->method('getOwner')
4708
+            ->willReturn($nodeOwner);
4709
+        $node->method('getId')
4710
+            ->willReturn(42);
4711
+
4712
+        $userFolder = $this->createMock(Folder::class);
4713
+        $file = $this->createMock(File::class);
4714
+
4715
+        $owner = $this->createMock(IUser::class);
4716
+        $owner->expects($this->once())
4717
+            ->method('getUID')
4718
+            ->willReturn('owner');
4719
+        $folder = $this->createMock(Folder::class);
4720
+
4721
+        $file->method('getParent')
4722
+            ->willReturn($folder);
4723
+        $file->method('getPath')
4724
+            ->willReturn('/owner/files/folder/file');
4725
+        $file->method('getOwner')
4726
+            ->willReturn($owner);
4727
+        $file->method('getId')
4728
+            ->willReturn(23);
4729
+        $folder->method('getParent')
4730
+            ->willReturn($userFolder);
4731
+        $folder->method('getPath')
4732
+            ->willReturn('/owner/files/folder');
4733
+        $userFolder->method('getFirstNodeById')
4734
+            ->with($this->equalTo(42))
4735
+            ->willReturn($file);
4736
+        $userFolder->method('getPath')
4737
+            ->willReturn('/user1/files');
4738
+
4739
+        $this->userManager->method('userExists')
4740
+            ->with($this->equalTo('user1'))
4741
+            ->willReturn(true);
4742
+
4743
+        $this->defaultProvider->method('getAccessList')
4744
+            ->with(
4745
+                $this->equalTo([$file, $folder]),
4746
+                true
4747
+            )
4748
+            ->willReturn([
4749
+                'users' => [
4750
+                    'user1' => [],
4751
+                    'user2' => [],
4752
+                    'user3' => [],
4753
+                    '123456' => [],
4754
+                ],
4755
+                'public' => true,
4756
+            ]);
4757
+
4758
+        $extraProvider->method('getAccessList')
4759
+            ->with(
4760
+                $this->equalTo([$file, $folder]),
4761
+                true
4762
+            )
4763
+            ->willReturn([
4764
+                'users' => [
4765
+                    'user3' => [],
4766
+                    'user4' => [],
4767
+                    'user5' => [],
4768
+                    '234567' => [],
4769
+                ],
4770
+                'remote' => [
4771
+                    'remote1',
4772
+                ],
4773
+            ]);
4774
+
4775
+        $this->rootFolder->method('getUserFolder')
4776
+            ->with($this->equalTo('user1'))
4777
+            ->willReturn($userFolder);
4778
+
4779
+        $expected = [
4780
+            'users' => [
4781
+                'owner' => [
4782
+                    'node_id' => 23,
4783
+                    'node_path' => '/folder/file'
4784
+                ]
4785
+                , 'user1' => [], 'user2' => [], 'user3' => [], '123456' => [], 'user4' => [], 'user5' => [], '234567' => []],
4786
+            'remote' => [
4787
+                'remote1',
4788
+            ],
4789
+            'public' => true,
4790
+        ];
4791
+
4792
+        $result = $manager->getAccessList($node, true, true);
4793
+
4794
+        $this->assertSame($expected['public'], $result['public']);
4795
+        $this->assertSame($expected['remote'], $result['remote']);
4796
+        $this->assertSame($expected['users'], $result['users']);
4797
+    }
4798
+
4799
+    public function testGetAllShares(): void {
4800
+        $factory = new DummyFactory2($this->createMock(IServerContainer::class));
4801
+
4802
+        $manager = $this->createManager($factory);
4803
+
4804
+        $factory->setProvider($this->defaultProvider);
4805
+        $extraProvider = $this->createMock(IShareProvider::class);
4806
+        $factory->setSecondProvider($extraProvider);
4807
+
4808
+        $share1 = $this->createMock(IShare::class);
4809
+        $share2 = $this->createMock(IShare::class);
4810
+        $share3 = $this->createMock(IShare::class);
4811
+        $share4 = $this->createMock(IShare::class);
4812
+
4813
+        $this->defaultProvider->method('getAllShares')
4814
+            ->willReturnCallback(function () use ($share1, $share2) {
4815
+                yield $share1;
4816
+                yield $share2;
4817
+            });
4818
+        $extraProvider->method('getAllShares')
4819
+            ->willReturnCallback(function () use ($share3, $share4) {
4820
+                yield $share3;
4821
+                yield $share4;
4822
+            });
4823
+
4824
+        // "yield from", used in "getAllShares()", does not reset the keys, so
4825
+        // "use_keys" has to be disabled to collect all the values while
4826
+        // ignoring the keys returned by the generator.
4827
+        $result = iterator_to_array($manager->getAllShares(), $use_keys = false);
4828
+
4829
+        $expects = [$share1, $share2, $share3, $share4];
4830
+
4831
+        $this->assertSame($expects, $result);
4832
+    }
4833
+
4834
+    public function dataCurrentUserCanEnumerateTargetUser(): array {
4835
+        return [
4836
+            'Full match guest' => [true, true, false, false, false, false, false, true],
4837
+            'Full match user' => [false, true, false, false, false, false, false, true],
4838
+            'Enumeration off guest' => [true, false, false, false, false, false, false, false],
4839
+            'Enumeration off user' => [false, false, false, false, false, false, false, false],
4840
+            'Enumeration guest' => [true, false, true, false, false, false, false, true],
4841
+            'Enumeration user' => [false, false, true, false, false, false, false, true],
4842
+
4843
+            // Restricted enumerations guests never works
4844
+            'Guest phone' => [true, false, true, true, false, false, false, false],
4845
+            'Guest group' => [true, false, true, false, true, false, false, false],
4846
+            'Guest both' => [true, false, true, true, true, false, false, false],
4847
+
4848
+            // Restricted enumerations users
4849
+            'User phone but not known' => [false, false, true, true, false, false, false, false],
4850
+            'User phone known' => [false, false, true, true, false, true, false, true],
4851
+            'User group but no match' => [false, false, true, false, true, false, false, false],
4852
+            'User group with match' => [false, false, true, false, true, false, true, true],
4853
+        ];
4854
+    }
4855
+
4856
+    /**
4857
+     * @dataProvider dataCurrentUserCanEnumerateTargetUser
4858
+     * @param bool $expected
4859
+     */
4860
+    public function testCurrentUserCanEnumerateTargetUser(bool $currentUserIsGuest, bool $allowEnumerationFullMatch, bool $allowEnumeration, bool $limitEnumerationToPhone, bool $limitEnumerationToGroups, bool $isKnownToUser, bool $haveCommonGroup, bool $expected): void {
4861
+        /** @var IManager|MockObject $manager */
4862
+        $manager = $this->createManagerMock()
4863
+            ->setMethods([
4864
+                'allowEnumerationFullMatch',
4865
+                'allowEnumeration',
4866
+                'limitEnumerationToPhone',
4867
+                'limitEnumerationToGroups',
4868
+            ])
4869
+            ->getMock();
4870
+
4871
+        $manager->method('allowEnumerationFullMatch')
4872
+            ->willReturn($allowEnumerationFullMatch);
4873
+        $manager->method('allowEnumeration')
4874
+            ->willReturn($allowEnumeration);
4875
+        $manager->method('limitEnumerationToPhone')
4876
+            ->willReturn($limitEnumerationToPhone);
4877
+        $manager->method('limitEnumerationToGroups')
4878
+            ->willReturn($limitEnumerationToGroups);
4879
+
4880
+        $this->knownUserService->method('isKnownToUser')
4881
+            ->with('current', 'target')
4882
+            ->willReturn($isKnownToUser);
4883
+
4884
+        $currentUser = null;
4885
+        if (!$currentUserIsGuest) {
4886
+            $currentUser = $this->createMock(IUser::class);
4887
+            $currentUser->method('getUID')
4888
+                ->willReturn('current');
4889
+        }
4890
+        $targetUser = $this->createMock(IUser::class);
4891
+        $targetUser->method('getUID')
4892
+            ->willReturn('target');
4893
+
4894
+        if ($haveCommonGroup) {
4895
+            $this->groupManager->method('getUserGroupIds')
4896
+                ->willReturnMap([
4897
+                    [$targetUser, ['gid1', 'gid2']],
4898
+                    [$currentUser, ['gid2', 'gid3']],
4899
+                ]);
4900
+        } else {
4901
+            $this->groupManager->method('getUserGroupIds')
4902
+                ->willReturnMap([
4903
+                    [$targetUser, ['gid1', 'gid2']],
4904
+                    [$currentUser, ['gid3', 'gid4']],
4905
+                ]);
4906
+        }
4907
+
4908
+        $this->assertSame($expected, $manager->currentUserCanEnumerateTargetUser($currentUser, $targetUser));
4909
+    }
4910 4910
 }
4911 4911
 
4912 4912
 class DummyFactory implements IProviderFactory {
4913
-	/** @var IShareProvider */
4914
-	protected $provider;
4915
-
4916
-	public function __construct(\OCP\IServerContainer $serverContainer) {
4917
-	}
4918
-
4919
-	/**
4920
-	 * @param IShareProvider $provider
4921
-	 */
4922
-	public function setProvider($provider) {
4923
-		$this->provider = $provider;
4924
-	}
4925
-
4926
-	/**
4927
-	 * @param string $id
4928
-	 * @return IShareProvider
4929
-	 */
4930
-	public function getProvider($id) {
4931
-		return $this->provider;
4932
-	}
4933
-
4934
-	/**
4935
-	 * @param int $shareType
4936
-	 * @return IShareProvider
4937
-	 */
4938
-	public function getProviderForType($shareType) {
4939
-		return $this->provider;
4940
-	}
4941
-
4942
-	/**
4943
-	 * @return IShareProvider[]
4944
-	 */
4945
-	public function getAllProviders() {
4946
-		return [$this->provider];
4947
-	}
4948
-
4949
-	public function registerProvider(string $shareProvier): void {
4950
-	}
4913
+    /** @var IShareProvider */
4914
+    protected $provider;
4915
+
4916
+    public function __construct(\OCP\IServerContainer $serverContainer) {
4917
+    }
4918
+
4919
+    /**
4920
+     * @param IShareProvider $provider
4921
+     */
4922
+    public function setProvider($provider) {
4923
+        $this->provider = $provider;
4924
+    }
4925
+
4926
+    /**
4927
+     * @param string $id
4928
+     * @return IShareProvider
4929
+     */
4930
+    public function getProvider($id) {
4931
+        return $this->provider;
4932
+    }
4933
+
4934
+    /**
4935
+     * @param int $shareType
4936
+     * @return IShareProvider
4937
+     */
4938
+    public function getProviderForType($shareType) {
4939
+        return $this->provider;
4940
+    }
4941
+
4942
+    /**
4943
+     * @return IShareProvider[]
4944
+     */
4945
+    public function getAllProviders() {
4946
+        return [$this->provider];
4947
+    }
4948
+
4949
+    public function registerProvider(string $shareProvier): void {
4950
+    }
4951 4951
 }
4952 4952
 
4953 4953
 class DummyFactory2 extends DummyFactory {
4954
-	/** @var IShareProvider */
4955
-	private $provider2;
4956
-
4957
-	/**
4958
-	 * @param IShareProvider $provider
4959
-	 */
4960
-	public function setSecondProvider($provider) {
4961
-		$this->provider2 = $provider;
4962
-	}
4963
-
4964
-	public function getAllProviders() {
4965
-		return [$this->provider, $this->provider2];
4966
-	}
4967
-
4968
-	public function registerProvider(string $shareProvier): void {
4969
-	}
4954
+    /** @var IShareProvider */
4955
+    private $provider2;
4956
+
4957
+    /**
4958
+     * @param IShareProvider $provider
4959
+     */
4960
+    public function setSecondProvider($provider) {
4961
+        $this->provider2 = $provider;
4962
+    }
4963
+
4964
+    public function getAllProviders() {
4965
+        return [$this->provider, $this->provider2];
4966
+    }
4967
+
4968
+    public function registerProvider(string $shareProvier): void {
4969
+    }
4970 4970
 }
Please login to merge, or discard this patch.