Merge pull request #52012 from nextcloud/fix-user-collaborators-returned-when-searching-for-mail-collaborators

Fix user collaborators returned when searching for mail collaborators
pull/56392/head
Daniel Calviño Sánchez 2025-11-12 11:04:07 +07:00 committed by GitHub
commit 0ce4b4b371
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1053 additions and 109 deletions

@ -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"

@ -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());
}

@ -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
// <XXX>
// <element>
// <label>...</label>
// <value>...</value>
// </element>
// </XXX>
// the "$elements[$shareeType]" variable contains an "element" key which
// in turn contains "label" and "value" keys, but when the XML contains
// <XXX>
// <element>
// <label>...</label>
// <value>...</value>
// </element>
// <element>
// <label>...</label>
// <value>...</value>
// </element>
// </XXX>
// 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;
}

@ -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

@ -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

@ -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

@ -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',

@ -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',

@ -0,0 +1,45 @@
<?php
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Collaboration\Collaborators;
use OC\KnownUser\KnownUserService;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUserSession;
use OCP\Mail\IEmailValidator;
use OCP\Share\IShare;
/**
* Dummy subclass to initialize a MailPlugin with a specific share type.
*/
class MailByMailPlugin extends MailPlugin {
public function __construct(
IManager $contactsManager,
ICloudIdManager $cloudIdManager,
IConfig $config,
IGroupManager $groupManager,
KnownUserService $knownUserService,
IUserSession $userSession,
IEmailValidator $emailValidator,
mixed $shareWithGroupOnlyExcludeGroupsList = [],
) {
parent::__construct(
$contactsManager,
$cloudIdManager,
$config,
$groupManager,
$knownUserService,
$userSession,
$emailValidator,
$shareWithGroupOnlyExcludeGroupsList,
IShare::TYPE_EMAIL,
);
}
}

@ -41,7 +41,8 @@ class MailPlugin implements ISearchPlugin {
private KnownUserService $knownUserService,
private IUserSession $userSession,
private IEmailValidator $emailValidator,
private mixed $shareWithGroupOnlyExcludeGroupsList = [],
private mixed $shareWithGroupOnlyExcludeGroupsList,
private int $shareType,
) {
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
@ -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;
}

@ -0,0 +1,45 @@
<?php
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Collaboration\Collaborators;
use OC\KnownUser\KnownUserService;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUserSession;
use OCP\Mail\IEmailValidator;
use OCP\Share\IShare;
/**
* Dummy subclass to initialize a MailPlugin with a specific share type.
*/
class UserByMailPlugin extends MailPlugin {
public function __construct(
IManager $contactsManager,
ICloudIdManager $cloudIdManager,
IConfig $config,
IGroupManager $groupManager,
KnownUserService $knownUserService,
IUserSession $userSession,
IEmailValidator $emailValidator,
mixed $shareWithGroupOnlyExcludeGroupsList = [],
) {
parent::__construct(
$contactsManager,
$cloudIdManager,
$config,
$groupManager,
$knownUserService,
$userSession,
$emailValidator,
$shareWithGroupOnlyExcludeGroupsList,
IShare::TYPE_USER,
);
}
}

@ -25,9 +25,10 @@ use OC\Authentication\Token\IProvider;
use OC\Avatar\AvatarManager;
use OC\Blurhash\Listener\GenerateBlurhashMetadata;
use OC\Collaboration\Collaborators\GroupPlugin;
use OC\Collaboration\Collaborators\MailPlugin;
use OC\Collaboration\Collaborators\MailByMailPlugin;
use OC\Collaboration\Collaborators\RemoteGroupPlugin;
use OC\Collaboration\Collaborators\RemotePlugin;
use OC\Collaboration\Collaborators\UserByMailPlugin;
use OC\Collaboration\Collaborators\UserPlugin;
use OC\Collaboration\Reference\ReferenceManager;
use OC\Command\CronBus;
@ -1112,8 +1113,9 @@ class Server extends ServerContainer implements IServerContainer {
// register default plugins
$instance->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]);

@ -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'],
],
]
];
}
}