Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions lib/Controller/ContactIntegrationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@

class ContactIntegrationController extends Controller {
private ContactIntegrationService $service;
private string $uid;

public function __construct(string $appName,
IRequest $request,
ContactIntegrationService $service) {
ContactIntegrationService $service,
string $userId) {
parent::__construct($appName, $request);

$this->service = $service;
$this->uid = $userId;
}

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

/**
Expand Down Expand Up @@ -88,7 +91,7 @@ public function newContact(?string $contactName = null, ?string $mail = null): J
*/
#[TrapError]
public function autoComplete(string $term): JSONResponse {
$res = $this->service->autoComplete($term);
$res = $this->service->autoComplete($this->uid, $term);
return (new JSONResponse($res))->cacheFor(60 * 60, false, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public function __construct(ContactsIntegration $ci) {
$this->contactsIntegration = $ci;
}

public function findMatches(string $mail): array {
$matches = $this->contactsIntegration->getContactsWithMail($mail);
public function findMatches(string $uid, string $mail): array {
$matches = $this->contactsIntegration->getContactsWithMail($uid, $mail);
return $matches;
}

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

public function autoComplete(string $term): array {
return $this->contactsIntegration->getContactsWithName($term);
public function autoComplete(string $uid, string $term): array {
return $this->contactsIntegration->getContactsWithName($uid, $term);
}
}
174 changes: 96 additions & 78 deletions lib/Service/ContactsIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUserManager;
use function is_array;

class ContactsIntegration {
/** @var IManager */
Expand Down Expand Up @@ -60,55 +61,9 @@ public function __construct(IManager $contactsManager,
* @return array
*/
public function getMatchingRecipient(string $userId, string $term): array {
if (!$this->contactsManager->isEnabled()) {
return [];
}

// If 'Allow username autocompletion in share dialog' is disabled in the admin sharing settings, then we must not
// auto-complete system users
$shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'no') === 'yes';
$shareeEnumerationInGroupOnly = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
$shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
$shareeEnumerationFullMatchUserId = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes') === 'yes';
$shareeEnumerationFullMatchEmail = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';

$result = $this->contactsManager->search(
$term,
['UID', 'FN', 'EMAIL'],
[
'enumeration' => $shareeEnumeration,
'fullmatch' => $shareeEnumerationFullMatch,
'limit' => 20,
],
);
if (empty($result)) {
return [];
}
$result = $this->search($userId, $term, ['UID', 'FN', 'EMAIL']);
$receivers = [];

if ($shareeEnumeration && $shareeEnumerationInGroupOnly) {
$user = $this->userManager->get($userId);
if ($user === null) {
return [];
}
$userGroups = $this->groupManager->getUserGroupIds($user);
}

foreach ($result as $r) {
$isSystemUser = isset($r['isLocalSystemBook']) && $r['isLocalSystemBook'];
$isInSameGroup = false;
if ($isSystemUser && $shareeEnumerationInGroupOnly) {
foreach ($userGroups as $userGroup) {
if ($this->groupManager->isInGroup($r['UID'], $userGroup)) {
$isInSameGroup = true;
break;
}
}
if (!$shareeEnumerationFullMatch && !$isInSameGroup) {
continue;
}
}

$id = $r['UID'];
$fn = $r['FN'] ?? null;
if (!isset($r['EMAIL'])) {
Expand All @@ -125,23 +80,10 @@ public function getMatchingRecipient(string $userId, string $term): array {
if ($e === '') {
continue;
}
$lowerTerm = strtolower($term);

if ($isSystemUser && $shareeEnumerationInGroupOnly && !$isInSameGroup) {
// Check for full match. If full match is disabled, matching results already filtered out
if (!($lowerTerm !== '' && (
($shareeEnumerationFullMatch && !empty($fn) && $lowerTerm === strtolower($fn)) ||
($shareeEnumerationFullMatchUserId && $lowerTerm === strtolower($id)) ||
($shareeEnumerationFullMatchEmail && $lowerTerm === strtolower($e))))) {
// Not a full Match
continue;
}
}

$receivers[] = [
'id' => $id,
// Show full name if possible or fall back to email
'label' => $fn,
'label' => $fn ?? $e,
'email' => $e,
'photo' => $photo,
'source' => 'contacts',
Expand Down Expand Up @@ -243,45 +185,121 @@ public function newContact(string $name, string $mailAddr, string $type = 'HOME'
return $createdContact;
}

private function search(string $userId, string $term, array $fields, ?bool $strictSearch = null): array {
if (!$this->contactsManager->isEnabled()) {
return [];
}

// If 'Allow username autocompletion in share dialog' is disabled in the admin sharing settings, then we must not
// auto-complete system users
$shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
$shareeEnumerationInGroupOnly = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
$shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
$shareeEnumerationFullMatchDisplayName = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_displayname', 'yes') === 'yes';
$shareeEnumerationFullMatchUserId = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes') === 'yes';
$shareeEnumerationFullMatchEmail = $shareeEnumerationFullMatch && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';

$options = [
'enumeration' => $shareeEnumeration,
'fullmatch' => $shareeEnumerationFullMatch,
'limit' => 20,
];
if ($strictSearch !== null) {
$options['strict_search'] = $strictSearch;
}

$result = $this->contactsManager->search(
$term,
$fields,
$options,
);

$userGroups = [];
if ($shareeEnumeration && $shareeEnumerationInGroupOnly) {
$user = $this->userManager->get($userId);
if ($user === null) {
return [];
}
$userGroups = $this->groupManager->getUserGroupIds($user);
}

$filteredResults = [];
foreach ($result as $r) {
$isSystemUser = isset($r['isLocalSystemBook']) && $r['isLocalSystemBook'];
$isInSameGroup = false;
if ($isSystemUser && $shareeEnumerationInGroupOnly) {
foreach ($userGroups as $userGroup) {
if ($this->groupManager->isInGroup($r['UID'], $userGroup)) {
$isInSameGroup = true;
break;
}
}
if (!$shareeEnumerationFullMatch && !$isInSameGroup) {
continue;
}
}

if ($isSystemUser && $shareeEnumerationInGroupOnly && !$isInSameGroup && $shareeEnumerationFullMatch) {
// Check for full match. If full match is disabled, non-matching results already filtered out above.
$id = $r['UID'];
$fn = $r['FN'] ?? null;
$lowerTerm = strtolower($term);
$isMatch = ($lowerTerm !== '' && (
($shareeEnumerationFullMatchDisplayName && !empty($fn) && $lowerTerm === strtolower($fn))
|| ($shareeEnumerationFullMatchUserId && $lowerTerm === strtolower($id)))) ;
if ($shareeEnumerationFullMatchEmail && !$isMatch) {
$email = $r['EMAIL'] ?? null;
if ($email === null) {
continue;
}
$emails = is_array($email) ? $email : [$email];
foreach ($emails as $e) {
if ($lowerTerm === strtolower($e)) {
$isMatch = true;
break;
}
}
}
if (!$isMatch) {
continue;
}
}

$filteredResults[] = $r;
}
return $filteredResults;
}

/**
* @param string[] $fields
*/
private function doSearch(string $term, array $fields, bool $strictSearch): array {
$allowSystemUsers = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'no') === 'yes';

$result = $this->contactsManager->search($term, $fields, [
'strict_search' => $strictSearch,
'limit' => 20,
]);
private function doSearch(string $userId, string $term, array $fields, bool $strictSearch) : array {
$result = $this->search($userId, $term, $fields, $strictSearch);
$matches = [];
foreach ($result as $r) {
if (!$allowSystemUsers && isset($r['isLocalSystemBook']) && $r['isLocalSystemBook']) {
continue;
}
$id = $r['UID'];
$fn = $r['FN'];
$email = $r['EMAIL'] ?? null;
$matches[] = [
'id' => $id,
'label' => $fn,
'email' => $email,
];
}
return $matches;
}

/**
* Extracts all Contacts with the specified mail address
*
* @param string $mailAddr
* @return array
*/
public function getContactsWithMail(string $mailAddr) {
return $this->doSearch($mailAddr, ['EMAIL'], true);
public function getContactsWithMail(string $userId, string $mailAddr): array {
return $this->doSearch($userId, $mailAddr, ['EMAIL'], true);
}

/**
* Extracts all Contacts with the specified name
*/
public function getContactsWithName(string $name): array {
return $this->doSearch($name, ['FN'], false);
public function getContactsWithName(string $userId, string $name): array {
return $this->doSearch($userId, $name, ['FN'], false);
}
}
Loading
Loading