fix: Fix user collaborators returned when searching for mail collaborators

The MailPlugin collaborator returned results for both user and mail
collaborators, but it was registered only for mail collaborators. While
it might make sense to move the user results to the UserPlugin instead
that change would be more complex and riskier, so for now the MailPlugin
is now registered for both user and mail collaborators and the results
are limited only to the registered type.

As the plugins are registered only with their class and then resolved
when needed using dependency injection it is not possible (as far as I
know) to provide an explicit parameter in the constructor to
differentiate whether the MailPlugin should return user or mail
collaborators. To overcome this two subclasses are introduced,
MailByMailPlugin and UserByMailPlugin, which just hardcode in their
constructor the collaborator type that their parent MailPlugin must use,
and those subclasses are the ones registered instead of the MailPlugin
(which still contains all the logic).

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
pull/52012/head
Daniel Calviño Sánchez 2025-04-08 03:05:30 +07:00
parent 2c841b2337
commit c40fcba5a4
9 changed files with 589 additions and 66 deletions

@ -107,28 +107,22 @@ Feature: autocomplete
When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no"
Then get email autocomplete for "auto"
| id | source |
| autocomplete | users |
Then get email autocomplete for "example"
| id | source |
| autocomplete | users |
| leon@example.com | emails |
| user@example.com | emails |
Then get email autocomplete for "autocomplete@example.com"
| id | source |
| autocomplete | users |
| autocomplete@example.com | emails |
When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "yes"
Then get email autocomplete for "auto"
| id | source |
| autocomplete | users |
Then get email autocomplete for "example"
| id | source |
| autocomplete | users |
| leon@example.com | emails |
| user@example.com | emails |
Then get email autocomplete for "autocomplete@example.com"
| id | source |
| autocomplete | users |
Scenario: getting autocomplete emails from address book without enumeration
Given As an "admin"
@ -156,7 +150,6 @@ Feature: autocomplete
| user@example.com | emails |
Then get email autocomplete for "autocomplete@example.com"
| id | source |
| autocomplete | users |
Scenario: getting autocomplete with limited enumeration by group
Given As an "admin"

@ -281,6 +281,7 @@ Feature: sharees
And the HTTP status code should be "200"
# UserPlugin provides two identical results (except for the field order, but
# that is hidden by the check).
# MailPlugin does not add a result if there is already one for that user.
And "exact users" sharees returned are
| Sharee2 | 0 | Sharee2 | sharee2@system.com |
| Sharee2 | 0 | Sharee2 | sharee2@system.com |
@ -301,6 +302,7 @@ Feature: sharees
Then the OCS status code should be "100"
And the HTTP status code should be "200"
And "exact users" sharees returned is empty
# MailPlugin does not add a result if there is already one for that user.
And "users" sharees returned are
| Sharee2 | 0 | Sharee2 | sharee2@system.com |
And "exact groups" sharees returned is empty
@ -320,7 +322,8 @@ Feature: sharees
And the HTTP status code should be "200"
# UserPlugin only searches in the system e-mail address, but not in
# secondary addresses.
And "exact users" sharees returned is empty
And "exact users" sharees returned are
| Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com |
And "users" sharees returned is empty
And "exact groups" sharees returned is empty
And "groups" sharees returned is empty
@ -340,7 +343,11 @@ Feature: sharees
And "exact users" sharees returned is empty
# UserPlugin only searches in the system e-mail address, but not in
# secondary addresses.
And "users" sharees returned is empty
# MailPlugin adds a result for every e-mail address of the contact unless
# there is an exact match.
And "users" sharees returned are
| Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com |
| Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com |
And "exact groups" sharees returned is empty
And "groups" sharees returned is empty
And "exact remotes" sharees returned is empty
@ -394,8 +401,7 @@ Feature: sharees
| shareType | 4 |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
And "exact users" sharees returned are
| Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com |
And "exact users" sharees returned is empty
And "users" sharees returned is empty
And "exact groups" sharees returned is empty
And "groups" sharees returned is empty
@ -413,11 +419,7 @@ Feature: sharees
Then the OCS status code should be "100"
And the HTTP status code should be "200"
And "exact users" sharees returned is empty
# MailPlugin adds a result for every e-mail address of the contact unless
# there is an exact match.
And "users" sharees returned are
| Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com |
| Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com |
And "users" sharees returned is empty
And "exact groups" sharees returned is empty
And "groups" sharees returned is empty
And "exact remotes" sharees returned is empty
@ -434,8 +436,7 @@ Feature: sharees
| shareType | 4 |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
And "exact users" sharees returned are
| Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com |
And "exact users" sharees returned is empty
And "users" sharees returned is empty
And "exact groups" sharees returned is empty
And "groups" sharees returned is empty
@ -453,11 +454,7 @@ Feature: sharees
Then the OCS status code should be "100"
And the HTTP status code should be "200"
And "exact users" sharees returned is empty
# MailPlugin adds a result for every e-mail address of the contact unless
# there is an exact match.
And "users" sharees returned are
| Sharee2 (sharee2@system.com) | 0 | Sharee2 | sharee2@system.com |
| Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com |
And "users" sharees returned is empty
And "exact groups" sharees returned is empty
And "groups" sharees returned is empty
And "exact remotes" sharees returned is empty
@ -540,7 +537,8 @@ Feature: sharees
| shareTypes | 0 4 |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
And "exact users" sharees returned is empty
And "exact users" sharees returned are
| Sharee2 (sharee2@secondary.com) | 0 | Sharee2 | sharee2@secondary.com |
And "users" sharees returned is empty
And "exact groups" sharees returned is empty
And "groups" sharees returned is empty

@ -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,
);
}
@ -94,8 +96,8 @@ class MailPluginTest extends TestCase {
* @param bool $expectedMoreResults
* @param bool $validEmail
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataSearch')]
public function testSearch($searchTerm, $contacts, $shareeEnumeration, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $validEmail): void {
#[\PHPUnit\Framework\Attributes\DataProvider('dataSearchEmail')]
public function testSearchEmail($searchTerm, $contacts, $shareeEnumeration, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $validEmail): void {
$this->config->expects($this->any())
->method('getAppValue')
->willReturnCallback(
@ -107,7 +109,7 @@ class MailPluginTest extends TestCase {
}
);
$this->instantiatePlugin();
$this->instantiatePlugin(IShare::TYPE_EMAIL);
$currentUser = $this->createMock(IUser::class);
$currentUser->method('getUID')
@ -132,7 +134,7 @@ class MailPluginTest extends TestCase {
$this->assertSame($expectedMoreResults, $moreResults);
}
public static function dataSearch(): array {
public static function dataSearchEmail(): array {
return [
// data set 0
['test', [], true, ['emails' => [], 'exact' => ['emails' => []]], false, false, false],
@ -398,7 +400,7 @@ class MailPluginTest extends TestCase {
false,
],
// data set 13
// Local user found by email
// Local user found by email => no result
[
'test@example.com',
[
@ -411,8 +413,8 @@ class MailPluginTest extends TestCase {
]
],
false,
['users' => [], 'exact' => ['users' => [['uuid' => 'uid1', 'name' => 'User', 'label' => 'User (test@example.com)','value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'shareWithDisplayNameUnique' => 'test@example.com']]]],
true,
['exact' => []],
false,
false,
true,
],
@ -436,7 +438,7 @@ class MailPluginTest extends TestCase {
true,
],
// data set 15
// Pagination and "more results" for user matches byyyyyyy emails
// Several local users found by email => no result nor pagination
[
'test@example',
[
@ -470,12 +472,9 @@ class MailPluginTest extends TestCase {
],
],
true,
['users' => [
['uuid' => 'uid1', 'name' => 'User1', 'label' => 'User1 (test@example.com)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'shareWithDisplayNameUnique' => 'test@example.com'],
['uuid' => 'uid2', 'name' => 'User2', 'label' => 'User2 (test@example.de)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'shareWithDisplayNameUnique' => 'test@example.de'],
], 'emails' => [], 'exact' => ['users' => [], 'emails' => []]],
['emails' => [], 'exact' => ['emails' => []]],
false,
false,
true,
false,
],
// data set 16
@ -565,6 +564,302 @@ class MailPluginTest extends TestCase {
];
}
/**
*
* @param string $searchTerm
* @param array $contacts
* @param bool $shareeEnumeration
* @param array $expectedResult
* @param bool $expectedExactIdMatch
* @param bool $expectedMoreResults
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataSearchUser')]
public function testSearchUser($searchTerm, $contacts, $shareeEnumeration, $expectedResult, $expectedExactIdMatch, $expectedMoreResults): void {
$this->config->expects($this->any())
->method('getAppValue')
->willReturnCallback(
function ($appName, $key, $default) use ($shareeEnumeration) {
if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') {
return $shareeEnumeration ? 'yes' : 'no';
}
return $default;
}
);
$this->instantiatePlugin(IShare::TYPE_USER);
$currentUser = $this->createMock(IUser::class);
$currentUser->method('getUID')
->willReturn('current');
$this->userSession->method('getUser')
->willReturn($currentUser);
$this->contactsManager->expects($this->any())
->method('search')
->willReturnCallback(function ($search, $searchAttributes) use ($searchTerm, $contacts) {
if ($search === $searchTerm) {
return $contacts;
}
return [];
});
$moreResults = $this->plugin->search($searchTerm, 2, 0, $this->searchResult);
$result = $this->searchResult->asArray();
$this->assertSame($expectedExactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails')));
$this->assertEquals($expectedResult, $result);
$this->assertSame($expectedMoreResults, $moreResults);
}
public static function dataSearchUser(): array {
return [
// data set 0
['test', [], true, ['exact' => []], false, false],
// data set 1
['test', [], false, ['exact' => []], false, false],
// data set 2
[
'test@remote.com',
[],
true,
['exact' => []],
false,
false,
],
// data set 3
[
'test@remote.com',
[],
false,
['exact' => []],
false,
false,
],
// data set 4
[
'test',
[
[
'UID' => 'uid3',
'FN' => 'User3 @ Localhost',
],
[
'UID' => 'uid2',
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'UID' => 'uid1',
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
true,
['exact' => []],
false,
false,
],
// data set 5
[
'test',
[
[
'UID' => 'uid3',
'FN' => 'User3 @ Localhost',
],
[
'UID' => 'uid2',
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'isLocalSystemBook' => true,
'UID' => 'uid1',
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
false,
['exact' => []],
false,
false,
],
// data set 6
[
'test@remote.com',
[
[
'UID' => 'uid3',
'FN' => 'User3 @ Localhost',
],
[
'UID' => 'uid2',
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'UID' => 'uid1',
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
true,
['exact' => []],
false,
false,
],
// data set 7
[
'username@localhost',
[
[
'UID' => 'uid3',
'FN' => 'User3 @ Localhost',
],
[
'UID' => 'uid2',
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'UID' => 'uid1',
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
true,
['exact' => []],
false,
false,
],
// data set 8
// Local user found by email
[
'test@example.com',
[
[
'UID' => 'uid1',
'FN' => 'User',
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test@localhost'],
'isLocalSystemBook' => true,
]
],
false,
['users' => [], 'exact' => ['users' => [['uuid' => 'uid1', 'name' => 'User', 'label' => 'User (test@example.com)','value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'shareWithDisplayNameUnique' => 'test@example.com']]]],
true,
false,
],
// data set 9
// Current local user found by email => no result
[
'test@example.com',
[
[
'UID' => 'uid1',
'FN' => 'User',
'EMAIL' => ['test@example.com'],
'CLOUD' => ['current@localhost'],
'isLocalSystemBook' => true,
]
],
true,
['exact' => []],
false,
false,
],
// data set 10
// Pagination and "more results" for user matches by emails
[
'test@example',
[
[
'UID' => 'uid1',
'FN' => 'User1',
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test1@localhost'],
'isLocalSystemBook' => true,
],
[
'UID' => 'uid2',
'FN' => 'User2',
'EMAIL' => ['test@example.de'],
'CLOUD' => ['test2@localhost'],
'isLocalSystemBook' => true,
],
[
'UID' => 'uid3',
'FN' => 'User3',
'EMAIL' => ['test@example.org'],
'CLOUD' => ['test3@localhost'],
'isLocalSystemBook' => true,
],
[
'UID' => 'uid4',
'FN' => 'User4',
'EMAIL' => ['test@example.net'],
'CLOUD' => ['test4@localhost'],
'isLocalSystemBook' => true,
],
],
true,
['users' => [
['uuid' => 'uid1', 'name' => 'User1', 'label' => 'User1 (test@example.com)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'shareWithDisplayNameUnique' => 'test@example.com'],
['uuid' => 'uid2', 'name' => 'User2', 'label' => 'User2 (test@example.de)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'shareWithDisplayNameUnique' => 'test@example.de'],
], 'exact' => ['users' => []]],
false,
true,
],
// data set 11
// Pagination and "more results" for normal emails
[
'test@example',
[
[
'UID' => 'uid1',
'FN' => 'User1',
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test1@localhost'],
],
[
'UID' => 'uid2',
'FN' => 'User2',
'EMAIL' => ['test@example.de'],
'CLOUD' => ['test2@localhost'],
],
[
'UID' => 'uid3',
'FN' => 'User3',
'EMAIL' => ['test@example.org'],
'CLOUD' => ['test3@localhost'],
],
[
'UID' => 'uid4',
'FN' => 'User4',
'EMAIL' => ['test@example.net'],
'CLOUD' => ['test4@localhost'],
],
],
true,
['exact' => []],
false,
false,
],
];
}
/**
*
* @param string $searchTerm
@ -575,8 +870,8 @@ class MailPluginTest extends TestCase {
* @param array $userToGroupMapping
* @param bool $validEmail
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataSearchGroupsOnly')]
public function testSearchGroupsOnly($searchTerm, $contacts, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $userToGroupMapping, $validEmail): void {
#[\PHPUnit\Framework\Attributes\DataProvider('dataSearchEmailGroupsOnly')]
public function testSearchEmailGroupsOnly($searchTerm, $contacts, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $userToGroupMapping, $validEmail): void {
$this->config->expects($this->any())
->method('getAppValue')
->willReturnCallback(
@ -590,7 +885,7 @@ class MailPluginTest extends TestCase {
}
);
$this->instantiatePlugin();
$this->instantiatePlugin(IShare::TYPE_EMAIL);
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject */
$currentUser = $this->createMock('\OCP\IUser');
@ -632,7 +927,7 @@ class MailPluginTest extends TestCase {
$this->assertSame($expectedMoreResults, $moreResults);
}
public static function dataSearchGroupsOnly(): array {
public static function dataSearchEmailGroupsOnly(): array {
return [
// The user `User` can share with the current user
[
@ -643,15 +938,15 @@ class MailPluginTest extends TestCase {
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test@localhost'],
'isLocalSystemBook' => true,
'UID' => 'User'
'UID' => 'User',
]
],
['users' => [['label' => 'User (test@example.com)', 'uuid' => 'User', 'name' => 'User', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'],'shareWithDisplayNameUnique' => 'test@example.com',]], 'emails' => [], 'exact' => ['emails' => [], 'users' => []]],
['emails' => [], 'exact' => ['emails' => []]],
false,
false,
[
'currentUser' => ['group1'],
'User' => ['group1']
'User' => ['group1'],
],
false,
],
@ -664,7 +959,7 @@ class MailPluginTest extends TestCase {
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test@localhost'],
'isLocalSystemBook' => true,
'UID' => 'User'
'UID' => 'User',
]
],
['emails' => [], 'exact' => ['emails' => []]],
@ -672,7 +967,7 @@ class MailPluginTest extends TestCase {
false,
[
'currentUser' => ['group1'],
'User' => ['group2']
'User' => ['group2'],
],
false,
],
@ -685,7 +980,7 @@ class MailPluginTest extends TestCase {
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test@localhost'],
'isLocalSystemBook' => true,
'UID' => 'User'
'UID' => 'User',
]
],
['emails' => [], 'exact' => ['emails' => [['label' => 'test@example.com', 'uuid' => 'test@example.com', 'value' => ['shareType' => IShare::TYPE_EMAIL,'shareWith' => 'test@example.com']]]]],
@ -693,10 +988,141 @@ class MailPluginTest extends TestCase {
false,
[
'currentUser' => ['group1'],
'User' => ['group2']
'User' => ['group2'],
],
true,
]
];
}
/**
*
* @param string $searchTerm
* @param array $contacts
* @param array $expectedResult
* @param bool $expectedExactIdMatch
* @param bool $expectedMoreResults
* @param array $userToGroupMapping
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataSearchUserGroupsOnly')]
public function testSearchUserGroupsOnly($searchTerm, $contacts, $expectedResult, $expectedExactIdMatch, $expectedMoreResults, $userToGroupMapping): void {
$this->config->expects($this->any())
->method('getAppValue')
->willReturnCallback(
function ($appName, $key, $default) {
if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') {
return 'yes';
} elseif ($appName === 'core' && $key === 'shareapi_only_share_with_group_members') {
return 'yes';
}
return $default;
}
);
$this->instantiatePlugin(IShare::TYPE_USER);
/** @var \OCP\IUser | \PHPUnit\Framework\MockObject\MockObject */
$currentUser = $this->createMock('\OCP\IUser');
$currentUser->expects($this->any())
->method('getUID')
->willReturn('currentUser');
$this->contactsManager->expects($this->any())
->method('search')
->willReturnCallback(function ($search, $searchAttributes) use ($searchTerm, $contacts) {
if ($search === $searchTerm) {
return $contacts;
}
return [];
});
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($currentUser);
$this->groupManager->expects($this->any())
->method('getUserGroupIds')
->willReturnCallback(function (\OCP\IUser $user) use ($userToGroupMapping) {
return $userToGroupMapping[$user->getUID()];
});
$this->groupManager->expects($this->any())
->method('isInGroup')
->willReturnCallback(function ($userId, $group) use ($userToGroupMapping) {
return in_array($group, $userToGroupMapping[$userId]);
});
$moreResults = $this->plugin->search($searchTerm, 2, 0, $this->searchResult);
$result = $this->searchResult->asArray();
$this->assertSame($expectedExactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails')));
$this->assertEquals($expectedResult, $result);
$this->assertSame($expectedMoreResults, $moreResults);
}
public static function dataSearchUserGroupsOnly(): array {
return [
// The user `User` can share with the current user
[
'test',
[
[
'FN' => 'User',
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test@localhost'],
'isLocalSystemBook' => true,
'UID' => 'User',
]
],
['users' => [['label' => 'User (test@example.com)', 'uuid' => 'User', 'name' => 'User', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'],'shareWithDisplayNameUnique' => 'test@example.com',]], 'exact' => ['users' => []]],
false,
false,
[
'currentUser' => ['group1'],
'User' => ['group1'],
],
],
// The user `User` cannot share with the current user
[
'test',
[
[
'FN' => 'User',
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test@localhost'],
'isLocalSystemBook' => true,
'UID' => 'User',
]
],
['exact' => []],
false,
false,
[
'currentUser' => ['group1'],
'User' => ['group2'],
],
],
// The user `User` cannot share with the current user, but there is an exact match on the e-mail address -> share by e-mail
[
'test@example.com',
[
[
'FN' => 'User',
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test@localhost'],
'isLocalSystemBook' => true,
'UID' => 'User',
]
],
['exact' => []],
false,
false,
[
'currentUser' => ['group1'],
'User' => ['group2'],
],
]
];
}
}