diff --git a/build/integration/features/bootstrap/FeatureContext.php b/build/integration/features/bootstrap/FeatureContext.php index ab37556f93187..ec00c79016014 100644 --- a/build/integration/features/bootstrap/FeatureContext.php +++ b/build/integration/features/bootstrap/FeatureContext.php @@ -25,5 +25,7 @@ protected function resetAppConfigs(): void { $this->deleteServerConfig('bruteForce', 'whitelist_0'); $this->deleteServerConfig('bruteForce', 'whitelist_1'); $this->deleteServerConfig('bruteforcesettings', 'apply_allowlist_to_ratelimit'); + $this->deleteServerConfig('core', 'shareapi_exclude_groups'); + $this->deleteServerConfig('core', 'shareapi_exclude_groups_list'); } } diff --git a/build/integration/features/bootstrap/ShareesContext.php b/build/integration/features/bootstrap/ShareesContext.php index 37e0e63e5472c..4b31d35c933cd 100644 --- a/build/integration/features/bootstrap/ShareesContext.php +++ b/build/integration/features/bootstrap/ShareesContext.php @@ -22,5 +22,7 @@ protected function resetAppConfigs() { $this->deleteServerConfig('core', 'shareapi_only_share_with_group_members'); $this->deleteServerConfig('core', 'shareapi_allow_share_dialog_user_enumeration'); $this->deleteServerConfig('core', 'shareapi_allow_group_sharing'); + $this->deleteServerConfig('core', 'shareapi_exclude_groups'); + $this->deleteServerConfig('core', 'shareapi_exclude_groups_list'); } } diff --git a/build/integration/features/contacts-menu.feature b/build/integration/features/contacts-menu.feature index a3a9e0f666740..c771a3fe2f1bf 100644 --- a/build/integration/features/contacts-menu.feature +++ b/build/integration/features/contacts-menu.feature @@ -71,6 +71,140 @@ Feature: contacts-menu And searched contact "1" is named "Test name" And searched contact "2" is named "user2" + + + Scenario: users can not be searched by display name when searcher belongs to a group excluded from sharing + Given user "user0" exists + And group "ExcludedGroup" exists + And user "user0" belongs to group "ExcludedGroup" + And parameter "shareapi_exclude_groups" of app "core" is set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "ExcludedGroup" + And user "user1" exists + And As an "admin" + And sending "PUT" to "/cloud/users/user1" with + | key | displayname | + | value | Test name | + When Logging in using web as "user0" + And searching for contacts matching with "test" + Then the list of searched contacts has "0" contacts + + Scenario: users can not be searched by email when searcher belongs to a group excluded from sharing + Given user "user0" exists + And group "ExcludedGroup" exists + And user "user0" belongs to group "ExcludedGroup" + And parameter "shareapi_exclude_groups" of app "core" is set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "ExcludedGroup" + And user "user1" exists + And As an "admin" + And sending "PUT" to "/cloud/users/user1" with + | key | email | + | value | test@example.com | + When Logging in using web as "user0" + And searching for contacts matching with "test" + Then the list of searched contacts has "0" contacts + + Scenario: users can be searched by display name when searcher belongs to both a group excluded from sharing and another group + Given user "user0" exists + And group "ExcludedGroup" exists + And user "user0" belongs to group "ExcludedGroup" + And group "AnotherGroup" exists + And user "user0" belongs to group "AnotherGroup" + And parameter "shareapi_exclude_groups" of app "core" is set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "ExcludedGroup" + And user "user1" exists + And As an "admin" + And sending "PUT" to "/cloud/users/user1" with + | key | displayname | + | value | Test name | + When Logging in using web as "user0" + And searching for contacts matching with "test" + Then the list of searched contacts has "1" contacts + And searched contact "0" is named "Test name" + + Scenario: users can be searched by email when searcher belongs to both a group excluded from sharing and another group + Given user "user0" exists + And group "ExcludedGroup" exists + And user "user0" belongs to group "ExcludedGroup" + And group "AnotherGroup" exists + And user "user0" belongs to group "AnotherGroup" + And parameter "shareapi_exclude_groups" of app "core" is set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "ExcludedGroup" + And user "user1" exists + And As an "admin" + And sending "PUT" to "/cloud/users/user1" with + | key | email | + | value | test@example.com | + When Logging in using web as "user0" + And searching for contacts matching with "test" + Then the list of searched contacts has "1" contacts + And searched contact "0" is named "user1" + + Scenario: users can not be searched by display name when searcher does not belong to a group allowed to share + Given user "user0" exists + And group "AllowedGroup" exists + And parameter "shareapi_exclude_groups" of app "core" is set to "allow" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "AllowedGroup" + And user "user1" exists + And As an "admin" + And sending "PUT" to "/cloud/users/user1" with + | key | displayname | + | value | Test name | + When Logging in using web as "user0" + And searching for contacts matching with "test" + Then the list of searched contacts has "0" contacts + + Scenario: users can not be searched by email when searcher does not belong to a group allowed to share + Given user "user0" exists + And group "AllowedGroup" exists + And parameter "shareapi_exclude_groups" of app "core" is set to "allow" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "AllowedGroup" + And user "user1" exists + And As an "admin" + And sending "PUT" to "/cloud/users/user1" with + | key | email | + | value | test@example.com | + When Logging in using web as "user0" + And searching for contacts matching with "test" + Then the list of searched contacts has "0" contacts + + Scenario: users can be searched by display name when searcher belongs to both a group allowed to share and another group + Given user "user0" exists + And group "AllowedGroup" exists + And user "user0" belongs to group "AllowedGroup" + And group "AnotherGroup" exists + And user "user0" belongs to group "AnotherGroup" + And parameter "shareapi_exclude_groups" of app "core" is set to "allow" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "AllowedGroup" + And user "user1" exists + And As an "admin" + And sending "PUT" to "/cloud/users/user1" with + | key | displayname | + | value | Test name | + When Logging in using web as "user0" + And searching for contacts matching with "test" + Then the list of searched contacts has "1" contacts + And searched contact "0" is named "Test name" + + Scenario: users can be searched by email when searcher belongs to both a group allowed to share and another group + Given user "user0" exists + And group "AllowedGroup" exists + And user "user0" belongs to group "AllowedGroup" + And group "AnotherGroup" exists + And user "user0" belongs to group "AnotherGroup" + And parameter "shareapi_exclude_groups" of app "core" is set to "allow" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "AllowedGroup" + And user "user1" exists + And As an "admin" + And sending "PUT" to "/cloud/users/user1" with + | key | email | + | value | test@example.com | + When Logging in using web as "user0" + And searching for contacts matching with "test" + Then the list of searched contacts has "1" contacts + And searched contact "0" is named "user1" + + + Scenario: users can not be found by display name if visibility is private Given user "user0" exists And user "user1" exists diff --git a/build/integration/sharees_features/sharees.feature b/build/integration/sharees_features/sharees.feature index 4ff6d70cc530e..bcfca9dbee456 100644 --- a/build/integration/sharees_features/sharees.feature +++ b/build/integration/sharees_features/sharees.feature @@ -117,6 +117,81 @@ Feature: sharees And "exact remotes" sharees returned is empty And "remotes" sharees returned is empty + Scenario: Search when belonging to a group excluded from sharing + Given As an "test" + And parameter "shareapi_exclude_groups" of app "core" is set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "ShareeGroup" + When getting sharees for + | search | sharee | + | itemType | file | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned is empty + And "users" sharees returned is empty + And "exact groups" sharees returned is empty + And "groups" sharees returned is empty + And "exact remotes" sharees returned is empty + And "remotes" sharees returned is empty + + Scenario: Search when belonging to both a group excluded from sharing and another group + Given As an "test" + And group "AnotherGroup" exists + And user "test" belongs to group "AnotherGroup" + And parameter "shareapi_exclude_groups" of app "core" is set to "yes" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "ShareeGroup" + When getting sharees for + | search | sharee | + | itemType | file | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned is empty + And "users" sharees returned are + | Sharee1 | 0 | Sharee1 | Sharee1 | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | + And "exact groups" sharees returned is empty + And "groups" sharees returned are + | ShareeGroup | 1 | ShareeGroup | + And "exact remotes" sharees returned is empty + And "remotes" sharees returned is empty + + Scenario: Search when not belonging to a group allowed to share + Given As an "test" + And group "AnotherGroup" exists + And parameter "shareapi_exclude_groups" of app "core" is set to "allow" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "AnotherGroup" + When getting sharees for + | search | sharee | + | itemType | file | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned is empty + And "users" sharees returned is empty + And "exact groups" sharees returned is empty + And "groups" sharees returned is empty + And "exact remotes" sharees returned is empty + And "remotes" sharees returned is empty + + Scenario: Search when belonging to both a group allowed to share and another group + Given As an "test" + And group "AnotherGroup" exists + And user "test" belongs to group "AnotherGroup" + And parameter "shareapi_exclude_groups" of app "core" is set to "allow" + And parameter "shareapi_exclude_groups_list" of app "core" is set to "AnotherGroup" + When getting sharees for + | search | sharee | + | itemType | file | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned is empty + And "users" sharees returned are + | Sharee1 | 0 | Sharee1 | Sharee1 | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | + And "exact groups" sharees returned is empty + And "groups" sharees returned are + | ShareeGroup | 1 | ShareeGroup | + And "exact remotes" sharees returned is empty + And "remotes" sharees returned is empty + Scenario: Search without exact match no iteration allowed Given As an "test" And parameter "shareapi_allow_share_dialog_user_enumeration" of app "core" is set to "no" diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index 5fa25512c97bb..f34e10e85d1ff 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -149,7 +149,7 @@ public function getContacts(IUser $user, ?string $filter, ?int $limit = null, ?i * 1. if the `shareapi_allow_share_dialog_user_enumeration` config option is * enabled it will filter all local users * 2. if the `shareapi_exclude_groups` config option is enabled and the - * current user is in an excluded group it will filter all local users. + * current user is only in excluded groups it will filter all local users. * 3. if the `shareapi_only_share_with_group_members` config option is * enabled it will filter all users which doesn't have a common group * with the current user. @@ -184,8 +184,8 @@ private function filterContacts( $excludeGroupsList = $decodedExcludeGroups ?? []; if ($excludeGroups != 'allow') { - if (count(array_intersect($excludeGroupsList, $selfGroups)) !== 0) { - // a group of the current user is excluded -> filter all local users + if (count($selfGroups) > 0 && count(array_diff($selfGroups, $excludeGroupsList)) === 0) { + // all the groups of the current user are excluded -> filter all local users $skipLocal = true; } } else { diff --git a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php index 9097ee779d2b2..795c2c808d722 100644 --- a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php +++ b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php @@ -188,7 +188,94 @@ public function testGetContactsWithoutAvatarURI(): void { $this->assertEquals('https://photo', $entries[1]->getAvatar()); } - public function testGetContactsWhenUserIsInExcludeGroups(): void { + public static function dataGetContactsWhenUserIsInExcludeGroups(): array { + return [ + ['yes', '[]', [], ['user123', 'user12345']], + ['yes', '["excludedGroup1"]', [], ['user123', 'user12345']], + ['yes', '["excludedGroup1"]', ['anotherGroup1'], ['user123', 'user12345']], + ['yes', '["excludedGroup1"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']], + ['yes', '["excludedGroup1"]', ['excludedGroup1'], []], + ['yes', '["excludedGroup1"]', ['anotherGroup1', 'excludedGroup1'], ['user123', 'user12345']], + ['yes', '["excludedGroup1"]', ['excludedGroup1', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', [], ['user123', 'user12345']], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1'], ['user123', 'user12345']], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup1'], []], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup2'], []], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup3'], []], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup1', 'excludedGroup2', 'excludedGroup3'], []], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1', 'excludedGroup1'], ['user123', 'user12345']], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1', 'excludedGroup2', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']], + ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup3', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']], + ['allow', '[]', [], []], + ['allow', '["allowedGroup1"]', [], []], + ['allow', '["allowedGroup1"]', ['anotherGroup1'], []], + ['allow', '["allowedGroup1"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], []], + ['allow', '["allowedGroup1"]', ['allowedGroup1'], ['user123', 'user12345']], + ['allow', '["allowedGroup1"]', ['anotherGroup1', 'allowedGroup1'], ['user123', 'user12345']], + ['allow', '["allowedGroup1"]', ['allowedGroup1', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', [], []], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1'], []], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], []], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup1'], ['user123', 'user12345']], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup2'], ['user123', 'user12345']], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup3'], ['user123', 'user12345']], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup1', 'allowedGroup2', 'allowedGroup3'], ['user123', 'user12345']], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1', 'allowedGroup1'], ['user123', 'user12345']], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1', 'allowedGroup2', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']], + ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup3', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetContactsWhenUserIsInExcludeGroups')] + public function testGetContactsWhenUserIsInExcludeGroups(string $excludeGroups, string $excludeGroupsList, array $currentUserGroupIds, array $expectedUids): void { + $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', 'no'], + ['core', 'shareapi_exclude_groups', 'no', $excludeGroups], + ['core', 'shareapi_only_share_with_group_members', 'no', 'no'], + ['core', 'shareapi_exclude_groups_list', '', $excludeGroupsList], + ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'], + ]); + + /** @var IUser|MockObject $currentUser */ + $currentUser = $this->createMock(IUser::class); + $currentUser->expects($this->exactly(2)) + ->method('getUID') + ->willReturn('user001'); + + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($this->equalTo($currentUser)) + ->willReturn($currentUserGroupIds); + + $this->contactsManager->expects($this->once()) + ->method('search') + ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL'])) + ->willReturn([ + [ + 'UID' => 'user123', + 'isLocalSystemBook' => true + ], + [ + 'UID' => 'user12345', + 'isLocalSystemBook' => true + ], + ]); + + + $entries = $this->contactsStore->getContacts($currentUser, ''); + + $this->assertCount(count($expectedUids), $entries); + for ($i = 0; $i < count($expectedUids); $i++) { + $this->assertEquals($expectedUids[$i], $entries[$i]->getProperty('UID')); + } + } + + public function testGetContactsOnlyShareIfInTheSameGroupWhenUserIsInExcludeGroups(): void { $this->config ->method('getAppValue') ->willReturnMap([