diff --git a/build/integration/collaboration_features/autocomplete.feature b/build/integration/collaboration_features/autocomplete.feature index 5044758a70f..763b95bf170 100644 --- a/build/integration/collaboration_features/autocomplete.feature +++ b/build/integration/collaboration_features/autocomplete.feature @@ -41,28 +41,88 @@ 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 | 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 | 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@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" @@ -72,10 +132,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 | @@ -85,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/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php index 935ad2a4a1d..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,13 +822,23 @@ trait Provisioning { return $extractedElementsArray; } - /** - * @Given /^app "([^"]*)" is disabled$/ + * @Given /^app "([^"]*)" enabled state will be restored once the scenario finishes$/ * @param string $app */ - public function appIsDisabled($app) { - $fullUrl = $this->baseUrl . 'v2.php/cloud/apps?filter=disabled'; + 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(); $options = []; if ($this->currentUser === 'admin') { @@ -820,7 +849,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 +867,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 +880,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()); } diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php index 0cc490ff110..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); @@ -732,9 +738,46 @@ 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) { - $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..4ff6d70cc53 100644 --- a/build/integration/sharees_features/sharees.feature +++ b/build/integration/sharees_features/sharees.feature @@ -8,6 +8,14 @@ Feature: sharees And user "Sharee1" exists 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" @@ -18,7 +26,8 @@ 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 | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -34,7 +43,8 @@ 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 | + | Sharee2 | 0 | Sharee2 | sharee2@system.com | And "exact groups" sharees returned is empty And "groups" sharees returned are | ShareeGroup | 1 | ShareeGroup | @@ -68,7 +78,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 +95,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 +141,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 +172,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 +187,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 +217,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,8 +264,285 @@ 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 | + | 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). + # 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 | + 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 + # 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 + 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 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 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. + # 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 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 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 + | 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 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 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 + 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@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 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 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 + 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@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 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 + | 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 + + 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 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 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 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 6c81a0d68e2..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, ); } @@ -89,11 +91,13 @@ class MailPluginTest extends TestCase { * @param string $searchTerm * @param array $contacts * @param bool $shareeEnumeration - * @param array $expected - * @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 { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchEmail')] + public function testSearchEmail($searchTerm, $contacts, $shareeEnumeration, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $validEmail): void { $this->config->expects($this->any()) ->method('getAppValue') ->willReturnCallback( @@ -105,7 +109,7 @@ class MailPluginTest extends TestCase { } ); - $this->instantiatePlugin(); + $this->instantiatePlugin(IShare::TYPE_EMAIL); $currentUser = $this->createMock(IUser::class); $currentUser->method('getUID') @@ -125,12 +129,12 @@ 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 { + public static function dataSearchEmail(): array { return [ // data set 0 ['test', [], true, ['emails' => [], 'exact' => ['emails' => []]], false, false, false], @@ -396,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', [ @@ -409,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, ], @@ -434,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', [ @@ -468,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 @@ -567,13 +568,310 @@ class MailPluginTest extends TestCase { * * @param string $searchTerm * @param array $contacts - * @param array $expected - * @param bool $exactIdMatch - * @param bool $reachedEnd - * @param array groups + * @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 + * @param array $contacts + * @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 { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchEmailGroupsOnly')] + public function testSearchEmailGroupsOnly($searchTerm, $contacts, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $userToGroupMapping, $validEmail): void { $this->config->expects($this->any()) ->method('getAppValue') ->willReturnCallback( @@ -587,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'); @@ -624,12 +922,12 @@ 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 { + public static function dataSearchEmailGroupsOnly(): array { return [ // The user `User` can share with the current user [ @@ -640,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' => 0, 'shareWith' => 'test'],'shareWithDisplayNameUnique' => 'test@example.com',]], 'emails' => [], 'exact' => ['emails' => [], 'users' => []]], + ['emails' => [], 'exact' => ['emails' => []]], false, false, [ 'currentUser' => ['group1'], - 'User' => ['group1'] + 'User' => ['group1'], ], false, ], @@ -661,7 +959,7 @@ class MailPluginTest extends TestCase { 'EMAIL' => ['test@example.com'], 'CLOUD' => ['test@localhost'], 'isLocalSystemBook' => true, - 'UID' => 'User' + 'UID' => 'User', ] ], ['emails' => [], 'exact' => ['emails' => []]], @@ -669,7 +967,7 @@ class MailPluginTest extends TestCase { false, [ 'currentUser' => ['group1'], - 'User' => ['group2'] + 'User' => ['group2'], ], false, ], @@ -682,18 +980,149 @@ 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' => 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, [ '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'], + ], + ] + ]; + } }