From c7560ab3a934ccf0c6271dd223e2907bd7d57949 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 9 Mar 2021 21:48:48 +0100 Subject: [PATCH] Restrict autocompletion also based on the phonebook known users Signed-off-by: Joas Schilling --- apps/dav/appinfo/v1/caldav.php | 2 + apps/dav/appinfo/v1/carddav.php | 2 + apps/dav/lib/CardDAV/SystemAddressbook.php | 5 +- apps/dav/lib/Command/CreateCalendar.php | 2 + apps/dav/lib/Connector/Sabre/Principal.php | 88 ++-- apps/dav/lib/RootCollection.php | 2 + .../unit/CalDAV/AbstractCalDavBackend.php | 2 + .../tests/unit/CardDAV/CardDavBackendTest.php | 2 + .../unit/Connector/Sabre/PrincipalTest.php | 8 +- .../lib/AppInfo/Application.php | 2 + .../Collaborators/MailPlugin.php | 33 +- .../Collaborators/UserPlugin.php | 29 +- .../Contacts/ContactsMenu/ContactsStore.php | 112 +++-- lib/private/Share20/Manager.php | 5 + lib/public/Share/IManager.php | 8 + .../Collaborators/MailPluginTest.php | 14 +- .../Collaborators/UserPluginTest.php | 7 + .../ContactsMenu/ContactsStoreTest.php | 389 +++++++++++++++--- 18 files changed, 564 insertions(+), 148 deletions(-) diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php index e04653ddea..236d81f66f 100644 --- a/apps/dav/appinfo/v1/caldav.php +++ b/apps/dav/appinfo/v1/caldav.php @@ -27,6 +27,7 @@ */ // Backends +use OC\KnownUser\KnownUserService; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\Connector\LegacyDAVACL; use OCA\DAV\CalDAV\CalendarRoot; @@ -50,6 +51,7 @@ $principalBackend = new Principal( \OC::$server->getUserSession(), \OC::$server->getAppManager(), \OC::$server->query(\OCA\DAV\CalDAV\Proxy\ProxyMapper::class), + \OC::$server->get(KnownUserService::class), \OC::$server->getConfig(), 'principals/' ); diff --git a/apps/dav/appinfo/v1/carddav.php b/apps/dav/appinfo/v1/carddav.php index dbab1ae968..bb766bbaec 100644 --- a/apps/dav/appinfo/v1/carddav.php +++ b/apps/dav/appinfo/v1/carddav.php @@ -27,6 +27,7 @@ */ // Backends +use OC\KnownUser\KnownUserService; use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\CardDAV\AddressBookRoot; use OCA\DAV\CardDAV\CardDavBackend; @@ -53,6 +54,7 @@ $principalBackend = new Principal( \OC::$server->getUserSession(), \OC::$server->getAppManager(), \OC::$server->query(\OCA\DAV\CalDAV\Proxy\ProxyMapper::class), + \OC::$server->get(KnownUserService::class), \OC::$server->getConfig(), 'principals/' ); diff --git a/apps/dav/lib/CardDAV/SystemAddressbook.php b/apps/dav/lib/CardDAV/SystemAddressbook.php index c7190c8131..5b95215271 100644 --- a/apps/dav/lib/CardDAV/SystemAddressbook.php +++ b/apps/dav/lib/CardDAV/SystemAddressbook.php @@ -43,8 +43,9 @@ class SystemAddressbook extends AddressBook { public function getChildren() { $shareEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; - $restrictShareEnumeration = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; - if (!$shareEnumeration || ($shareEnumeration && $restrictShareEnumeration)) { + $shareEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + $shareEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; + if (!$shareEnumeration || $shareEnumerationGroup || $shareEnumerationPhone) { return []; } diff --git a/apps/dav/lib/Command/CreateCalendar.php b/apps/dav/lib/Command/CreateCalendar.php index 58c6a8c63f..1d543c71bc 100644 --- a/apps/dav/lib/Command/CreateCalendar.php +++ b/apps/dav/lib/Command/CreateCalendar.php @@ -27,6 +27,7 @@ namespace OCA\DAV\Command; +use OC\KnownUser\KnownUserService; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Proxy\ProxyMapper; use OCA\DAV\Connector\Sabre\Principal; @@ -86,6 +87,7 @@ class CreateCalendar extends Command { \OC::$server->getUserSession(), \OC::$server->getAppManager(), \OC::$server->query(ProxyMapper::class), + \OC::$server->get(KnownUserService::class), \OC::$server->getConfig() ); $random = \OC::$server->getSecureRandom(); diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php index c1b1dc1b2d..94302a12b4 100644 --- a/apps/dav/lib/Connector/Sabre/Principal.php +++ b/apps/dav/lib/Connector/Sabre/Principal.php @@ -36,6 +36,7 @@ namespace OCA\DAV\Connector\Sabre; +use OC\KnownUser\KnownUserService; use OCA\Circles\Exceptions\CircleDoesNotExistException; use OCA\DAV\CalDAV\Proxy\ProxyMapper; use OCA\DAV\Traits\PrincipalProxyTrait; @@ -82,27 +83,19 @@ class Principal implements BackendInterface { /** @var ProxyMapper */ private $proxyMapper; + /** @var KnownUserService */ + private $knownUserService; + /** @var IConfig */ private $config; - /** - * Principal constructor. - * - * @param IUserManager $userManager - * @param IGroupManager $groupManager - * @param IShareManager $shareManager - * @param IUserSession $userSession - * @param IAppManager $appManager - * @param ProxyMapper $proxyMapper - * @param IConfig $config - * @param string $principalPrefix - */ public function __construct(IUserManager $userManager, IGroupManager $groupManager, IShareManager $shareManager, IUserSession $userSession, IAppManager $appManager, ProxyMapper $proxyMapper, + KnownUserService $knownUserService, IConfig $config, string $principalPrefix = 'principals/users/') { $this->userManager = $userManager; @@ -113,6 +106,7 @@ class Principal implements BackendInterface { $this->principalPrefix = trim($principalPrefix, '/'); $this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/'); $this->proxyMapper = $proxyMapper; + $this->knownUserService = $knownUserService; $this->config = $config; } @@ -267,24 +261,24 @@ class Principal implements BackendInterface { } $allowEnumeration = $this->shareManager->allowEnumeration(); - $limitEnumeration = $this->shareManager->limitEnumerationToGroups(); + $limitEnumerationGroup = $this->shareManager->limitEnumerationToGroups(); + $limitEnumerationPhone = $this->shareManager->limitEnumerationToPhone(); // If sharing is restricted to group members only, // return only members that have groups in common $restrictGroups = false; + $currentUser = $this->userSession->getUser(); if ($this->shareManager->shareWithGroupMembersOnly()) { - $user = $this->userSession->getUser(); - if (!$user) { + if (!$currentUser instanceof IUser) { return []; } - $restrictGroups = $this->groupManager->getUserGroupIds($user); + $restrictGroups = $this->groupManager->getUserGroupIds($currentUser); } $currentUserGroups = []; - if ($limitEnumeration) { - $currentUser = $this->userSession->getUser(); - if ($currentUser) { + if ($limitEnumerationGroup) { + if ($currentUser instanceof IUser) { $currentUserGroups = $this->groupManager->getUserGroupIds($currentUser); } } @@ -302,14 +296,28 @@ class Principal implements BackendInterface { $users = \array_filter($users, static function (IUser $user) use ($value) { return $user->getEMailAddress() === $value; }); - } + } else { + $users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $currentUserGroups) { + if ($user->getEMailAddress() === $value) { + return true; + } - if ($limitEnumeration) { - $users = \array_filter($users, function (IUser $user) use ($currentUserGroups, $value) { - return !empty(array_intersect( - $this->groupManager->getUserGroupIds($user), - $currentUserGroups - )) || $user->getEMailAddress() === $value; + if ($limitEnumerationPhone + && $currentUser instanceof IUser + && $this->knownUserService->isKnownToUser($currentUser->getUID(), $user->getUID())) { + // Synced phonebook match + return true; + } + + if (!$limitEnumerationGroup) { + // No limitation on enumeration, all allowed + return true; + } + + return !empty($currentUserGroups) && !empty(array_intersect( + $this->groupManager->getUserGroupIds($user), + $currentUserGroups + )); }); } @@ -334,14 +342,28 @@ class Principal implements BackendInterface { $users = \array_filter($users, static function (IUser $user) use ($value) { return $user->getDisplayName() === $value; }); - } + } else { + $users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $currentUserGroups) { + if ($user->getDisplayName() === $value) { + return true; + } - if ($limitEnumeration) { - $users = \array_filter($users, function (IUser $user) use ($currentUserGroups, $value) { - return !empty(array_intersect( - $this->groupManager->getUserGroupIds($user), - $currentUserGroups - )) || $user->getDisplayName() === $value; + if ($limitEnumerationPhone + && $currentUser instanceof IUser + && $this->knownUserService->isKnownToUser($currentUser->getUID(), $user->getUID())) { + // Synced phonebook match + return true; + } + + if (!$limitEnumerationGroup) { + // No limitation on enumeration, all allowed + return true; + } + + return !empty($currentUserGroups) && !empty(array_intersect( + $this->groupManager->getUserGroupIds($user), + $currentUserGroups + )); }); } diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index 18874ecf74..16a209a98f 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -28,6 +28,7 @@ namespace OCA\DAV; +use OC\KnownUser\KnownUserService; use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\CalendarRoot; @@ -70,6 +71,7 @@ class RootCollection extends SimpleCollection { \OC::$server->getUserSession(), \OC::$server->getAppManager(), $proxyMapper, + \OC::$server->get(KnownUserService::class), \OC::$server->getConfig() ); $groupPrincipalBackend = new GroupPrincipalBackend($groupManager, $userSession, $shareManager, $config); diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php index 85efd0fd36..51ba8c1867 100644 --- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php @@ -27,6 +27,7 @@ namespace OCA\DAV\Tests\unit\CalDAV; +use OC\KnownUser\KnownUserService; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Proxy\ProxyMapper; use OCA\DAV\Connector\Sabre\Principal; @@ -92,6 +93,7 @@ abstract class AbstractCalDavBackend extends TestCase { $this->createMock(IUserSession::class), $this->createMock(IAppManager::class), $this->createMock(ProxyMapper::class), + $this->createMock(KnownUserService::class), $this->createMock(IConfig::class), ]) ->setMethods(['getPrincipalByPath', 'getGroupMembership']) diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index a8c7a78172..60f46ce8fa 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -33,6 +33,7 @@ namespace OCA\DAV\Tests\unit\CardDAV; +use OC\KnownUser\KnownUserService; use OCA\DAV\CalDAV\Proxy\ProxyMapper; use OCA\DAV\CardDAV\AddressBook; use OCA\DAV\CardDAV\CardDavBackend; @@ -139,6 +140,7 @@ class CardDavBackendTest extends TestCase { $this->createMock(IUserSession::class), $this->createMock(IAppManager::class), $this->createMock(ProxyMapper::class), + $this->createMock(KnownUserService::class), $this->createMock(IConfig::class), ]) ->setMethods(['getPrincipalByPath', 'getGroupMembership']) diff --git a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php index 117707eaf2..33c1ec1b58 100644 --- a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php @@ -30,6 +30,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; +use OC\KnownUser\KnownUserService; use OC\User\User; use OCA\DAV\CalDAV\Proxy\Proxy; use OCA\DAV\CalDAV\Proxy\ProxyMapper; @@ -41,6 +42,7 @@ use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; use OCP\Share\IManager; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\PropPatch; use Test\TestCase; @@ -67,6 +69,8 @@ class PrincipalTest extends TestCase { /** @var ProxyMapper | \PHPUnit\Framework\MockObject\MockObject */ private $proxyMapper; + /** @var KnownUserService|MockObject */ + private $knownUserService; /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */ private $config; @@ -77,6 +81,7 @@ class PrincipalTest extends TestCase { $this->userSession = $this->createMock(IUserSession::class); $this->appManager = $this->createMock(IAppManager::class); $this->proxyMapper = $this->createMock(ProxyMapper::class); + $this->knownUserService = $this->createMock(KnownUserService::class); $this->config = $this->createMock(IConfig::class); $this->connector = new \OCA\DAV\Connector\Sabre\Principal( @@ -86,6 +91,7 @@ class PrincipalTest extends TestCase { $this->userSession, $this->appManager, $this->proxyMapper, + $this->knownUserService, $this->config ); parent::setUp(); @@ -442,7 +448,7 @@ class PrincipalTest extends TestCase { if ($groupsOnly) { $user = $this->createMock(IUser::class); - $this->userSession->expects($this->once()) + $this->userSession->expects($this->atLeastOnce()) ->method('getUser') ->willReturn($user); diff --git a/apps/files_versions/lib/AppInfo/Application.php b/apps/files_versions/lib/AppInfo/Application.php index afbd42ffc3..e09ad7e90a 100644 --- a/apps/files_versions/lib/AppInfo/Application.php +++ b/apps/files_versions/lib/AppInfo/Application.php @@ -27,6 +27,7 @@ namespace OCA\Files_Versions\AppInfo; +use OC\KnownUser\KnownUserService; use OCA\DAV\CalDAV\Proxy\ProxyMapper; use OCA\DAV\Connector\Sabre\Principal; use OCA\Files\Event\LoadAdditionalScriptsEvent; @@ -72,6 +73,7 @@ class Application extends App implements IBootstrap { $server->getUserSession(), $server->getAppManager(), $server->get(ProxyMapper::class), + $server->get(KnownUserService::class), $server->getConfig() ); }); diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php index 7bdd29afc4..7da8cede6a 100644 --- a/lib/private/Collaboration/Collaborators/MailPlugin.php +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -27,6 +27,7 @@ namespace OC\Collaboration\Collaborators; +use OC\KnownUser\KnownUserService; use OCP\Collaboration\Collaborators\ISearchPlugin; use OCP\Collaboration\Collaborators\ISearchResult; use OCP\Collaboration\Collaborators\SearchResultType; @@ -40,8 +41,14 @@ use OCP\IUserSession; use OCP\Share\IShare; class MailPlugin implements ISearchPlugin { - protected $shareeEnumeration; + /* @var bool */ protected $shareWithGroupOnly; + /* @var bool */ + protected $shareeEnumeration; + /* @var bool */ + protected $shareeEnumerationInGroupOnly; + /* @var bool */ + protected $shareeEnumerationPhone; /** @var IManager */ private $contactsManager; @@ -52,20 +59,28 @@ class MailPlugin implements ISearchPlugin { /** @var IGroupManager */ private $groupManager; - + /** @var KnownUserService */ + private $knownUserService; /** @var IUserSession */ private $userSession; - public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IGroupManager $groupManager, IUserSession $userSession) { + public function __construct(IManager $contactsManager, + ICloudIdManager $cloudIdManager, + IConfig $config, + IGroupManager $groupManager, + KnownUserService $knownUserService, + IUserSession $userSession) { $this->contactsManager = $contactsManager; $this->cloudIdManager = $cloudIdManager; $this->config = $config; $this->groupManager = $groupManager; + $this->knownUserService = $knownUserService; $this->userSession = $userSession; $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + $this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; } /** @@ -77,6 +92,8 @@ class MailPlugin implements ISearchPlugin { * @since 13.0.0 */ public function search($search, $limit, $offset, ISearchResult $searchResult) { + $currentUserId = $this->userSession->getUser()->getUID(); + $result = $userResults = ['wide' => [], 'exact' => []]; $userType = new SearchResultType('users'); $emailType = new SearchResultType('emails'); @@ -152,8 +169,12 @@ class MailPlugin implements ISearchPlugin { continue; } - $addToWide = !$this->shareeEnumerationInGroupOnly; - if ($this->shareeEnumerationInGroupOnly) { + $addToWide = !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone); + if (!$addToWide && $this->shareeEnumerationPhone && $this->knownUserService->isKnownToUser($currentUserId, $contact['UID'])) { + $addToWide = true; + } + + if (!$addToWide && $this->shareeEnumerationInGroupOnly) { $addToWide = false; $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); foreach ($userGroups as $userGroup) { @@ -181,7 +202,7 @@ class MailPlugin implements ISearchPlugin { } if ($exactEmailMatch - || isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch) { + || (isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch)) { if ($exactEmailMatch) { $searchResult->markExactIdMatch($emailType); } diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php index d832a42000..5114ccd8eb 100644 --- a/lib/private/Collaboration/Collaborators/UserPlugin.php +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -32,6 +32,7 @@ namespace OC\Collaboration\Collaborators; +use OC\KnownUser\KnownUserService; use OCP\Collaboration\Collaborators\ISearchPlugin; use OCP\Collaboration\Collaborators\ISearchResult; use OCP\Collaboration\Collaborators\SearchResultType; @@ -46,8 +47,12 @@ use OCP\UserStatus\IManager as IUserStatusManager; class UserPlugin implements ISearchPlugin { /* @var bool */ protected $shareWithGroupOnly; + /* @var bool */ protected $shareeEnumeration; + /* @var bool */ protected $shareeEnumerationInGroupOnly; + /* @var bool */ + protected $shareeEnumerationPhone; /** @var IConfig */ private $config; @@ -57,33 +62,29 @@ class UserPlugin implements ISearchPlugin { private $userSession; /** @var IUserManager */ private $userManager; + /** @var KnownUserService */ + private $knownUserService; /** @var IUserStatusManager */ private $userStatusManager; - /** - * UserPlugin constructor. - * - * @param IConfig $config - * @param IUserManager $userManager - * @param IGroupManager $groupManager - * @param IUserSession $userSession - * @param IUserStatusManager $userStatusManager - */ public function __construct(IConfig $config, IUserManager $userManager, IGroupManager $groupManager, IUserSession $userSession, + KnownUserService $knownUserService, IUserStatusManager $userStatusManager) { $this->config = $config; $this->groupManager = $groupManager; $this->userSession = $userSession; $this->userManager = $userManager; + $this->knownUserService = $knownUserService; $this->userStatusManager = $userStatusManager; $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + $this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; } public function search($search, $limit, $offset, ISearchResult $searchResult) { @@ -91,6 +92,7 @@ class UserPlugin implements ISearchPlugin { $users = []; $hasMoreResults = false; + $currentUserId = $this->userSession->getUser()->getUID(); $currentUserGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); if ($this->shareWithGroupOnly) { // Search in all the groups this user is part of @@ -168,11 +170,16 @@ class UserPlugin implements ISearchPlugin { ]; } else { $addToWideResults = false; - if ($this->shareeEnumeration && !$this->shareeEnumerationInGroupOnly) { + if ($this->shareeEnumeration && + !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone)) { $addToWideResults = true; } - if ($this->shareeEnumerationInGroupOnly) { + if ($this->shareeEnumerationPhone && $this->knownUserService->isKnownToUser($currentUserId, $user->getUID())) { + $addToWideResults = true; + } + + if (!$addToWideResults && $this->shareeEnumerationInGroupOnly) { $commonGroups = array_intersect($currentUserGroups, $this->groupManager->getUserGroupIds($user)); if (!empty($commonGroups)) { $addToWideResults = true; diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index e2bd7edc63..852765506c 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -31,6 +31,7 @@ namespace OC\Contacts\ContactsMenu; +use OC\KnownUser\KnownUserService; use OCP\Contacts\ContactsMenu\IContactsStore; use OCP\Contacts\ContactsMenu\IEntry; use OCP\Contacts\IManager; @@ -53,20 +54,19 @@ class ContactsStore implements IContactsStore { /** @var IGroupManager */ private $groupManager; - /** - * @param IManager $contactsManager - * @param IConfig $config - * @param IUserManager $userManager - * @param IGroupManager $groupManager - */ + /** @var KnownUserService */ + private $knownUserService; + public function __construct(IManager $contactsManager, IConfig $config, IUserManager $userManager, - IGroupManager $groupManager) { + IGroupManager $groupManager, + KnownUserService $knownUserService) { $this->contactsManager = $contactsManager; $this->config = $config; $this->userManager = $userManager; $this->groupManager = $groupManager; + $this->knownUserService = $knownUserService; } /** @@ -103,7 +103,7 @@ class ContactsStore implements IContactsStore { } /** - * Filters the contacts. Applies 3 filters: + * Filters the contacts. Applied filters: * 1. filter the current user * 2. if the `shareapi_allow_share_dialog_user_enumeration` config option is * enabled it will filter all local users @@ -122,20 +122,21 @@ class ContactsStore implements IContactsStore { array $entries, $filter) { $disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes'; - $restrictEnumeration = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + $restrictEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + $restrictEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes'; // whether to filter out local users $skipLocal = false; - // whether to filter out all users which doesn't have the same group as the current user - $ownGroupsOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes' || $restrictEnumeration; + // whether to filter out all users which don't have a common group as the current user + $ownGroupsOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; $selfGroups = $this->groupManager->getUserGroupIds($self); if ($excludedGroups) { $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', ''); $decodedExcludeGroups = json_decode($excludedGroups, true); - $excludeGroupsList = ($decodedExcludeGroups !== null) ? $decodedExcludeGroups : []; + $excludeGroupsList = $decodedExcludeGroups ?? []; if (count(array_intersect($excludeGroupsList, $selfGroups)) !== 0) { // a group of the current user is excluded -> filter all local users @@ -145,47 +146,76 @@ class ContactsStore implements IContactsStore { $selfUID = $self->getUID(); - return array_values(array_filter($entries, function (IEntry $entry) use ($self, $skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $filter) { - if ($skipLocal && $entry->getProperty('isLocalSystemBook') === true) { + return array_values(array_filter($entries, function (IEntry $entry) use ($skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $restrictEnumerationGroup, $restrictEnumerationPhone, $filter) { + if ($entry->getProperty('UID') === $selfUID) { return false; } - // Prevent enumerating local users - if ($disallowEnumeration && $entry->getProperty('isLocalSystemBook')) { - $filterUser = true; + if ($entry->getProperty('isLocalSystemBook')) { + if ($skipLocal) { + return false; + } - $mailAddresses = $entry->getEMailAddresses(); - foreach ($mailAddresses as $mailAddress) { - if ($mailAddress === $filter) { + $checkedCommonGroupAlready = false; + + // Prevent enumerating local users + if ($disallowEnumeration) { + $filterUser = true; + + $mailAddresses = $entry->getEMailAddresses(); + foreach ($mailAddresses as $mailAddress) { + if ($mailAddress === $filter) { + $filterUser = false; + break; + } + } + + if ($entry->getProperty('UID') && $entry->getProperty('UID') === $filter) { $filterUser = false; - break; + } + + if ($filterUser) { + return false; + } + } elseif ($restrictEnumerationPhone || $restrictEnumerationGroup) { + $canEnumerate = false; + if ($restrictEnumerationPhone) { + $canEnumerate = $this->knownUserService->isKnownToUser($selfUID, $entry->getProperty('UID')); + } + + if (!$canEnumerate && $restrictEnumerationGroup) { + $user = $this->userManager->get($entry->getProperty('UID')); + + if ($user === null) { + return false; + } + + $contactGroups = $this->groupManager->getUserGroupIds($user); + $canEnumerate = !empty(array_intersect($contactGroups, $selfGroups)); + $checkedCommonGroupAlready = true; + } + + if (!$canEnumerate) { + return false; } } - if ($entry->getProperty('UID') && $entry->getProperty('UID') === $filter) { - $filterUser = false; - } + if ($ownGroupsOnly && !$checkedCommonGroupAlready) { + $user = $this->userManager->get($entry->getProperty('UID')); - if ($filterUser) { - return false; + if ($user === null) { + return false; + } + + $contactGroups = $this->groupManager->getUserGroupIds($user); + if (empty(array_intersect($contactGroups, $selfGroups))) { + // no groups in common, so shouldn't see the contact + return false; + } } } - if ($ownGroupsOnly && $entry->getProperty('isLocalSystemBook') === true) { - $uid = $this->userManager->get($entry->getProperty('UID')); - - if ($uid === null) { - return false; - } - - $contactGroups = $this->groupManager->getUserGroupIds($uid); - if (count(array_intersect($contactGroups, $selfGroups)) === 0) { - // no groups in common, so shouldn't see the contact - return false; - } - } - - return $entry->getProperty('UID') !== $selfUID; + return true; })); } diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 9a2b413896..d7105873df 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -1822,6 +1822,11 @@ class Manager implements IManager { $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; } + public function limitEnumerationToPhone(): bool { + return $this->allowEnumeration() && + $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; + } + /** * Copied from \OC_Util::isSharingDisabledForUser * diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index 635ccc1483..0c8732b4b1 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -384,6 +384,14 @@ interface IManager { */ public function limitEnumerationToGroups(): bool; + /** + * Check if user enumeration is limited to the phonebook matches + * + * @return bool + * @since 21.0.1 + */ + public function limitEnumerationToPhone(): bool; + /** * Check if sharing is disabled for the given user * diff --git a/tests/lib/Collaboration/Collaborators/MailPluginTest.php b/tests/lib/Collaboration/Collaborators/MailPluginTest.php index 141d4b680b..3128231a10 100644 --- a/tests/lib/Collaboration/Collaborators/MailPluginTest.php +++ b/tests/lib/Collaboration/Collaborators/MailPluginTest.php @@ -26,6 +26,7 @@ namespace Test\Collaboration\Collaborators; use OC\Collaboration\Collaborators\MailPlugin; use OC\Collaboration\Collaborators\SearchResult; use OC\Federation\CloudIdManager; +use OC\KnownUser\KnownUserService; use OCP\Collaboration\Collaborators\SearchResultType; use OCP\Contacts\IManager; use OCP\Federation\ICloudIdManager; @@ -55,6 +56,9 @@ class MailPluginTest extends TestCase { /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ protected $groupManager; + /** @var KnownUserService|\PHPUnit\Framework\MockObject\MockObject */ + protected $knownUserService; + /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ protected $userSession; @@ -64,6 +68,7 @@ class MailPluginTest extends TestCase { $this->config = $this->createMock(IConfig::class); $this->contactsManager = $this->createMock(IManager::class); $this->groupManager = $this->createMock(IGroupManager::class); + $this->knownUserService = $this->createMock(KnownUserService::class); $this->userSession = $this->createMock(IUserSession::class); $this->cloudIdManager = new CloudIdManager($this->contactsManager); @@ -71,7 +76,14 @@ class MailPluginTest extends TestCase { } public function instantiatePlugin() { - $this->plugin = new MailPlugin($this->contactsManager, $this->cloudIdManager, $this->config, $this->groupManager, $this->userSession); + $this->plugin = new MailPlugin( + $this->contactsManager, + $this->cloudIdManager, + $this->config, + $this->groupManager, + $this->knownUserService, + $this->userSession + ); } /** diff --git a/tests/lib/Collaboration/Collaborators/UserPluginTest.php b/tests/lib/Collaboration/Collaborators/UserPluginTest.php index 2806540d00..f2e0e7e274 100644 --- a/tests/lib/Collaboration/Collaborators/UserPluginTest.php +++ b/tests/lib/Collaboration/Collaborators/UserPluginTest.php @@ -25,6 +25,7 @@ namespace Test\Collaboration\Collaborators; use OC\Collaboration\Collaborators\SearchResult; use OC\Collaboration\Collaborators\UserPlugin; +use OC\KnownUser\KnownUserService; use OCP\Collaboration\Collaborators\ISearchResult; use OCP\IConfig; use OCP\IGroup; @@ -49,6 +50,9 @@ class UserPluginTest extends TestCase { /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ protected $session; + /** @var KnownUserService|\PHPUnit\Framework\MockObject\MockObject */ + protected $knownUserService; + /** @var IUserStatusManager|\PHPUnit\Framework\MockObject\MockObject */ protected $userStatusManager; @@ -78,6 +82,8 @@ class UserPluginTest extends TestCase { $this->session = $this->createMock(IUserSession::class); + $this->knownUserService = $this->createMock(KnownUserService::class); + $this->userStatusManager = $this->createMock(IUserStatusManager::class); $this->searchResult = new SearchResult(); @@ -93,6 +99,7 @@ class UserPluginTest extends TestCase { $this->userManager, $this->groupManager, $this->session, + $this->knownUserService, $this->userStatusManager ); } diff --git a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php index acfe83ac55..ad83178096 100644 --- a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php +++ b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php @@ -26,11 +26,13 @@ namespace Tests\Contacts\ContactsMenu; use OC\Contacts\ContactsMenu\ContactsStore; +use OC\KnownUser\KnownUserService; use OCP\Contacts\IManager; use OCP\IConfig; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class ContactsStoreTest extends TestCase { @@ -44,6 +46,8 @@ class ContactsStoreTest extends TestCase { private $groupManager; /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ private $config; + /** @var KnownUserService|MockObject */ + private $knownUserService; protected function setUp(): void { parent::setUp(); @@ -52,7 +56,14 @@ class ContactsStoreTest extends TestCase { $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); $this->config = $this->createMock(IConfig::class); - $this->contactsStore = new ContactsStore($this->contactsManager, $this->config, $this->userManager, $this->groupManager); + $this->knownUserService = $this->createMock(KnownUserService::class); + $this->contactsStore = new ContactsStore( + $this->contactsManager, + $this->config, + $this->userManager, + $this->groupManager, + $this->knownUserService + ); } public function testGetContactsWithoutFilter() { @@ -171,29 +182,16 @@ class ContactsStoreTest extends TestCase { } public function testGetContactsWhenUserIsInExcludeGroups() { - $this->config->expects($this->at(0))->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_allow_share_dialog_user_enumeration'), $this->equalTo('yes')) - ->willReturn('yes'); - - $this->config->expects($this->at(1)) + $this->config ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_restrict_user_enumeration_to_group'), $this->equalTo('no')) - ->willReturn('no'); - - $this->config->expects($this->at(2)) - ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_exclude_groups'), $this->equalTo('no')) - ->willReturn('yes'); - - $this->config->expects($this->at(3)) - ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_only_share_with_group_members'), $this->equalTo('no')) - ->willReturn('yes'); - - $this->config->expects($this->at(4)) - ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_exclude_groups_list'), $this->equalTo('')) - ->willReturn('["group1", "group5", "group6"]'); + ->willReturnMap([ + ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'], + ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'], + ['core', 'shareapi_exclude_groups', 'no', 'yes'], + ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'], + ['core', 'shareapi_exclude_groups_list', '', '["group1", "group5", "group6"]'], + ]); /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */ $currentUser = $this->createMock(IUser::class); @@ -228,22 +226,15 @@ class ContactsStoreTest extends TestCase { } public function testGetContactsOnlyShareIfInTheSameGroup() { - $this->config->expects($this->at(0))->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_allow_share_dialog_user_enumeration'), $this->equalTo('yes')) - ->willReturn('yes'); - - $this->config->expects($this->at(1)) ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_restrict_user_enumeration_to_group'), $this->equalTo('no')) - ->willReturn('no'); - - $this->config->expects($this->at(2)) ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_exclude_groups'), $this->equalTo('no')) - ->willReturn('no'); - - $this->config->expects($this->at(3)) + $this->config ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_only_share_with_group_members'), $this->equalTo('no')) - ->willReturn('yes'); + ->willReturnMap([ + ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'], + ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'], + ['core', 'shareapi_exclude_groups', 'no', 'no'], + ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'], + ]); /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */ $currentUser = $this->createMock(IUser::class); @@ -314,22 +305,15 @@ class ContactsStoreTest extends TestCase { } public function testGetContactsOnlyEnumerateIfInTheSameGroup() { - $this->config->expects($this->at(0))->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_allow_share_dialog_user_enumeration'), $this->equalTo('yes')) - ->willReturn('yes'); - - $this->config->expects($this->at(1)) ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_restrict_user_enumeration_to_group'), $this->equalTo('no')) - ->willReturn('yes'); - - $this->config->expects($this->at(2)) ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_exclude_groups'), $this->equalTo('no')) - ->willReturn('no'); - - $this->config->expects($this->at(3)) + $this->config ->method('getAppValue') - ->with($this->equalTo('core'), $this->equalTo('shareapi_only_share_with_group_members'), $this->equalTo('no')) - ->willReturn('no'); + ->willReturnMap([ + ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'], + ['core', 'shareapi_exclude_groups', 'no', 'no'], + ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'], + ]); /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */ $currentUser = $this->createMock(IUser::class); @@ -399,6 +383,305 @@ class ContactsStoreTest extends TestCase { $this->assertEquals('contact', $entries[2]->getProperty('UID')); } + public function testGetContactsOnlyEnumerateIfPhoneBookMatch() { + $this->config + ->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'], + ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'], + ['core', 'shareapi_exclude_groups', 'no', 'no'], + ['core', 'shareapi_only_share_with_group_members', 'no', 'no'], + ]); + + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */ + $currentUser = $this->createMock(IUser::class); + $currentUser->expects($this->once()) + ->method('getUID') + ->willReturn('user001'); + + $this->groupManager->expects($this->at(0)) + ->method('getUserGroupIds') + ->with($this->equalTo($currentUser)) + ->willReturn(['group1', 'group2', 'group3']); + + $this->knownUserService->method('isKnownToUser') + ->willReturnMap([ + ['user001', 'user1', true], + ['user001', 'user2', true], + ['user001', 'user3', false], + ]); + + $this->contactsManager->expects($this->once()) + ->method('search') + ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL'])) + ->willReturn([ + [ + 'UID' => 'user1', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'user2', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'user3', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'contact', + ], + ]); + + $entries = $this->contactsStore->getContacts($currentUser, ''); + + $this->assertCount(3, $entries); + $this->assertEquals('user1', $entries[0]->getProperty('UID')); + $this->assertEquals('user2', $entries[1]->getProperty('UID')); + $this->assertEquals('contact', $entries[2]->getProperty('UID')); + } + + public function testGetContactsOnlyEnumerateIfPhoneBookMatchWithOwnGroupsOnly() { + $this->config + ->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'], + ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'], + ['core', 'shareapi_exclude_groups', 'no', 'no'], + ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'], + ]); + + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */ + $currentUser = $this->createMock(IUser::class); + $currentUser->expects($this->once()) + ->method('getUID') + ->willReturn('user001'); + + $this->groupManager->expects($this->at(0)) + ->method('getUserGroupIds') + ->with($this->equalTo($currentUser)) + ->willReturn(['group1', 'group2', 'group3']); + + $user1 = $this->createMock(IUser::class); + $this->userManager->expects($this->at(0)) + ->method('get') + ->with('user1') + ->willReturn($user1); + $this->groupManager->expects($this->at(1)) + ->method('getUserGroupIds') + ->with($this->equalTo($user1)) + ->willReturn(['group1']); + $user2 = $this->createMock(IUser::class); + $this->userManager->expects($this->at(1)) + ->method('get') + ->with('user2') + ->willReturn($user2); + $this->groupManager->expects($this->at(2)) + ->method('getUserGroupIds') + ->with($this->equalTo($user2)) + ->willReturn(['group2', 'group3']); + $user3 = $this->createMock(IUser::class); + $this->userManager->expects($this->at(2)) + ->method('get') + ->with('user3') + ->willReturn($user3); + $this->groupManager->expects($this->at(3)) + ->method('getUserGroupIds') + ->with($this->equalTo($user3)) + ->willReturn(['group8', 'group9']); + + $this->knownUserService->method('isKnownToUser') + ->willReturnMap([ + ['user001', 'user1', true], + ['user001', 'user2', true], + ['user001', 'user3', true], + ]); + + $this->contactsManager->expects($this->once()) + ->method('search') + ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL'])) + ->willReturn([ + [ + 'UID' => 'user1', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'user2', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'user3', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'contact', + ], + ]); + + $entries = $this->contactsStore->getContacts($currentUser, ''); + + $this->assertCount(3, $entries); + $this->assertEquals('user1', $entries[0]->getProperty('UID')); + $this->assertEquals('user2', $entries[1]->getProperty('UID')); + $this->assertEquals('contact', $entries[2]->getProperty('UID')); + } + + public function testGetContactsOnlyEnumerateIfPhoneBookOrSameGroup() { + $this->config + ->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'], + ['core', 'shareapi_exclude_groups', 'no', 'no'], + ['core', 'shareapi_only_share_with_group_members', 'no', 'no'], + ]); + + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */ + $currentUser = $this->createMock(IUser::class); + $currentUser->expects($this->once()) + ->method('getUID') + ->willReturn('user001'); + + $this->groupManager->expects($this->at(0)) + ->method('getUserGroupIds') + ->with($this->equalTo($currentUser)) + ->willReturn(['group1', 'group2', 'group3']); + + $user1 = $this->createMock(IUser::class); + $this->userManager->expects($this->at(0)) + ->method('get') + ->with('user1') + ->willReturn($user1); + $this->groupManager->expects($this->at(1)) + ->method('getUserGroupIds') + ->with($this->equalTo($user1)) + ->willReturn(['group1']); + + $this->knownUserService->method('isKnownToUser') + ->willReturnMap([ + ['user001', 'user1', false], + ['user001', 'user2', true], + ['user001', 'user3', true], + ]); + + $this->contactsManager->expects($this->once()) + ->method('search') + ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL'])) + ->willReturn([ + [ + 'UID' => 'user1', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'user2', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'user3', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'contact', + ], + ]); + + $entries = $this->contactsStore->getContacts($currentUser, ''); + + $this->assertCount(4, $entries); + $this->assertEquals('user1', $entries[0]->getProperty('UID')); + $this->assertEquals('user2', $entries[1]->getProperty('UID')); + $this->assertEquals('user3', $entries[2]->getProperty('UID')); + $this->assertEquals('contact', $entries[3]->getProperty('UID')); + } + + public function testGetContactsOnlyEnumerateIfPhoneBookOrSameGroupInOwnGroupsOnly() { + $this->config + ->method('getAppValue') + ->willReturnMap([ + ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'], + ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'], + ['core', 'shareapi_exclude_groups', 'no', 'no'], + ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'], + ]); + + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */ + $currentUser = $this->createMock(IUser::class); + $currentUser->expects($this->once()) + ->method('getUID') + ->willReturn('user001'); + + $this->groupManager->expects($this->at(0)) + ->method('getUserGroupIds') + ->with($this->equalTo($currentUser)) + ->willReturn(['group1', 'group2', 'group3']); + + $user1 = $this->createMock(IUser::class); + $this->userManager->expects($this->at(0)) + ->method('get') + ->with('user1') + ->willReturn($user1); + $this->groupManager->expects($this->at(1)) + ->method('getUserGroupIds') + ->with($this->equalTo($user1)) + ->willReturn(['group1']); + $user2 = $this->createMock(IUser::class); + $this->userManager->expects($this->at(1)) + ->method('get') + ->with('user2') + ->willReturn($user2); + $this->groupManager->expects($this->at(2)) + ->method('getUserGroupIds') + ->with($this->equalTo($user2)) + ->willReturn(['group2', 'group3']); + $user3 = $this->createMock(IUser::class); + $this->userManager->expects($this->at(2)) + ->method('get') + ->with('user3') + ->willReturn($user3); + $this->groupManager->expects($this->at(3)) + ->method('getUserGroupIds') + ->with($this->equalTo($user3)) + ->willReturn(['group8', 'group9']); + + $this->knownUserService->method('isKnownToUser') + ->willReturnMap([ + ['user001', 'user1', false], + ['user001', 'user2', true], + ['user001', 'user3', true], + ]); + + $this->contactsManager->expects($this->once()) + ->method('search') + ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL'])) + ->willReturn([ + [ + 'UID' => 'user1', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'user2', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'user3', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'contact', + ], + ]); + + $entries = $this->contactsStore->getContacts($currentUser, ''); + + $this->assertCount(3, $entries); + $this->assertEquals('user1', $entries[0]->getProperty('UID')); + $this->assertEquals('user2', $entries[1]->getProperty('UID')); + $this->assertEquals('contact', $entries[2]->getProperty('UID')); + } + public function testGetContactsWithFilter() { $this->config->expects($this->at(0))->method('getAppValue') ->with($this->equalTo('core'), $this->equalTo('shareapi_allow_share_dialog_user_enumeration'), $this->equalTo('yes'))