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