Skip to content

Commit a4f6653

Browse files
authored
Merge pull request #12818 from nextcloud/backport/12796/stable3.7
[stable3.7] fix(autocomplete): respect privacy settings for sharing
2 parents 7b489f2 + a079161 commit a4f6653

4 files changed

Lines changed: 287 additions & 92 deletions

File tree

lib/Controller/ContactIntegrationController.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@
3232

3333
class ContactIntegrationController extends Controller {
3434
private ContactIntegrationService $service;
35+
private string $uid;
3536

3637
public function __construct(string $appName,
3738
IRequest $request,
38-
ContactIntegrationService $service) {
39+
ContactIntegrationService $service,
40+
string $userId) {
3941
parent::__construct($appName, $request);
4042

4143
$this->service = $service;
44+
$this->uid = $userId;
4245
}
4346

4447
/**
@@ -49,7 +52,7 @@ public function __construct(string $appName,
4952
*/
5053
#[TrapError]
5154
public function match(string $mail): JSONResponse {
52-
return (new JSONResponse($this->service->findMatches($mail)))->cacheFor(60 * 60, false, true);
55+
return (new JSONResponse($this->service->findMatches($this->uid, $mail)))->cacheFor(60 * 60, false, true);
5356
}
5457

5558
/**
@@ -88,7 +91,7 @@ public function newContact(?string $contactName = null, ?string $mail = null): J
8891
*/
8992
#[TrapError]
9093
public function autoComplete(string $term): JSONResponse {
91-
$res = $this->service->autoComplete($term);
94+
$res = $this->service->autoComplete($this->uid, $term);
9295
return (new JSONResponse($res))->cacheFor(60 * 60, false, true);
9396
}
9497
}

lib/Service/ContactIntegration/ContactIntegrationService.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ public function __construct(ContactsIntegration $ci) {
3333
$this->contactsIntegration = $ci;
3434
}
3535

36-
public function findMatches(string $mail): array {
37-
$matches = $this->contactsIntegration->getContactsWithMail($mail);
36+
public function findMatches(string $uid, string $mail): array {
37+
$matches = $this->contactsIntegration->getContactsWithMail($uid, $mail);
3838
return $matches;
3939
}
4040

@@ -46,7 +46,7 @@ public function newContact(string $name, string $mail): ?array {
4646
return $this->contactsIntegration->newContact($name, $mail);
4747
}
4848

49-
public function autoComplete(string $term): array {
50-
return $this->contactsIntegration->getContactsWithName($term);
49+
public function autoComplete(string $uid, string $term): array {
50+
return $this->contactsIntegration->getContactsWithName($uid, $term);
5151
}
5252
}

lib/Service/ContactsIntegration.php

Lines changed: 96 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use OCP\IConfig;
2929
use OCP\IGroupManager;
3030
use OCP\IUserManager;
31+
use function is_array;
3132

3233
class ContactsIntegration {
3334
/** @var IManager */
@@ -60,55 +61,9 @@ public function __construct(IManager $contactsManager,
6061
* @return array
6162
*/
6263
public function getMatchingRecipient(string $userId, string $term): array {
63-
if (!$this->contactsManager->isEnabled()) {
64-
return [];
65-
}
66-
67-
// If 'Allow username autocompletion in share dialog' is disabled in the admin sharing settings, then we must not
68-
// auto-complete system users
69-
$shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'no') === 'yes';
70-
$shareeEnumerationInGroupOnly = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
71-
$shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
72-
$shareeEnumerationFullMatchUserId = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes') === 'yes';
73-
$shareeEnumerationFullMatchEmail = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
74-
75-
$result = $this->contactsManager->search(
76-
$term,
77-
['UID', 'FN', 'EMAIL'],
78-
[
79-
'enumeration' => $shareeEnumeration,
80-
'fullmatch' => $shareeEnumerationFullMatch,
81-
'limit' => 20,
82-
],
83-
);
84-
if (empty($result)) {
85-
return [];
86-
}
64+
$result = $this->search($userId, $term, ['UID', 'FN', 'EMAIL']);
8765
$receivers = [];
88-
89-
if ($shareeEnumeration && $shareeEnumerationInGroupOnly) {
90-
$user = $this->userManager->get($userId);
91-
if ($user === null) {
92-
return [];
93-
}
94-
$userGroups = $this->groupManager->getUserGroupIds($user);
95-
}
96-
9766
foreach ($result as $r) {
98-
$isSystemUser = isset($r['isLocalSystemBook']) && $r['isLocalSystemBook'];
99-
$isInSameGroup = false;
100-
if ($isSystemUser && $shareeEnumerationInGroupOnly) {
101-
foreach ($userGroups as $userGroup) {
102-
if ($this->groupManager->isInGroup($r['UID'], $userGroup)) {
103-
$isInSameGroup = true;
104-
break;
105-
}
106-
}
107-
if (!$shareeEnumerationFullMatch && !$isInSameGroup) {
108-
continue;
109-
}
110-
}
111-
11267
$id = $r['UID'];
11368
$fn = $r['FN'] ?? null;
11469
if (!isset($r['EMAIL'])) {
@@ -125,23 +80,10 @@ public function getMatchingRecipient(string $userId, string $term): array {
12580
if ($e === '') {
12681
continue;
12782
}
128-
$lowerTerm = strtolower($term);
129-
130-
if ($isSystemUser && $shareeEnumerationInGroupOnly && !$isInSameGroup) {
131-
// Check for full match. If full match is disabled, matching results already filtered out
132-
if (!($lowerTerm !== '' && (
133-
($shareeEnumerationFullMatch && !empty($fn) && $lowerTerm === strtolower($fn)) ||
134-
($shareeEnumerationFullMatchUserId && $lowerTerm === strtolower($id)) ||
135-
($shareeEnumerationFullMatchEmail && $lowerTerm === strtolower($e))))) {
136-
// Not a full Match
137-
continue;
138-
}
139-
}
140-
14183
$receivers[] = [
14284
'id' => $id,
14385
// Show full name if possible or fall back to email
144-
'label' => $fn,
86+
'label' => $fn ?? $e,
14587
'email' => $e,
14688
'photo' => $photo,
14789
'source' => 'contacts',
@@ -243,45 +185,121 @@ public function newContact(string $name, string $mailAddr, string $type = 'HOME'
243185
return $createdContact;
244186
}
245187

188+
private function search(string $userId, string $term, array $fields, ?bool $strictSearch = null): array {
189+
if (!$this->contactsManager->isEnabled()) {
190+
return [];
191+
}
192+
193+
// If 'Allow username autocompletion in share dialog' is disabled in the admin sharing settings, then we must not
194+
// auto-complete system users
195+
$shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
196+
$shareeEnumerationInGroupOnly = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
197+
$shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
198+
$shareeEnumerationFullMatchDisplayName = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_displayname', 'yes') === 'yes';
199+
$shareeEnumerationFullMatchUserId = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes') === 'yes';
200+
$shareeEnumerationFullMatchEmail = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
201+
202+
$options = [
203+
'enumeration' => $shareeEnumeration,
204+
'fullmatch' => $shareeEnumerationFullMatch,
205+
'limit' => 20,
206+
];
207+
if ($strictSearch !== null) {
208+
$options['strict_search'] = $strictSearch;
209+
}
210+
211+
$result = $this->contactsManager->search(
212+
$term,
213+
$fields,
214+
$options,
215+
);
216+
217+
$userGroups = [];
218+
if ($shareeEnumeration && $shareeEnumerationInGroupOnly) {
219+
$user = $this->userManager->get($userId);
220+
if ($user === null) {
221+
return [];
222+
}
223+
$userGroups = $this->groupManager->getUserGroupIds($user);
224+
}
225+
226+
$filteredResults = [];
227+
foreach ($result as $r) {
228+
$isSystemUser = isset($r['isLocalSystemBook']) && $r['isLocalSystemBook'];
229+
$isInSameGroup = false;
230+
if ($isSystemUser && $shareeEnumerationInGroupOnly) {
231+
foreach ($userGroups as $userGroup) {
232+
if ($this->groupManager->isInGroup($r['UID'], $userGroup)) {
233+
$isInSameGroup = true;
234+
break;
235+
}
236+
}
237+
if (!$shareeEnumerationFullMatch && !$isInSameGroup) {
238+
continue;
239+
}
240+
}
241+
242+
if ($isSystemUser && $shareeEnumerationInGroupOnly && !$isInSameGroup && $shareeEnumerationFullMatch) {
243+
// Check for full match. If full match is disabled, non-matching results already filtered out above.
244+
$id = $r['UID'];
245+
$fn = $r['FN'] ?? null;
246+
$lowerTerm = strtolower($term);
247+
$isMatch = ($lowerTerm !== '' && (
248+
($shareeEnumerationFullMatchDisplayName && !empty($fn) && $lowerTerm === strtolower($fn))
249+
|| ($shareeEnumerationFullMatchUserId && $lowerTerm === strtolower($id)))) ;
250+
if ($shareeEnumerationFullMatchEmail && !$isMatch) {
251+
$email = $r['EMAIL'] ?? null;
252+
if ($email === null) {
253+
continue;
254+
}
255+
$emails = is_array($email) ? $email : [$email];
256+
foreach ($emails as $e) {
257+
if ($lowerTerm === strtolower($e)) {
258+
$isMatch = true;
259+
break;
260+
}
261+
}
262+
}
263+
if (!$isMatch) {
264+
continue;
265+
}
266+
}
267+
268+
$filteredResults[] = $r;
269+
}
270+
return $filteredResults;
271+
}
272+
246273
/**
247274
* @param string[] $fields
248275
*/
249-
private function doSearch(string $term, array $fields, bool $strictSearch): array {
250-
$allowSystemUsers = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'no') === 'yes';
251-
252-
$result = $this->contactsManager->search($term, $fields, [
253-
'strict_search' => $strictSearch,
254-
'limit' => 20,
255-
]);
276+
private function doSearch(string $userId, string $term, array $fields, bool $strictSearch) : array {
277+
$result = $this->search($userId, $term, $fields, $strictSearch);
256278
$matches = [];
257279
foreach ($result as $r) {
258-
if (!$allowSystemUsers && isset($r['isLocalSystemBook']) && $r['isLocalSystemBook']) {
259-
continue;
260-
}
261280
$id = $r['UID'];
262281
$fn = $r['FN'];
282+
$email = $r['EMAIL'] ?? null;
263283
$matches[] = [
264284
'id' => $id,
265285
'label' => $fn,
286+
'email' => $email,
266287
];
267288
}
268289
return $matches;
269290
}
270291

271292
/**
272293
* Extracts all Contacts with the specified mail address
273-
*
274-
* @param string $mailAddr
275-
* @return array
276294
*/
277-
public function getContactsWithMail(string $mailAddr) {
278-
return $this->doSearch($mailAddr, ['EMAIL'], true);
295+
public function getContactsWithMail(string $userId, string $mailAddr): array {
296+
return $this->doSearch($userId, $mailAddr, ['EMAIL'], true);
279297
}
280298

281299
/**
282300
* Extracts all Contacts with the specified name
283301
*/
284-
public function getContactsWithName(string $name): array {
285-
return $this->doSearch($name, ['FN'], false);
302+
public function getContactsWithName(string $userId, string $name): array {
303+
return $this->doSearch($userId, $name, ['FN'], false);
286304
}
287305
}

0 commit comments

Comments
 (0)