From a164ab84d6ae889bcb89691a2a00e4571d68d64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 20 Mar 2025 08:06:38 +0100 Subject: [PATCH 01/13] test: Check unique display name if provided in the response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- .../integration/features/bootstrap/Sharing.php | 8 +++++++- .../openldap-uid-username.feature | 2 +- .../sharees_features/sharees.feature | 18 +++++++++--------- .../sharees_provisioningapiv2.feature | 16 ++++++++-------- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php index 0cc490ff110..be04c85f52d 100644 --- a/build/integration/features/bootstrap/Sharing.php +++ b/build/integration/features/bootstrap/Sharing.php @@ -734,7 +734,13 @@ trait Sharing { $sharees = []; foreach ($elements[$shareeType] as $element) { - $sharees[] = [$element['label'], $element['value']['shareType'], $element['value']['shareWith']]; + $sharee = [$element['label'], $element['value']['shareType'], $element['value']['shareWith']]; + + if (array_key_exists('shareWithDisplayNameUnique', $element)) { + $sharee[] = $element['shareWithDisplayNameUnique']; + } + + $sharees[] = $sharee; } return $sharees; } diff --git a/build/integration/openldap_features/openldap-uid-username.feature b/build/integration/openldap_features/openldap-uid-username.feature index bee4098972b..02336b05128 100644 --- a/build/integration/openldap_features/openldap-uid-username.feature +++ b/build/integration/openldap_features/openldap-uid-username.feature @@ -161,5 +161,5 @@ Feature: LDAP And the HTTP status code should be "200" And "exact users" sharees returned is empty And "users" sharees returned are - | Elisa | 0 | elisa | + | Elisa | 0 | elisa | elisa@nextcloud.ci | And "exact groups" sharees returned is empty diff --git a/build/integration/sharees_features/sharees.feature b/build/integration/sharees_features/sharees.feature index 9480ef997ae..d8e629532d0 100644 --- a/build/integration/sharees_features/sharees.feature +++ b/build/integration/sharees_features/sharees.feature @@ -18,7 +18,7 @@ Feature: sharees And the HTTP status code should be "200" And "exact users" sharees returned is empty And "users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -34,7 +34,7 @@ Feature: sharees And the HTTP status code should be "200" And "exact users" sharees returned is empty And "users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -68,7 +68,7 @@ Feature: sharees And the HTTP status code should be "200" And "exact users" sharees returned is empty And "users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -85,7 +85,7 @@ Feature: sharees Then the OCS status code should be "100" And the HTTP status code should be "200" And "exact users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "users" sharees returned is empty And "exact groups" sharees returned is empty And "groups" sharees returned is empty @@ -131,7 +131,7 @@ Feature: sharees Then the OCS status code should be "100" And the HTTP status code should be "200" And "exact users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "users" sharees returned is empty And "exact groups" sharees returned is empty And "groups" sharees returned is empty @@ -162,7 +162,7 @@ Feature: sharees Then the OCS status code should be "100" And the HTTP status code should be "200" Then "exact users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | Then "users" sharees returned is empty Then "exact groups" sharees returned is empty Then "groups" sharees returned is empty @@ -177,7 +177,7 @@ Feature: sharees Then the OCS status code should be "100" And the HTTP status code should be "200" Then "exact users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | Then "users" sharees returned is empty Then "exact groups" sharees returned is empty Then "groups" sharees returned is empty @@ -207,7 +207,7 @@ Feature: sharees Then the OCS status code should be "100" And the HTTP status code should be "200" Then "exact users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | Then "users" sharees returned is empty Then "exact groups" sharees returned is empty Then "groups" sharees returned is empty @@ -254,7 +254,7 @@ Feature: sharees And the HTTP status code should be "200" And "exact users" sharees returned is empty And "users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "exact groups" sharees returned is empty And "groups" sharees returned is empty And "exact remotes" sharees returned is empty diff --git a/build/integration/sharees_features/sharees_provisioningapiv2.feature b/build/integration/sharees_features/sharees_provisioningapiv2.feature index b27bb7a4f21..c5a42f7b6a8 100644 --- a/build/integration/sharees_features/sharees_provisioningapiv2.feature +++ b/build/integration/sharees_features/sharees_provisioningapiv2.feature @@ -18,7 +18,7 @@ Feature: sharees_provisioningapiv2 And the HTTP status code should be "200" And "exact users" sharees returned is empty And "users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -34,7 +34,7 @@ Feature: sharees_provisioningapiv2 And the HTTP status code should be "200" And "exact users" sharees returned is empty And "users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -68,7 +68,7 @@ Feature: sharees_provisioningapiv2 And the HTTP status code should be "200" And "exact users" sharees returned is empty And "users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -114,7 +114,7 @@ Feature: sharees_provisioningapiv2 Then the OCS status code should be "200" And the HTTP status code should be "200" And "exact users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "users" sharees returned is empty And "exact groups" sharees returned is empty And "groups" sharees returned is empty @@ -145,7 +145,7 @@ Feature: sharees_provisioningapiv2 Then the OCS status code should be "200" And the HTTP status code should be "200" Then "exact users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | Then "users" sharees returned is empty Then "exact groups" sharees returned is empty Then "groups" sharees returned is empty @@ -160,7 +160,7 @@ Feature: sharees_provisioningapiv2 Then the OCS status code should be "200" And the HTTP status code should be "200" Then "exact users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | Then "users" sharees returned is empty Then "exact groups" sharees returned is empty Then "groups" sharees returned is empty @@ -190,7 +190,7 @@ Feature: sharees_provisioningapiv2 Then the OCS status code should be "200" And the HTTP status code should be "200" Then "exact users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | Then "users" sharees returned is empty Then "exact groups" sharees returned is empty Then "groups" sharees returned is empty @@ -237,7 +237,7 @@ Feature: sharees_provisioningapiv2 And the HTTP status code should be "200" And "exact users" sharees returned is empty And "users" sharees returned are - | Sharee1 | 0 | Sharee1 | + | Sharee1 | 0 | Sharee1 | Sharee1 | And "exact groups" sharees returned is empty And "groups" sharees returned is empty And "exact remotes" sharees returned is empty From 27b57b8c860a5ff993faf13c14c5a5c0b42898a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 20 Mar 2025 08:15:05 +0100 Subject: [PATCH 02/13] test: Fix getting returned sharees when there are several results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- .../features/bootstrap/Sharing.php | 31 +++++++++++++++++++ .../sharees_features/sharees.feature | 4 +++ 2 files changed, 35 insertions(+) diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php index be04c85f52d..ad93db68353 100644 --- a/build/integration/features/bootstrap/Sharing.php +++ b/build/integration/features/bootstrap/Sharing.php @@ -732,6 +732,37 @@ trait Sharing { $shareeType = substr($shareeType, 6); } + // "simplexml_load_string" creates a SimpleXMLElement object for each + // XML element with child elements. In turn, each child is indexed by + // its tag in the SimpleXMLElement object. However, when there are + // several child XML elements with the same tag, an array with all the + // children with the same tag is indexed instead. Therefore, when the + // XML contains + // + // + // + // ... + // + // + // the "$elements[$shareeType]" variable contains an "element" key which + // in turn contains "label" and "value" keys, but when the XML contains + // + // + // + // ... + // + // + // + // ... + // + // + // the "$elements[$shareeType]" variable contains an "element" key which + // in turn contains "0" and "1" keys, and in turn each one contains + // "label" and "value" keys. + if (array_key_exists('element', $elements[$shareeType]) && is_int(array_keys($elements[$shareeType]['element'])[0])) { + $elements[$shareeType] = $elements[$shareeType]['element']; + } + $sharees = []; foreach ($elements[$shareeType] as $element) { $sharee = [$element['label'], $element['value']['shareType'], $element['value']['shareWith']]; diff --git a/build/integration/sharees_features/sharees.feature b/build/integration/sharees_features/sharees.feature index d8e629532d0..0e52c89f778 100644 --- a/build/integration/sharees_features/sharees.feature +++ b/build/integration/sharees_features/sharees.feature @@ -8,6 +8,7 @@ Feature: sharees And user "Sharee1" exists And group "ShareeGroup" exists And user "test" belongs to group "ShareeGroup" + And user "Sharee2" exists Scenario: Search without exact match Given As an "test" @@ -19,6 +20,7 @@ Feature: sharees And "exact users" sharees returned is empty And "users" sharees returned are | Sharee1 | 0 | Sharee1 | Sharee1 | + | Sharee2 | 0 | Sharee2 | Sharee2 | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -35,6 +37,7 @@ Feature: sharees And "exact users" sharees returned is empty And "users" sharees returned are | Sharee1 | 0 | Sharee1 | Sharee1 | + | Sharee2 | 0 | Sharee2 | Sharee2 | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -255,6 +258,7 @@ Feature: sharees And "exact users" sharees returned is empty And "users" sharees returned are | Sharee1 | 0 | Sharee1 | Sharee1 | + | Sharee2 | 0 | Sharee2 | Sharee2 | And "exact groups" sharees returned is empty And "groups" sharees returned is empty And "exact remotes" sharees returned is empty From e68e5c395546361e063fbdf3bac92006fe291940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 20 Mar 2025 08:31:58 +0100 Subject: [PATCH 03/13] test: Add integration tests for getting collaborators by mail addresses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- .../sharees_features/sharees.feature | 188 +++++++++++++++++- 1 file changed, 185 insertions(+), 3 deletions(-) diff --git a/build/integration/sharees_features/sharees.feature b/build/integration/sharees_features/sharees.feature index 0e52c89f778..b2a88701988 100644 --- a/build/integration/sharees_features/sharees.feature +++ b/build/integration/sharees_features/sharees.feature @@ -9,6 +9,13 @@ Feature: sharees And group "ShareeGroup" exists And user "test" belongs to group "ShareeGroup" And user "Sharee2" exists + And As an "admin" + And sending "PUT" to "/cloud/users/Sharee2" with + | key | email | + | value | sharee2@system.com | + And sending "PUT" to "/cloud/users/Sharee2" with + | key | additional_mail | + | value | sharee2@secondary.com | Scenario: Search without exact match Given As an "test" @@ -20,7 +27,7 @@ Feature: sharees And "exact users" sharees returned is empty And "users" sharees returned are | Sharee1 | 0 | Sharee1 | Sharee1 | - | Sharee2 | 0 | Sharee2 | Sharee2 | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -37,7 +44,7 @@ Feature: sharees And "exact users" sharees returned is empty And "users" sharees returned are | Sharee1 | 0 | Sharee1 | Sharee1 | - | Sharee2 | 0 | Sharee2 | Sharee2 | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -258,8 +265,183 @@ Feature: sharees And "exact users" sharees returned is empty And "users" sharees returned are | Sharee1 | 0 | Sharee1 | Sharee1 | - | Sharee2 | 0 | Sharee2 | Sharee2 | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | 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 user by system e-mail address + Given As an "test" + When getting sharees for + | search | sharee2@system.com | + | itemType | file | + | shareType | 0 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + # UserPlugin provides two identical results (except for the field order, but + # that is hidden by the check). + And "exact users" sharees returned are + | Sharee2 | 0 | Sharee2 | sharee2@system.com | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty + + Scenario: Search user by system e-mail address without exact match + Given As an "test" + When getting sharees for + | search | sharee2@system.c | + | itemType | file | + | shareType | 0 | + 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 + | Sharee2 | 0 | Sharee2 | sharee2@system.com | + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty + + Scenario: Search user by secondary e-mail address + Given As an "test" + When getting sharees for + | search | sharee2@secondary.com | + | itemType | file | + | shareType | 0 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + # UserPlugin only searches in the system e-mail address, but not in + # secondary addresses. + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty + + Scenario: Search user by secondary e-mail address without exact match + Given As an "test" + When getting sharees for + | search | sharee2@secondary.c | + | itemType | file | + | shareType | 0 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned is empty + # UserPlugin only searches in the system e-mail address, but not in + # secondary addresses. + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty + + Scenario: Search e-mail + Given As an "test" + When getting sharees for + | search | sharee2@unknown.com | + | itemType | file | + | shareType | 4 | + 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 + And "exact emails" sharees returned are + | sharee2@unknown.com | 4 | sharee2@unknown.com | + And "emails" sharees returned is empty + + Scenario: Search e-mail matching system e-mail address of user + Given As an "test" + When getting sharees for + | search | sharee2@system.com | + | itemType | file | + | shareType | 4 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned are + | Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com | + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty + + Scenario: Search e-mail partially matching system e-mail address of user + Given As an "test" + When getting sharees for + | search | sharee2@system.c | + | itemType | file | + | shareType | 4 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned is empty + # MailPlugin adds a result for every e-mail address of the contact unless + # there is an exact match. + And "users" sharees returned are + | Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com | + | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | + 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 + And "exact emails" sharees returned are + | sharee2@system.c | 4 | sharee2@system.c | + And "emails" sharees returned is empty + + Scenario: Search e-mail matching secondary e-mail address of user + Given As an "test" + When getting sharees for + | search | sharee2@secondary.com | + | itemType | file | + | shareType | 4 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned are + | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty + + Scenario: Search e-mail partially matching secondary e-mail address of user + Given As an "test" + When getting sharees for + | search | sharee2@secondary.c | + | itemType | file | + | shareType | 4 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned is empty + # MailPlugin adds a result for every e-mail address of the contact unless + # there is an exact match. + And "users" sharees returned are + | Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com | + | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | + 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 + And "exact emails" sharees returned are + | sharee2@secondary.c | 4 | sharee2@secondary.c | + And "emails" sharees returned is empty From e4d58d35fae3a3f39c2e2f8a83641479e3f70aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 20 Mar 2025 08:34:50 +0100 Subject: [PATCH 04/13] test: Add integration tests for getting user and mail collaborators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OCS endpoint expects either an int or an array for "shareType". However, when using "getRowsHash()" only a single key is taken into account, so instead of: | shareType[] | 0 | | shareType[] | 4 | the share types are provided in a single row like: | shareTypes | 0 4 | and then converted to "shareType[]=0&shareType[]=4" when sending the request. Signed-off-by: Daniel Calviño Sánchez --- .../features/bootstrap/Sharing.php | 8 +++- .../sharees_features/sharees.feature | 39 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php index ad93db68353..d93f114f27b 100644 --- a/build/integration/features/bootstrap/Sharing.php +++ b/build/integration/features/bootstrap/Sharing.php @@ -697,7 +697,13 @@ trait Sharing { if ($body instanceof TableNode) { $parameters = []; foreach ($body->getRowsHash() as $key => $value) { - $parameters[] = $key . '=' . $value; + if ($key === 'shareTypes') { + foreach (explode(' ', $value) as $shareType) { + $parameters[] = 'shareType[]=' . $shareType; + } + } else { + $parameters[] = $key . '=' . $value; + } } if (!empty($parameters)) { $url .= '?' . implode('&', $parameters); diff --git a/build/integration/sharees_features/sharees.feature b/build/integration/sharees_features/sharees.feature index b2a88701988..57d5d1dc6dd 100644 --- a/build/integration/sharees_features/sharees.feature +++ b/build/integration/sharees_features/sharees.feature @@ -445,3 +445,42 @@ Feature: sharees And "exact emails" sharees returned are | sharee2@secondary.c | 4 | sharee2@secondary.c | And "emails" sharees returned is empty + + Scenario: Search user and e-mail matching system e-mail address of user + Given As an "test" + When getting sharees for + | search | sharee2@system.com | + | itemType | file | + | shareTypes | 0 4 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + # UserPlugin provides two identical results (except for the field order, but + # that is hidden by the check) + And "exact users" sharees returned are + | Sharee2 | 0 | Sharee2 | sharee2@system.com | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty + + Scenario: Search user and e-mail matching secondary e-mail address of user + Given As an "test" + When getting sharees for + | search | sharee2@secondary.com | + | itemType | file | + | shareTypes | 0 4 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And "exact users" sharees returned are + | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty From 7441f15a94dff5831e71ec04a52ec42e53749961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Fri, 26 Sep 2025 15:34:13 +0200 Subject: [PATCH 05/13] test: Extend tests to check the same cases with and without full match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- .../collaboration_features/autocomplete.feature | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/build/integration/collaboration_features/autocomplete.feature b/build/integration/collaboration_features/autocomplete.feature index 5044758a70f..ef4ee27bfda 100644 --- a/build/integration/collaboration_features/autocomplete.feature +++ b/build/integration/collaboration_features/autocomplete.feature @@ -47,11 +47,20 @@ Feature: autocomplete | key | email | | value | autocomplete@example.com | And there is a contact in an addressbook + When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no" + Then get email autocomplete for "auto" + | id | source | + | autocomplete | users | Then get email autocomplete for "example" | id | source | | autocomplete | users | | leon@example.com | emails | | user@example.com | emails | + Then get email autocomplete for "autocomplete@example.com" + | id | source | + | autocomplete | users | + | autocomplete@example.com | emails | + When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "yes" Then get email autocomplete for "auto" | id | source | | autocomplete | users | @@ -72,10 +81,15 @@ Feature: autocomplete And there is a contact in an addressbook And parameter "shareapi_allow_share_dialog_user_enumeration" of app "core" is set to "no" When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no" + Then get email autocomplete for "auto" + | id | source | Then get email autocomplete for "example" | id | source | | leon@example.com | emails | | user@example.com | emails | + Then get email autocomplete for "autocomplete@example.com" + | id | source | + | autocomplete@example.com | emails | When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "yes" Then get email autocomplete for "auto" | id | source | From c1b475b75e3471b9bf517f6f048093d347003749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Fri, 26 Sep 2025 18:18:34 +0200 Subject: [PATCH 06/13] test: Add tests to get user autocompletes similar to the email ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- .../autocomplete.feature | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/build/integration/collaboration_features/autocomplete.feature b/build/integration/collaboration_features/autocomplete.feature index ef4ee27bfda..e9820f28be5 100644 --- a/build/integration/collaboration_features/autocomplete.feature +++ b/build/integration/collaboration_features/autocomplete.feature @@ -41,6 +41,63 @@ Feature: autocomplete Then get autocomplete for "autocomplete" | id | source | + Scenario: getting autocomplete from address book with enumeration + Given As an "admin" + And sending "PUT" to "/cloud/users/autocomplete" with + | key | email | + | value | autocomplete@example.com | + And there is a contact in an addressbook + When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no" + Then get autocomplete for "auto" + | id | source | + | auto | users | + | autocomplete | users | + | autocomplete2 | users | + Then get autocomplete for "example" + | id | source | + | autocomplete | users | + Then get autocomplete for "autocomplete@example.com" + | id | source | + | autocomplete | users | + When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "yes" + Then get autocomplete for "auto" + | id | source | + | auto | users | + | autocomplete | users | + | autocomplete2 | users | + Then get autocomplete for "example" + | id | source | + | autocomplete | users | + Then get autocomplete for "autocomplete@example.com" + | id | source | + | autocomplete | users | + | autocomplete | users | + + Scenario: getting autocomplete from address book without enumeration + Given As an "admin" + And sending "PUT" to "/cloud/users/autocomplete" with + | key | email | + | value | autocomplete@example.com | + And there is a contact in an addressbook + And parameter "shareapi_allow_share_dialog_user_enumeration" of app "core" is set to "no" + When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no" + Then get autocomplete for "auto" + | id | source | + Then get autocomplete for "example" + | id | source | + Then get autocomplete for "autocomplete@example.com" + | id | source | + When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "yes" + Then get autocomplete for "auto" + | id | source | + | auto | users | + Then get autocomplete for "example" + | id | source | + Then get autocomplete for "autocomplete@example.com" + | id | source | + | autocomplete | users | + | autocomplete | users | + Scenario: getting autocomplete emails from address book with enumeration Given As an "admin" And sending "PUT" to "/cloud/users/autocomplete" with From cee2492196c509d555366c4adecceb90a4ee5692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sat, 4 Oct 2025 01:35:13 +0200 Subject: [PATCH 07/13] test: Extract function to get apps with certain enabled state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- .../features/bootstrap/Provisioning.php | 45 ++++++------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/build/integration/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php index 935ad2a4a1d..1d0984abd72 100644 --- a/build/integration/features/bootstrap/Provisioning.php +++ b/build/integration/features/bootstrap/Provisioning.php @@ -803,13 +803,8 @@ trait Provisioning { return $extractedElementsArray; } - - /** - * @Given /^app "([^"]*)" is disabled$/ - * @param string $app - */ - public function appIsDisabled($app) { - $fullUrl = $this->baseUrl . 'v2.php/cloud/apps?filter=disabled'; + private function getAppsWithFilter($filter) { + $fullUrl = $this->baseUrl . 'v2.php/cloud/apps?filter=' . $filter; $client = new Client(); $options = []; if ($this->currentUser === 'admin') { @@ -820,7 +815,15 @@ trait Provisioning { ]; $this->response = $client->get($fullUrl, $options); - $respondedArray = $this->getArrayOfAppsResponded($this->response); + return $this->getArrayOfAppsResponded($this->response); + } + + /** + * @Given /^app "([^"]*)" is disabled$/ + * @param string $app + */ + public function appIsDisabled($app) { + $respondedArray = $this->getAppsWithFilter('disabled'); Assert::assertContains($app, $respondedArray); Assert::assertEquals(200, $this->response->getStatusCode()); } @@ -830,18 +833,7 @@ trait Provisioning { * @param string $app */ public function appIsEnabled($app) { - $fullUrl = $this->baseUrl . 'v2.php/cloud/apps?filter=enabled'; - $client = new Client(); - $options = []; - if ($this->currentUser === 'admin') { - $options['auth'] = $this->adminUser; - } - $options['headers'] = [ - 'OCS-APIREQUEST' => 'true', - ]; - - $this->response = $client->get($fullUrl, $options); - $respondedArray = $this->getArrayOfAppsResponded($this->response); + $respondedArray = $this->getAppsWithFilter('enabled'); Assert::assertContains($app, $respondedArray); Assert::assertEquals(200, $this->response->getStatusCode()); } @@ -854,18 +846,7 @@ trait Provisioning { * @param string $app */ public function appIsNotEnabled($app) { - $fullUrl = $this->baseUrl . 'v2.php/cloud/apps?filter=enabled'; - $client = new Client(); - $options = []; - if ($this->currentUser === 'admin') { - $options['auth'] = $this->adminUser; - } - $options['headers'] = [ - 'OCS-APIREQUEST' => 'true', - ]; - - $this->response = $client->get($fullUrl, $options); - $respondedArray = $this->getArrayOfAppsResponded($this->response); + $respondedArray = $this->getAppsWithFilter('enabled'); Assert::assertNotContains($app, $respondedArray); Assert::assertEquals(200, $this->response->getStatusCode()); } From dcda12c5dba2d22fd48ba93ce18570cae55dd9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sat, 4 Oct 2025 01:37:52 +0200 Subject: [PATCH 08/13] test: Add integration tests to get collaborators without sharebymail app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "sharebymail" app is enabled by default, so it needs to be enabled once the scenario ends as other scenarios could expect that the app is enabled. To solve that now a special step is added that records the enabled state of the given app and restores it once the scenario ends. This step only restores the state of already installed apps. If an app is installed during the test it will not be neither disabled nor uninstalled after the test ends. Therefore, at least for now, it is necessary to explicitly call the step to record the app to be restored, rather than automatically keeping track of the changes in the enabled state of the apps during the scenario. Signed-off-by: Daniel Calviño Sánchez --- .../features/bootstrap/Provisioning.php | 34 ++++++++++ .../sharees_features/sharees.feature | 64 +++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/build/integration/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php index 1d0984abd72..16fb9e297c7 100644 --- a/build/integration/features/bootstrap/Provisioning.php +++ b/build/integration/features/bootstrap/Provisioning.php @@ -16,6 +16,12 @@ require __DIR__ . '/../../vendor/autoload.php'; trait Provisioning { use BasicStructure; + /** @var array */ + private $appsToEnableAfterScenario = []; + + /** @var array */ + private $appsToDisableAfterScenario = []; + /** @var array */ private $createdUsers = []; @@ -28,6 +34,19 @@ trait Provisioning { /** @var array */ private $createdGroups = []; + /** @AfterScenario */ + public function restoreAppsEnabledStateAfterScenario() { + $this->asAn('admin'); + + foreach ($this->appsToEnableAfterScenario as $app) { + $this->sendingTo('POST', '/cloud/apps/' . $app); + } + + foreach ($this->appsToDisableAfterScenario as $app) { + $this->sendingTo('DELETE', '/cloud/apps/' . $app); + } + } + /** * @Given /^user "([^"]*)" exists$/ * @param string $user @@ -803,6 +822,21 @@ trait Provisioning { return $extractedElementsArray; } + /** + * @Given /^app "([^"]*)" enabled state will be restored once the scenario finishes$/ + * @param string $app + */ + public function appEnabledStateWillBeRestoredOnceTheScenarioFinishes($app) { + if (in_array($app, $this->getAppsWithFilter('enabled'))) { + $this->appsToEnableAfterScenario[] = $app; + } elseif (in_array($app, $this->getAppsWithFilter('disabled'))) { + $this->appsToDisableAfterScenario[] = $app; + } + + // Apps that were not installed before the scenario will not be + // disabled nor uninstalled after the scenario. + } + private function getAppsWithFilter($filter) { $fullUrl = $this->baseUrl . 'v2.php/cloud/apps?filter=' . $filter; $client = new Client(); diff --git a/build/integration/sharees_features/sharees.feature b/build/integration/sharees_features/sharees.feature index 57d5d1dc6dd..e9e7b7ab6fb 100644 --- a/build/integration/sharees_features/sharees.feature +++ b/build/integration/sharees_features/sharees.feature @@ -366,6 +366,26 @@ Feature: sharees | sharee2@unknown.com | 4 | sharee2@unknown.com | And "emails" sharees returned is empty + Scenario: Search e-mail when sharebymail app is disabled + Given app "sharebymail" enabled state will be restored once the scenario finishes + And sending "DELETE" to "/cloud/apps/sharebymail" + And app "sharebymail" is disabled + And As an "test" + When getting sharees for + | search | sharee2@unknown.com | + | itemType | file | + | shareType | 4 | + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty + Scenario: Search e-mail matching system e-mail address of user Given As an "test" When getting sharees for @@ -467,6 +487,30 @@ Feature: sharees And "exact emails" sharees returned is empty And "emails" sharees returned is empty + Scenario: Search user and e-mail matching system e-mail address of user when sharebymail app is disabled + Given app "sharebymail" enabled state will be restored once the scenario finishes + And sending "DELETE" to "/cloud/apps/sharebymail" + And app "sharebymail" is disabled + And As an "test" + When getting sharees for + | search | sharee2@system.com | + | itemType | file | + | shareTypes | 0 4 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + # UserPlugin provides two identical results (except for the field order, but + # that is hidden by the check) + And "exact users" sharees returned are + | Sharee2 | 0 | Sharee2 | sharee2@system.com | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty + Scenario: Search user and e-mail matching secondary e-mail address of user Given As an "test" When getting sharees for @@ -484,3 +528,23 @@ Feature: sharees And "remotes" sharees returned is empty And "exact emails" sharees returned is empty And "emails" sharees returned is empty + + Scenario: Search user and e-mail matching secondary e-mail address of user when sharebymail app is disabled + Given app "sharebymail" enabled state will be restored once the scenario finishes + And sending "DELETE" to "/cloud/apps/sharebymail" + And app "sharebymail" is disabled + And As an "test" + When getting sharees for + | search | sharee2@secondary.com | + | itemType | file | + | shareTypes | 0 4 | + 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 + And "exact emails" sharees returned is empty + And "emails" sharees returned is empty From 45f2683a7398042fa55e4444a7f67f1c921b9a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sat, 5 Apr 2025 11:55:17 +0200 Subject: [PATCH 09/13] test: Fix parameter documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- tests/lib/Collaboration/Collaborators/MailPluginTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/lib/Collaboration/Collaborators/MailPluginTest.php b/tests/lib/Collaboration/Collaborators/MailPluginTest.php index 6c81a0d68e2..e74c901a716 100644 --- a/tests/lib/Collaboration/Collaborators/MailPluginTest.php +++ b/tests/lib/Collaboration/Collaborators/MailPluginTest.php @@ -90,7 +90,9 @@ class MailPluginTest extends TestCase { * @param array $contacts * @param bool $shareeEnumeration * @param array $expected + * @param bool $exactIdMatch * @param bool $reachedEnd + * @param bool $validEmail */ #[\PHPUnit\Framework\Attributes\DataProvider('dataGetEmail')] public function testSearch($searchTerm, $contacts, $shareeEnumeration, $expected, $exactIdMatch, $reachedEnd, $validEmail): void { @@ -570,7 +572,8 @@ class MailPluginTest extends TestCase { * @param array $expected * @param bool $exactIdMatch * @param bool $reachedEnd - * @param array groups + * @param array $userToGroupMapping + * @param bool $validEmail */ #[\PHPUnit\Framework\Attributes\DataProvider('dataGetEmailGroupsOnly')] public function testSearchGroupsOnly($searchTerm, $contacts, $expected, $exactIdMatch, $reachedEnd, $userToGroupMapping, $validEmail): void { From a0e8d943f1f63bc47998efbd04d398645a691167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 31 Mar 2025 21:04:21 +0200 Subject: [PATCH 10/13] test: Rename parameters to show that they are expected values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- .../Collaborators/MailPluginTest.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/lib/Collaboration/Collaborators/MailPluginTest.php b/tests/lib/Collaboration/Collaborators/MailPluginTest.php index e74c901a716..48167301790 100644 --- a/tests/lib/Collaboration/Collaborators/MailPluginTest.php +++ b/tests/lib/Collaboration/Collaborators/MailPluginTest.php @@ -89,13 +89,13 @@ class MailPluginTest extends TestCase { * @param string $searchTerm * @param array $contacts * @param bool $shareeEnumeration - * @param array $expected - * @param bool $exactIdMatch - * @param bool $reachedEnd + * @param array $expectedResult + * @param bool $expectedExactIdMatch + * @param bool $expectedMoreResults * @param bool $validEmail */ #[\PHPUnit\Framework\Attributes\DataProvider('dataGetEmail')] - public function testSearch($searchTerm, $contacts, $shareeEnumeration, $expected, $exactIdMatch, $reachedEnd, $validEmail): void { + public function testSearch($searchTerm, $contacts, $shareeEnumeration, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $validEmail): void { $this->config->expects($this->any()) ->method('getAppValue') ->willReturnCallback( @@ -127,9 +127,9 @@ class MailPluginTest extends TestCase { $moreResults = $this->plugin->search($searchTerm, 2, 0, $this->searchResult); $result = $this->searchResult->asArray(); - $this->assertSame($exactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails'))); - $this->assertEquals($expected, $result); - $this->assertSame($reachedEnd, $moreResults); + $this->assertSame($expectedExactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails'))); + $this->assertEquals($expectedResult, $result); + $this->assertSame($expectedMoreResults, $moreResults); } public static function dataGetEmail(): array { @@ -569,14 +569,14 @@ class MailPluginTest extends TestCase { * * @param string $searchTerm * @param array $contacts - * @param array $expected - * @param bool $exactIdMatch - * @param bool $reachedEnd + * @param array $expectedResult + * @param bool $expectedExactIdMatch + * @param bool $expectedMoreResults * @param array $userToGroupMapping * @param bool $validEmail */ #[\PHPUnit\Framework\Attributes\DataProvider('dataGetEmailGroupsOnly')] - public function testSearchGroupsOnly($searchTerm, $contacts, $expected, $exactIdMatch, $reachedEnd, $userToGroupMapping, $validEmail): void { + public function testSearchGroupsOnly($searchTerm, $contacts, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $userToGroupMapping, $validEmail): void { $this->config->expects($this->any()) ->method('getAppValue') ->willReturnCallback( @@ -627,9 +627,9 @@ class MailPluginTest extends TestCase { $moreResults = $this->plugin->search($searchTerm, 2, 0, $this->searchResult); $result = $this->searchResult->asArray(); - $this->assertSame($exactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails'))); - $this->assertEquals($expected, $result); - $this->assertSame($reachedEnd, $moreResults); + $this->assertSame($expectedExactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails'))); + $this->assertEquals($expectedResult, $result); + $this->assertSame($expectedMoreResults, $moreResults); } public static function dataGetEmailGroupsOnly(): array { From 74fd144003f28c3758593a79d7dd4002a325200e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Mon, 31 Mar 2025 21:18:43 +0200 Subject: [PATCH 11/13] test: Replace magic value with named constant of share type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- tests/lib/Collaboration/Collaborators/MailPluginTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lib/Collaboration/Collaborators/MailPluginTest.php b/tests/lib/Collaboration/Collaborators/MailPluginTest.php index 48167301790..eec8bdbb2b2 100644 --- a/tests/lib/Collaboration/Collaborators/MailPluginTest.php +++ b/tests/lib/Collaboration/Collaborators/MailPluginTest.php @@ -646,7 +646,7 @@ class MailPluginTest extends TestCase { 'UID' => 'User' ] ], - ['users' => [['label' => 'User (test@example.com)', 'uuid' => 'User', 'name' => 'User', 'value' => ['shareType' => 0, 'shareWith' => 'test'],'shareWithDisplayNameUnique' => 'test@example.com',]], 'emails' => [], 'exact' => ['emails' => [], 'users' => []]], + ['users' => [['label' => 'User (test@example.com)', 'uuid' => 'User', 'name' => 'User', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'],'shareWithDisplayNameUnique' => 'test@example.com',]], 'emails' => [], 'exact' => ['emails' => [], 'users' => []]], false, false, [ @@ -688,7 +688,7 @@ class MailPluginTest extends TestCase { 'UID' => 'User' ] ], - ['emails' => [], 'exact' => ['emails' => [['label' => 'test@example.com', 'uuid' => 'test@example.com', 'value' => ['shareType' => 4,'shareWith' => 'test@example.com']]]]], + ['emails' => [], 'exact' => ['emails' => [['label' => 'test@example.com', 'uuid' => 'test@example.com', 'value' => ['shareType' => IShare::TYPE_EMAIL,'shareWith' => 'test@example.com']]]]], false, false, [ From 2c841b233754c324d6fe079148ff52023b3176f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Sat, 5 Apr 2025 12:08:10 +0200 Subject: [PATCH 12/13] test: Rename data providers to match the name of their tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Calviño Sánchez --- tests/lib/Collaboration/Collaborators/MailPluginTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/lib/Collaboration/Collaborators/MailPluginTest.php b/tests/lib/Collaboration/Collaborators/MailPluginTest.php index eec8bdbb2b2..6f0d52efca5 100644 --- a/tests/lib/Collaboration/Collaborators/MailPluginTest.php +++ b/tests/lib/Collaboration/Collaborators/MailPluginTest.php @@ -94,7 +94,7 @@ class MailPluginTest extends TestCase { * @param bool $expectedMoreResults * @param bool $validEmail */ - #[\PHPUnit\Framework\Attributes\DataProvider('dataGetEmail')] + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearch')] public function testSearch($searchTerm, $contacts, $shareeEnumeration, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $validEmail): void { $this->config->expects($this->any()) ->method('getAppValue') @@ -132,7 +132,7 @@ class MailPluginTest extends TestCase { $this->assertSame($expectedMoreResults, $moreResults); } - public static function dataGetEmail(): array { + public static function dataSearch(): array { return [ // data set 0 ['test', [], true, ['emails' => [], 'exact' => ['emails' => []]], false, false, false], @@ -575,7 +575,7 @@ class MailPluginTest extends TestCase { * @param array $userToGroupMapping * @param bool $validEmail */ - #[\PHPUnit\Framework\Attributes\DataProvider('dataGetEmailGroupsOnly')] + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchGroupsOnly')] public function testSearchGroupsOnly($searchTerm, $contacts, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $userToGroupMapping, $validEmail): void { $this->config->expects($this->any()) ->method('getAppValue') @@ -632,7 +632,7 @@ class MailPluginTest extends TestCase { $this->assertSame($expectedMoreResults, $moreResults); } - public static function dataGetEmailGroupsOnly(): array { + public static function dataSearchGroupsOnly(): array { return [ // The user `User` can share with the current user [ From c40fcba5a49388ec9d68dd9f623eabd02312dec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Tue, 8 Apr 2025 03:05:30 +0200 Subject: [PATCH 13/13] fix: Fix user collaborators returned when searching for mail collaborators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MailPlugin collaborator returned results for both user and mail collaborators, but it was registered only for mail collaborators. While it might make sense to move the user results to the UserPlugin instead that change would be more complex and riskier, so for now the MailPlugin is now registered for both user and mail collaborators and the results are limited only to the registered type. As the plugins are registered only with their class and then resolved when needed using dependency injection it is not possible (as far as I know) to provide an explicit parameter in the constructor to differentiate whether the MailPlugin should return user or mail collaborators. To overcome this two subclasses are introduced, MailByMailPlugin and UserByMailPlugin, which just hardcode in their constructor the collaborator type that their parent MailPlugin must use, and those subclasses are the ones registered instead of the MailPlugin (which still contains all the logic). Signed-off-by: Daniel Calviño Sánchez --- .../autocomplete.feature | 7 - .../sharees_features/sharees.feature | 32 +- lib/composer/composer/autoload_classmap.php | 2 + lib/composer/composer/autoload_static.php | 2 + .../Collaborators/MailByMailPlugin.php | 45 ++ .../Collaborators/MailPlugin.php | 40 +- .../Collaborators/UserByMailPlugin.php | 45 ++ lib/private/Server.php | 6 +- .../Collaborators/MailPluginTest.php | 476 +++++++++++++++++- 9 files changed, 589 insertions(+), 66 deletions(-) create mode 100644 lib/private/Collaboration/Collaborators/MailByMailPlugin.php create mode 100644 lib/private/Collaboration/Collaborators/UserByMailPlugin.php diff --git a/build/integration/collaboration_features/autocomplete.feature b/build/integration/collaboration_features/autocomplete.feature index e9820f28be5..763b95bf170 100644 --- a/build/integration/collaboration_features/autocomplete.feature +++ b/build/integration/collaboration_features/autocomplete.feature @@ -107,28 +107,22 @@ Feature: autocomplete When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no" Then get email autocomplete for "auto" | id | source | - | autocomplete | users | Then get email autocomplete for "example" | id | source | - | autocomplete | users | | leon@example.com | emails | | user@example.com | emails | Then get email autocomplete for "autocomplete@example.com" | id | source | - | autocomplete | users | | autocomplete@example.com | emails | When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "yes" Then get email autocomplete for "auto" | id | source | - | autocomplete | users | Then get email autocomplete for "example" | id | source | - | autocomplete | users | | leon@example.com | emails | | user@example.com | emails | Then get email autocomplete for "autocomplete@example.com" | id | source | - | autocomplete | users | Scenario: getting autocomplete emails from address book without enumeration Given As an "admin" @@ -156,7 +150,6 @@ Feature: autocomplete | user@example.com | emails | Then get email autocomplete for "autocomplete@example.com" | id | source | - | autocomplete | users | Scenario: getting autocomplete with limited enumeration by group Given As an "admin" diff --git a/build/integration/sharees_features/sharees.feature b/build/integration/sharees_features/sharees.feature index e9e7b7ab6fb..4ff6d70cc53 100644 --- a/build/integration/sharees_features/sharees.feature +++ b/build/integration/sharees_features/sharees.feature @@ -281,6 +281,7 @@ Feature: sharees And the HTTP status code should be "200" # UserPlugin provides two identical results (except for the field order, but # that is hidden by the check). + # MailPlugin does not add a result if there is already one for that user. And "exact users" sharees returned are | Sharee2 | 0 | Sharee2 | sharee2@system.com | | Sharee2 | 0 | Sharee2 | sharee2@system.com | @@ -301,6 +302,7 @@ Feature: sharees Then the OCS status code should be "100" And the HTTP status code should be "200" And "exact users" sharees returned is empty + # MailPlugin does not add a result if there is already one for that user. And "users" sharees returned are | Sharee2 | 0 | Sharee2 | sharee2@system.com | And "exact groups" sharees returned is empty @@ -320,7 +322,8 @@ Feature: sharees And the HTTP status code should be "200" # UserPlugin only searches in the system e-mail address, but not in # secondary addresses. - And "exact users" sharees returned is empty + And "exact users" sharees returned are + | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | And "users" sharees returned is empty And "exact groups" sharees returned is empty And "groups" sharees returned is empty @@ -340,7 +343,11 @@ Feature: sharees And "exact users" sharees returned is empty # UserPlugin only searches in the system e-mail address, but not in # secondary addresses. - And "users" sharees returned is empty + # MailPlugin adds a result for every e-mail address of the contact unless + # there is an exact match. + And "users" sharees returned are + | Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com | + | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | And "exact groups" sharees returned is empty And "groups" sharees returned is empty And "exact remotes" sharees returned is empty @@ -394,8 +401,7 @@ Feature: sharees | shareType | 4 | Then the OCS status code should be "100" And the HTTP status code should be "200" - And "exact users" sharees returned are - | Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com | + 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 @@ -413,11 +419,7 @@ Feature: sharees Then the OCS status code should be "100" And the HTTP status code should be "200" And "exact users" sharees returned is empty - # MailPlugin adds a result for every e-mail address of the contact unless - # there is an exact match. - And "users" sharees returned are - | Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com | - | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | + 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 @@ -434,8 +436,7 @@ Feature: sharees | shareType | 4 | Then the OCS status code should be "100" And the HTTP status code should be "200" - And "exact users" sharees returned are - | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | + 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 @@ -453,11 +454,7 @@ Feature: sharees Then the OCS status code should be "100" And the HTTP status code should be "200" And "exact users" sharees returned is empty - # MailPlugin adds a result for every e-mail address of the contact unless - # there is an exact match. - And "users" sharees returned are - | Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com | - | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | + 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 @@ -540,7 +537,8 @@ Feature: sharees | shareTypes | 0 4 | Then the OCS status code should be "100" And the HTTP status code should be "200" - And "exact users" sharees returned is empty + And "exact users" sharees returned are + | Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com | And "users" sharees returned is empty And "exact groups" sharees returned is empty And "groups" sharees returned is empty diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 1810eab032c..16eb03b15cf 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1202,11 +1202,13 @@ return array( 'OC\\Collaboration\\AutoComplete\\Manager' => $baseDir . '/lib/private/Collaboration/AutoComplete/Manager.php', 'OC\\Collaboration\\Collaborators\\GroupPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/GroupPlugin.php', 'OC\\Collaboration\\Collaborators\\LookupPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/LookupPlugin.php', + 'OC\\Collaboration\\Collaborators\\MailByMailPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/MailByMailPlugin.php', 'OC\\Collaboration\\Collaborators\\MailPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/MailPlugin.php', 'OC\\Collaboration\\Collaborators\\RemoteGroupPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php', 'OC\\Collaboration\\Collaborators\\RemotePlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/RemotePlugin.php', 'OC\\Collaboration\\Collaborators\\Search' => $baseDir . '/lib/private/Collaboration/Collaborators/Search.php', 'OC\\Collaboration\\Collaborators\\SearchResult' => $baseDir . '/lib/private/Collaboration/Collaborators/SearchResult.php', + 'OC\\Collaboration\\Collaborators\\UserByMailPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/UserByMailPlugin.php', 'OC\\Collaboration\\Collaborators\\UserPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/UserPlugin.php', 'OC\\Collaboration\\Reference\\File\\FileReferenceEventListener' => $baseDir . '/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php', 'OC\\Collaboration\\Reference\\File\\FileReferenceProvider' => $baseDir . '/lib/private/Collaboration/Reference/File/FileReferenceProvider.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 753e56107d0..da38482d5ce 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1243,11 +1243,13 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Collaboration\\AutoComplete\\Manager' => __DIR__ . '/../../..' . '/lib/private/Collaboration/AutoComplete/Manager.php', 'OC\\Collaboration\\Collaborators\\GroupPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/GroupPlugin.php', 'OC\\Collaboration\\Collaborators\\LookupPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/LookupPlugin.php', + 'OC\\Collaboration\\Collaborators\\MailByMailPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/MailByMailPlugin.php', 'OC\\Collaboration\\Collaborators\\MailPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/MailPlugin.php', 'OC\\Collaboration\\Collaborators\\RemoteGroupPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php', 'OC\\Collaboration\\Collaborators\\RemotePlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/RemotePlugin.php', 'OC\\Collaboration\\Collaborators\\Search' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/Search.php', 'OC\\Collaboration\\Collaborators\\SearchResult' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/SearchResult.php', + 'OC\\Collaboration\\Collaborators\\UserByMailPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/UserByMailPlugin.php', 'OC\\Collaboration\\Collaborators\\UserPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/UserPlugin.php', 'OC\\Collaboration\\Reference\\File\\FileReferenceEventListener' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php', 'OC\\Collaboration\\Reference\\File\\FileReferenceProvider' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/File/FileReferenceProvider.php', diff --git a/lib/private/Collaboration/Collaborators/MailByMailPlugin.php b/lib/private/Collaboration/Collaborators/MailByMailPlugin.php new file mode 100644 index 00000000000..53f90e33c57 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/MailByMailPlugin.php @@ -0,0 +1,45 @@ +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'; @@ -139,7 +140,7 @@ class MailPlugin implements ISearchPlugin { continue; } - if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { + if ($this->shareType === IShare::TYPE_USER && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { $singleResult = [[ 'label' => $displayName, 'uuid' => $contact['UID'] ?? $emailAddress, @@ -183,22 +184,28 @@ class MailPlugin implements ISearchPlugin { } } if ($addToWide && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { - $userResults['wide'][] = [ - 'label' => $displayName, - 'uuid' => $contact['UID'] ?? $emailAddress, - 'name' => $contact['FN'] ?? $displayName, - 'value' => [ - 'shareType' => IShare::TYPE_USER, - 'shareWith' => $cloud->getUser(), - ], - 'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser() - ]; + if ($this->shareType === IShare::TYPE_USER) { + $userResults['wide'][] = [ + 'label' => $displayName, + 'uuid' => $contact['UID'] ?? $emailAddress, + 'name' => $contact['FN'] ?? $displayName, + 'value' => [ + 'shareType' => IShare::TYPE_USER, + 'shareWith' => $cloud->getUser(), + ], + 'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser() + ]; + } continue; } } continue; } + if ($this->shareType !== IShare::TYPE_EMAIL) { + continue; + } + if ($exactEmailMatch || (isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch)) { if ($exactEmailMatch) { @@ -239,7 +246,8 @@ class MailPlugin implements ISearchPlugin { $userResults['wide'] = array_slice($userResults['wide'], $offset, $limit); } - if (!$searchResult->hasExactIdMatch($emailType) && $this->emailValidator->isValid($search)) { + if ($this->shareType === IShare::TYPE_EMAIL + && !$searchResult->hasExactIdMatch($emailType) && $this->emailValidator->isValid($search)) { $result['exact'][] = [ 'label' => $search, 'uuid' => $search, @@ -250,10 +258,12 @@ class MailPlugin implements ISearchPlugin { ]; } - if (!empty($userResults['wide'])) { + if ($this->shareType === IShare::TYPE_USER && !empty($userResults['wide'])) { $searchResult->addResultSet($userType, $userResults['wide'], []); } - $searchResult->addResultSet($emailType, $result['wide'], $result['exact']); + if ($this->shareType === IShare::TYPE_EMAIL) { + $searchResult->addResultSet($emailType, $result['wide'], $result['exact']); + } return !$reachedEnd; } diff --git a/lib/private/Collaboration/Collaborators/UserByMailPlugin.php b/lib/private/Collaboration/Collaborators/UserByMailPlugin.php new file mode 100644 index 00000000000..44c1a95221c --- /dev/null +++ b/lib/private/Collaboration/Collaborators/UserByMailPlugin.php @@ -0,0 +1,45 @@ +registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserPlugin::class]); + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserByMailPlugin::class]); $instance->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => GroupPlugin::class]); - $instance->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => MailPlugin::class]); + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => MailByMailPlugin::class]); $instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => RemotePlugin::class]); $instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE_GROUP', 'class' => RemoteGroupPlugin::class]); diff --git a/tests/lib/Collaboration/Collaborators/MailPluginTest.php b/tests/lib/Collaboration/Collaborators/MailPluginTest.php index 6f0d52efca5..e6580854784 100644 --- a/tests/lib/Collaboration/Collaborators/MailPluginTest.php +++ b/tests/lib/Collaboration/Collaborators/MailPluginTest.php @@ -72,7 +72,7 @@ class MailPluginTest extends TestCase { $this->searchResult = new SearchResult(); } - public function instantiatePlugin() { + public function instantiatePlugin(int $shareType) { $this->plugin = new MailPlugin( $this->contactsManager, $this->cloudIdManager, @@ -81,6 +81,8 @@ class MailPluginTest extends TestCase { $this->knownUserService, $this->userSession, $this->getEmailValidatorWithStrictEmailCheck(), + [], + $shareType, ); } @@ -94,8 +96,8 @@ class MailPluginTest extends TestCase { * @param bool $expectedMoreResults * @param bool $validEmail */ - #[\PHPUnit\Framework\Attributes\DataProvider('dataSearch')] - public function testSearch($searchTerm, $contacts, $shareeEnumeration, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $validEmail): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchEmail')] + public function testSearchEmail($searchTerm, $contacts, $shareeEnumeration, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $validEmail): void { $this->config->expects($this->any()) ->method('getAppValue') ->willReturnCallback( @@ -107,7 +109,7 @@ class MailPluginTest extends TestCase { } ); - $this->instantiatePlugin(); + $this->instantiatePlugin(IShare::TYPE_EMAIL); $currentUser = $this->createMock(IUser::class); $currentUser->method('getUID') @@ -132,7 +134,7 @@ class MailPluginTest extends TestCase { $this->assertSame($expectedMoreResults, $moreResults); } - public static function dataSearch(): array { + public static function dataSearchEmail(): array { return [ // data set 0 ['test', [], true, ['emails' => [], 'exact' => ['emails' => []]], false, false, false], @@ -398,7 +400,7 @@ class MailPluginTest extends TestCase { false, ], // data set 13 - // Local user found by email + // Local user found by email => no result [ 'test@example.com', [ @@ -411,8 +413,8 @@ class MailPluginTest extends TestCase { ] ], false, - ['users' => [], 'exact' => ['users' => [['uuid' => 'uid1', 'name' => 'User', 'label' => 'User (test@example.com)','value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'shareWithDisplayNameUnique' => 'test@example.com']]]], - true, + ['exact' => []], + false, false, true, ], @@ -436,7 +438,7 @@ class MailPluginTest extends TestCase { true, ], // data set 15 - // Pagination and "more results" for user matches byyyyyyy emails + // Several local users found by email => no result nor pagination [ 'test@example', [ @@ -470,12 +472,9 @@ class MailPluginTest extends TestCase { ], ], true, - ['users' => [ - ['uuid' => 'uid1', 'name' => 'User1', 'label' => 'User1 (test@example.com)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'shareWithDisplayNameUnique' => 'test@example.com'], - ['uuid' => 'uid2', 'name' => 'User2', 'label' => 'User2 (test@example.de)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'shareWithDisplayNameUnique' => 'test@example.de'], - ], 'emails' => [], 'exact' => ['users' => [], 'emails' => []]], + ['emails' => [], 'exact' => ['emails' => []]], + false, false, - true, false, ], // data set 16 @@ -565,6 +564,302 @@ class MailPluginTest extends TestCase { ]; } + /** + * + * @param string $searchTerm + * @param array $contacts + * @param bool $shareeEnumeration + * @param array $expectedResult + * @param bool $expectedExactIdMatch + * @param bool $expectedMoreResults + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchUser')] + public function testSearchUser($searchTerm, $contacts, $shareeEnumeration, $expectedResult, $expectedExactIdMatch, $expectedMoreResults): void { + $this->config->expects($this->any()) + ->method('getAppValue') + ->willReturnCallback( + function ($appName, $key, $default) use ($shareeEnumeration) { + if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') { + return $shareeEnumeration ? 'yes' : 'no'; + } + return $default; + } + ); + + $this->instantiatePlugin(IShare::TYPE_USER); + + $currentUser = $this->createMock(IUser::class); + $currentUser->method('getUID') + ->willReturn('current'); + $this->userSession->method('getUser') + ->willReturn($currentUser); + + $this->contactsManager->expects($this->any()) + ->method('search') + ->willReturnCallback(function ($search, $searchAttributes) use ($searchTerm, $contacts) { + if ($search === $searchTerm) { + return $contacts; + } + return []; + }); + + $moreResults = $this->plugin->search($searchTerm, 2, 0, $this->searchResult); + $result = $this->searchResult->asArray(); + + $this->assertSame($expectedExactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails'))); + $this->assertEquals($expectedResult, $result); + $this->assertSame($expectedMoreResults, $moreResults); + } + + public static function dataSearchUser(): array { + return [ + // data set 0 + ['test', [], true, ['exact' => []], false, false], + // data set 1 + ['test', [], false, ['exact' => []], false, false], + // data set 2 + [ + 'test@remote.com', + [], + true, + ['exact' => []], + false, + false, + ], + // data set 3 + [ + 'test@remote.com', + [], + false, + ['exact' => []], + false, + false, + ], + // data set 4 + [ + 'test', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'EMAIL' => [ + 'username@localhost', + ], + ], + ], + true, + ['exact' => []], + false, + false, + ], + // data set 5 + [ + 'test', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'EMAIL' => [ + ], + ], + [ + 'isLocalSystemBook' => true, + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'EMAIL' => [ + 'username@localhost', + ], + ], + ], + false, + ['exact' => []], + false, + false, + ], + // data set 6 + [ + 'test@remote.com', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'EMAIL' => [ + 'username@localhost', + ], + ], + ], + true, + ['exact' => []], + false, + false, + ], + // data set 7 + [ + 'username@localhost', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'EMAIL' => [ + 'username@localhost', + ], + ], + ], + true, + ['exact' => []], + false, + false, + ], + // data set 8 + // Local user found by email + [ + 'test@example.com', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test@localhost'], + 'isLocalSystemBook' => true, + ] + ], + false, + ['users' => [], 'exact' => ['users' => [['uuid' => 'uid1', 'name' => 'User', 'label' => 'User (test@example.com)','value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'shareWithDisplayNameUnique' => 'test@example.com']]]], + true, + false, + ], + // data set 9 + // Current local user found by email => no result + [ + 'test@example.com', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['current@localhost'], + 'isLocalSystemBook' => true, + ] + ], + true, + ['exact' => []], + false, + false, + ], + // data set 10 + // Pagination and "more results" for user matches by emails + [ + 'test@example', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User1', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test1@localhost'], + 'isLocalSystemBook' => true, + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2', + 'EMAIL' => ['test@example.de'], + 'CLOUD' => ['test2@localhost'], + 'isLocalSystemBook' => true, + ], + [ + 'UID' => 'uid3', + 'FN' => 'User3', + 'EMAIL' => ['test@example.org'], + 'CLOUD' => ['test3@localhost'], + 'isLocalSystemBook' => true, + ], + [ + 'UID' => 'uid4', + 'FN' => 'User4', + 'EMAIL' => ['test@example.net'], + 'CLOUD' => ['test4@localhost'], + 'isLocalSystemBook' => true, + ], + ], + true, + ['users' => [ + ['uuid' => 'uid1', 'name' => 'User1', 'label' => 'User1 (test@example.com)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'shareWithDisplayNameUnique' => 'test@example.com'], + ['uuid' => 'uid2', 'name' => 'User2', 'label' => 'User2 (test@example.de)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'shareWithDisplayNameUnique' => 'test@example.de'], + ], 'exact' => ['users' => []]], + false, + true, + ], + // data set 11 + // Pagination and "more results" for normal emails + [ + 'test@example', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User1', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test1@localhost'], + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2', + 'EMAIL' => ['test@example.de'], + 'CLOUD' => ['test2@localhost'], + ], + [ + 'UID' => 'uid3', + 'FN' => 'User3', + 'EMAIL' => ['test@example.org'], + 'CLOUD' => ['test3@localhost'], + ], + [ + 'UID' => 'uid4', + 'FN' => 'User4', + 'EMAIL' => ['test@example.net'], + 'CLOUD' => ['test4@localhost'], + ], + ], + true, + ['exact' => []], + false, + false, + ], + ]; + } + /** * * @param string $searchTerm @@ -575,8 +870,8 @@ class MailPluginTest extends TestCase { * @param array $userToGroupMapping * @param bool $validEmail */ - #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchGroupsOnly')] - public function testSearchGroupsOnly($searchTerm, $contacts, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $userToGroupMapping, $validEmail): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchEmailGroupsOnly')] + public function testSearchEmailGroupsOnly($searchTerm, $contacts, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $userToGroupMapping, $validEmail): void { $this->config->expects($this->any()) ->method('getAppValue') ->willReturnCallback( @@ -590,7 +885,7 @@ class MailPluginTest extends TestCase { } ); - $this->instantiatePlugin(); + $this->instantiatePlugin(IShare::TYPE_EMAIL); /** @var IUser|\PHPUnit\Framework\MockObject\MockObject */ $currentUser = $this->createMock('\OCP\IUser'); @@ -632,7 +927,7 @@ class MailPluginTest extends TestCase { $this->assertSame($expectedMoreResults, $moreResults); } - public static function dataSearchGroupsOnly(): array { + public static function dataSearchEmailGroupsOnly(): array { return [ // The user `User` can share with the current user [ @@ -643,15 +938,15 @@ class MailPluginTest extends TestCase { 'EMAIL' => ['test@example.com'], 'CLOUD' => ['test@localhost'], 'isLocalSystemBook' => true, - 'UID' => 'User' + 'UID' => 'User', ] ], - ['users' => [['label' => 'User (test@example.com)', 'uuid' => 'User', 'name' => 'User', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'],'shareWithDisplayNameUnique' => 'test@example.com',]], 'emails' => [], 'exact' => ['emails' => [], 'users' => []]], + ['emails' => [], 'exact' => ['emails' => []]], false, false, [ 'currentUser' => ['group1'], - 'User' => ['group1'] + 'User' => ['group1'], ], false, ], @@ -664,7 +959,7 @@ class MailPluginTest extends TestCase { 'EMAIL' => ['test@example.com'], 'CLOUD' => ['test@localhost'], 'isLocalSystemBook' => true, - 'UID' => 'User' + 'UID' => 'User', ] ], ['emails' => [], 'exact' => ['emails' => []]], @@ -672,7 +967,7 @@ class MailPluginTest extends TestCase { false, [ 'currentUser' => ['group1'], - 'User' => ['group2'] + 'User' => ['group2'], ], false, ], @@ -685,7 +980,7 @@ class MailPluginTest extends TestCase { 'EMAIL' => ['test@example.com'], 'CLOUD' => ['test@localhost'], 'isLocalSystemBook' => true, - 'UID' => 'User' + 'UID' => 'User', ] ], ['emails' => [], 'exact' => ['emails' => [['label' => 'test@example.com', 'uuid' => 'test@example.com', 'value' => ['shareType' => IShare::TYPE_EMAIL,'shareWith' => 'test@example.com']]]]], @@ -693,10 +988,141 @@ class MailPluginTest extends TestCase { false, [ 'currentUser' => ['group1'], - 'User' => ['group2'] + 'User' => ['group2'], ], true, ] ]; } + + /** + * + * @param string $searchTerm + * @param array $contacts + * @param array $expectedResult + * @param bool $expectedExactIdMatch + * @param bool $expectedMoreResults + * @param array $userToGroupMapping + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchUserGroupsOnly')] + public function testSearchUserGroupsOnly($searchTerm, $contacts, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $userToGroupMapping): void { + $this->config->expects($this->any()) + ->method('getAppValue') + ->willReturnCallback( + function ($appName, $key, $default) { + if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') { + return 'yes'; + } elseif ($appName === 'core' && $key === 'shareapi_only_share_with_group_members') { + return 'yes'; + } + return $default; + } + ); + + $this->instantiatePlugin(IShare::TYPE_USER); + + /** @var \OCP\IUser | \PHPUnit\Framework\MockObject\MockObject */ + $currentUser = $this->createMock('\OCP\IUser'); + + $currentUser->expects($this->any()) + ->method('getUID') + ->willReturn('currentUser'); + + $this->contactsManager->expects($this->any()) + ->method('search') + ->willReturnCallback(function ($search, $searchAttributes) use ($searchTerm, $contacts) { + if ($search === $searchTerm) { + return $contacts; + } + return []; + }); + + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($currentUser); + + $this->groupManager->expects($this->any()) + ->method('getUserGroupIds') + ->willReturnCallback(function (\OCP\IUser $user) use ($userToGroupMapping) { + return $userToGroupMapping[$user->getUID()]; + }); + + $this->groupManager->expects($this->any()) + ->method('isInGroup') + ->willReturnCallback(function ($userId, $group) use ($userToGroupMapping) { + return in_array($group, $userToGroupMapping[$userId]); + }); + + $moreResults = $this->plugin->search($searchTerm, 2, 0, $this->searchResult); + $result = $this->searchResult->asArray(); + + $this->assertSame($expectedExactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails'))); + $this->assertEquals($expectedResult, $result); + $this->assertSame($expectedMoreResults, $moreResults); + } + + public static function dataSearchUserGroupsOnly(): array { + return [ + // The user `User` can share with the current user + [ + 'test', + [ + [ + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test@localhost'], + 'isLocalSystemBook' => true, + 'UID' => 'User', + ] + ], + ['users' => [['label' => 'User (test@example.com)', 'uuid' => 'User', 'name' => 'User', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'],'shareWithDisplayNameUnique' => 'test@example.com',]], 'exact' => ['users' => []]], + false, + false, + [ + 'currentUser' => ['group1'], + 'User' => ['group1'], + ], + ], + // The user `User` cannot share with the current user + [ + 'test', + [ + [ + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test@localhost'], + 'isLocalSystemBook' => true, + 'UID' => 'User', + ] + ], + ['exact' => []], + false, + false, + [ + 'currentUser' => ['group1'], + 'User' => ['group2'], + ], + ], + // The user `User` cannot share with the current user, but there is an exact match on the e-mail address -> share by e-mail + [ + 'test@example.com', + [ + [ + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test@localhost'], + 'isLocalSystemBook' => true, + 'UID' => 'User', + ] + ], + ['exact' => []], + false, + false, + [ + 'currentUser' => ['group1'], + 'User' => ['group2'], + ], + ] + ]; + } }