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