Merge pull request #6328 from nextcloud/split-sharees-api-logic

Splits off the logic from sharees endpoint thus making it available from within Nc/via PHP.
This commit is contained in:
blizzz 2017-10-04 15:43:44 +02:00 committed by GitHub
commit 2d62f97f1b
24 changed files with 3286 additions and 2123 deletions

View File

@ -6,6 +6,7 @@
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license AGPL-3.0
*
@ -27,52 +28,23 @@ namespace OCA\Files_Sharing\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCSController;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\Http\Client\IClientService;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\ILogger;
use OCP\Collaboration\Collaborators\ISearch;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IConfig;
use OCP\IUserSession;
use OCP\IURLGenerator;
use OCP\Share;
use OCP\Share\IManager;
class ShareesAPIController extends OCSController {
/** @var IGroupManager */
protected $groupManager;
/** @var IUserManager */
protected $userManager;
/** @var IManager */
protected $contactsManager;
/** @var IConfig */
protected $config;
/** @var IUserSession */
protected $userSession;
/** @var IURLGenerator */
protected $urlGenerator;
/** @var ILogger */
protected $logger;
/** @var \OCP\Share\IManager */
/** @var IManager */
protected $shareManager;
/** @var IClientService */
protected $clientService;
/** @var ICloudIdManager */
protected $cloudIdManager;
/** @var bool */
protected $shareWithGroupOnly = false;
@ -103,326 +75,31 @@ class ShareesAPIController extends OCSController {
];
protected $reachedEndFor = [];
/** @var ISearch */
private $collaboratorSearch;
/**
* @param string $appName
* @param IRequest $request
* @param IGroupManager $groupManager
* @param IUserManager $userManager
* @param IManager $contactsManager
* @param IConfig $config
* @param IUserSession $userSession
* @param IURLGenerator $urlGenerator
* @param ILogger $logger
* @param \OCP\Share\IManager $shareManager
* @param IClientService $clientService
* @param ICloudIdManager $cloudIdManager
* @param IManager $shareManager
* @param ISearch $collaboratorSearch
*/
public function __construct($appName,
IRequest $request,
IGroupManager $groupManager,
IUserManager $userManager,
IManager $contactsManager,
IConfig $config,
IUserSession $userSession,
IURLGenerator $urlGenerator,
ILogger $logger,
\OCP\Share\IManager $shareManager,
IClientService $clientService,
ICloudIdManager $cloudIdManager
public function __construct(
$appName,
IRequest $request,
IConfig $config,
IURLGenerator $urlGenerator,
IManager $shareManager,
ISearch $collaboratorSearch
) {
parent::__construct($appName, $request);
$this->groupManager = $groupManager;
$this->userManager = $userManager;
$this->contactsManager = $contactsManager;
$this->config = $config;
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->shareManager = $shareManager;
$this->clientService = $clientService;
$this->cloudIdManager = $cloudIdManager;
}
/**
* @param string $search
*/
protected function getUsers($search) {
$this->result['users'] = $this->result['exact']['users'] = $users = [];
$userGroups = [];
if ($this->shareWithGroupOnly) {
// Search in all the groups this user is part of
$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
foreach ($userGroups as $userGroup) {
$usersTmp = $this->groupManager->displayNamesInGroup($userGroup, $search, $this->limit, $this->offset);
foreach ($usersTmp as $uid => $userDisplayName) {
$users[$uid] = $userDisplayName;
}
}
} else {
// Search in all users
$usersTmp = $this->userManager->searchDisplayName($search, $this->limit, $this->offset);
foreach ($usersTmp as $user) {
$users[$user->getUID()] = $user->getDisplayName();
}
}
if (!$this->shareeEnumeration || sizeof($users) < $this->limit) {
$this->reachedEndFor[] = 'users';
}
$foundUserById = false;
$lowerSearch = strtolower($search);
foreach ($users as $uid => $userDisplayName) {
if (strtolower($uid) === $lowerSearch || strtolower($userDisplayName) === $lowerSearch) {
if (strtolower($uid) === $lowerSearch) {
$foundUserById = true;
}
$this->result['exact']['users'][] = [
'label' => $userDisplayName,
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $uid,
],
];
} else {
$this->result['users'][] = [
'label' => $userDisplayName,
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $uid,
],
];
}
}
if ($this->offset === 0 && !$foundUserById) {
// On page one we try if the search result has a direct hit on the
// user id and if so, we add that to the exact match list
$user = $this->userManager->get($search);
if ($user instanceof IUser) {
$addUser = true;
if ($this->shareWithGroupOnly) {
// Only add, if we have a common group
$commonGroups = array_intersect($userGroups, $this->groupManager->getUserGroupIds($user));
$addUser = !empty($commonGroups);
}
if ($addUser) {
array_push($this->result['exact']['users'], [
'label' => $user->getDisplayName(),
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $user->getUID(),
],
]);
}
}
}
if (!$this->shareeEnumeration) {
$this->result['users'] = [];
}
}
/**
* @param string $search
*/
protected function getGroups($search) {
$this->result['groups'] = $this->result['exact']['groups'] = [];
$groups = $this->groupManager->search($search, $this->limit, $this->offset);
$groupIds = array_map(function (IGroup $group) { return $group->getGID(); }, $groups);
if (!$this->shareeEnumeration || sizeof($groups) < $this->limit) {
$this->reachedEndFor[] = 'groups';
}
$userGroups = [];
if (!empty($groups) && $this->shareWithGroupOnly) {
// Intersect all the groups that match with the groups this user is a member of
$userGroups = $this->groupManager->getUserGroups($this->userSession->getUser());
$userGroups = array_map(function (IGroup $group) { return $group->getGID(); }, $userGroups);
$groupIds = array_intersect($groupIds, $userGroups);
}
$lowerSearch = strtolower($search);
foreach ($groups as $group) {
// FIXME: use a more efficient approach
$gid = $group->getGID();
if (!in_array($gid, $groupIds)) {
continue;
}
if (strtolower($gid) === $lowerSearch || strtolower($group->getDisplayName()) === $lowerSearch) {
$this->result['exact']['groups'][] = [
'label' => $group->getDisplayName(),
'value' => [
'shareType' => Share::SHARE_TYPE_GROUP,
'shareWith' => $gid,
],
];
} else {
$this->result['groups'][] = [
'label' => $group->getDisplayName(),
'value' => [
'shareType' => Share::SHARE_TYPE_GROUP,
'shareWith' => $gid,
],
];
}
}
if ($this->offset === 0 && empty($this->result['exact']['groups'])) {
// On page one we try if the search result has a direct hit on the
// user id and if so, we add that to the exact match list
$group = $this->groupManager->get($search);
if ($group instanceof IGroup && (!$this->shareWithGroupOnly || in_array($group->getGID(), $userGroups))) {
array_push($this->result['exact']['groups'], [
'label' => $group->getDisplayName(),
'value' => [
'shareType' => Share::SHARE_TYPE_GROUP,
'shareWith' => $group->getGID(),
],
]);
}
}
if (!$this->shareeEnumeration) {
$this->result['groups'] = [];
}
}
/**
* @param string $search
* @suppress PhanUndeclaredClassMethod
*/
protected function getCircles($search) {
$this->result['circles'] = $this->result['exact']['circles'] = [];
$result = \OCA\Circles\Api\Sharees::search($search, $this->limit, $this->offset);
if (array_key_exists('circles', $result['exact'])) {
$this->result['exact']['circles'] = $result['exact']['circles'];
}
if (array_key_exists('circles', $result)) {
$this->result['circles'] = $result['circles'];
}
}
/**
* @param string $search
* @return array
*/
protected function getRemote($search) {
$result = ['results' => [], 'exact' => []];
// Search in contacts
//@todo Pagination missing
$addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN']);
$result['exactIdMatch'] = false;
foreach ($addressBookContacts as $contact) {
if (isset($contact['isLocalSystemBook'])) {
continue;
}
if (isset($contact['CLOUD'])) {
$cloudIds = $contact['CLOUD'];
if (!is_array($cloudIds)) {
$cloudIds = [$cloudIds];
}
$lowerSearch = strtolower($search);
foreach ($cloudIds as $cloudId) {
try {
list(, $serverUrl) = $this->splitUserRemote($cloudId);
} catch (\InvalidArgumentException $e) {
continue;
}
if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) {
if (strtolower($cloudId) === $lowerSearch) {
$result['exactIdMatch'] = true;
}
$result['exact'][] = [
'label' => $contact['FN'] . " ($cloudId)",
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $cloudId,
'server' => $serverUrl,
],
];
} else {
$result['results'][] = [
'label' => $contact['FN'] . " ($cloudId)",
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $cloudId,
'server' => $serverUrl,
],
];
}
}
}
}
if (!$this->shareeEnumeration) {
$result['results'] = [];
}
if (!$result['exactIdMatch'] && $this->cloudIdManager->isValidCloudId($search) && $this->offset === 0) {
$result['exact'][] = [
'label' => $search,
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $search,
],
];
}
$this->reachedEndFor[] = 'remotes';
return $result;
}
/**
* split user and remote from federated cloud id
*
* @param string $address federated share address
* @return array [user, remoteURL]
* @throws \InvalidArgumentException
*/
public function splitUserRemote($address) {
try {
$cloudId = $this->cloudIdManager->resolveCloudId($address);
return [$cloudId->getUser(), $cloudId->getRemote()];
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e);
}
}
/**
* Strips away a potential file names and trailing slashes:
* - http://localhost
* - http://localhost/
* - http://localhost/index.php
* - http://localhost/index.php/s/{shareToken}
*
* all return: http://localhost
*
* @param string $remote
* @return string
*/
protected function fixRemoteURL($remote) {
$remote = str_replace('\\', '/', $remote);
if ($fileNamePosition = strpos($remote, '/index.php')) {
$remote = substr($remote, 0, $fileNamePosition);
}
$remote = rtrim($remote, '/');
return $remote;
$this->collaboratorSearch = $collaboratorSearch;
}
/**
@ -461,7 +138,9 @@ class ShareesAPIController extends OCSController {
Share::SHARE_TYPE_USER,
];
if ($itemType === 'file' || $itemType === 'folder') {
if ($itemType === null) {
throw new OCSBadRequestException('Missing itemType');
} elseif ($itemType === 'file' || $itemType === 'folder') {
if ($this->shareManager->allowGroupSharing()) {
$shareTypes[] = Share::SHARE_TYPE_GROUP;
}
@ -478,6 +157,7 @@ class ShareesAPIController extends OCSController {
$shareTypes[] = Share::SHARE_TYPE_EMAIL;
}
// FIXME: DI
if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
$shareTypes[] = Share::SHARE_TYPE_CIRCLE;
}
@ -495,94 +175,16 @@ class ShareesAPIController extends OCSController {
$this->limit = (int) $perPage;
$this->offset = $perPage * ($page - 1);
return $this->searchSharees($search, $itemType, $shareTypes, $page, $perPage, $lookup);
}
list($result, $hasMoreResults) = $this->collaboratorSearch->search($search, $shareTypes, $lookup, $this->limit, $this->offset);
/**
* Method to get out the static call for better testing
*
* @param string $itemType
* @return bool
*/
protected function isRemoteSharingAllowed($itemType) {
try {
$backend = \OC\Share\Share::getBackend($itemType);
return $backend->isShareTypeAllowed(Share::SHARE_TYPE_REMOTE);
} catch (\Exception $e) {
return false;
// extra treatment for 'exact' subarray, with a single merge expected keys might be lost
if(isset($result['exact'])) {
$result['exact'] = array_merge($this->result['exact'], $result['exact']);
}
}
/**
* Testable search function that does not need globals
*
* @param string $search
* @param string $itemType
* @param array $shareTypes
* @param int $page
* @param int $perPage
* @param bool $lookup
* @return DataResponse
* @throws OCSBadRequestException
*/
protected function searchSharees($search, $itemType, array $shareTypes, $page, $perPage, $lookup) {
// Verify arguments
if ($itemType === null) {
throw new OCSBadRequestException('Missing itemType');
}
// Get users
if (in_array(Share::SHARE_TYPE_USER, $shareTypes)) {
$this->getUsers($search);
}
// Get groups
if (in_array(Share::SHARE_TYPE_GROUP, $shareTypes)) {
$this->getGroups($search);
}
// Get circles
if (in_array(Share::SHARE_TYPE_CIRCLE, $shareTypes)) {
$this->getCircles($search);
}
// Get remote
$remoteResults = ['results' => [], 'exact' => [], 'exactIdMatch' => false];
if (in_array(Share::SHARE_TYPE_REMOTE, $shareTypes)) {
$remoteResults = $this->getRemote($search);
}
// Get emails
$mailResults = ['results' => [], 'exact' => [], 'exactIdMatch' => false];
if (in_array(Share::SHARE_TYPE_EMAIL, $shareTypes)) {
$mailResults = $this->getEmail($search);
}
// Get from lookup server
if ($lookup) {
$this->getLookup($search);
}
// if we have a exact match, either for the federated cloud id or for the
// email address we only return the exact match. It is highly unlikely
// that the exact same email address and federated cloud id exists
if ($mailResults['exactIdMatch'] && !$remoteResults['exactIdMatch']) {
$this->result['emails'] = $mailResults['results'];
$this->result['exact']['emails'] = $mailResults['exact'];
} else if (!$mailResults['exactIdMatch'] && $remoteResults['exactIdMatch']) {
$this->result['remotes'] = $remoteResults['results'];
$this->result['exact']['remotes'] = $remoteResults['exact'];
} else {
$this->result['remotes'] = $remoteResults['results'];
$this->result['exact']['remotes'] = $remoteResults['exact'];
$this->result['emails'] = $mailResults['results'];
$this->result['exact']['emails'] = $mailResults['exact'];
}
$this->result = array_merge($this->result, $result);
$response = new DataResponse($this->result);
if (sizeof($this->reachedEndFor) < 3) {
if ($hasMoreResults) {
$response->addHeader('Link', $this->getPaginationLink($page, [
'search' => $search,
'itemType' => $itemType,
@ -595,166 +197,22 @@ class ShareesAPIController extends OCSController {
}
/**
* @param string $search
* @return array
*/
protected function getEmail($search) {
$result = ['results' => [], 'exact' => [], 'exactIdMatch' => false];
// Search in contacts
//@todo Pagination missing
$addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN']);
$lowerSearch = strtolower($search);
foreach ($addressBookContacts as $contact) {
if (isset($contact['EMAIL'])) {
$emailAddresses = $contact['EMAIL'];
if (!is_array($emailAddresses)) {
$emailAddresses = [$emailAddresses];
}
foreach ($emailAddresses as $emailAddress) {
$exactEmailMatch = strtolower($emailAddress) === $lowerSearch;
if (isset($contact['isLocalSystemBook'])) {
if ($exactEmailMatch) {
try {
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
} catch (\InvalidArgumentException $e) {
continue;
}
if (!$this->hasUserInResult($cloud->getUser())) {
$this->result['exact']['users'][] = [
'label' => $contact['FN'] . " ($emailAddress)",
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $cloud->getUser(),
],
];
}
return ['results' => [], 'exact' => [], 'exactIdMatch' => true];
}
if ($this->shareeEnumeration) {
try {
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
} catch (\InvalidArgumentException $e) {
continue;
}
if (!$this->hasUserInResult($cloud->getUser())) {
$this->result['users'][] = [
'label' => $contact['FN'] . " ($emailAddress)",
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $cloud->getUser(),
],
];
}
}
continue;
}
if ($exactEmailMatch || strtolower($contact['FN']) === $lowerSearch) {
if ($exactEmailMatch) {
$result['exactIdMatch'] = true;
}
$result['exact'][] = [
'label' => $contact['FN'] . " ($emailAddress)",
'value' => [
'shareType' => Share::SHARE_TYPE_EMAIL,
'shareWith' => $emailAddress,
],
];
} else {
$result['results'][] = [
'label' => $contact['FN'] . " ($emailAddress)",
'value' => [
'shareType' => Share::SHARE_TYPE_EMAIL,
'shareWith' => $emailAddress,
],
];
}
}
}
}
if (!$this->shareeEnumeration) {
$result['results'] = [];
}
if (!$result['exactIdMatch'] && filter_var($search, FILTER_VALIDATE_EMAIL)) {
$result['exact'][] = [
'label' => $search,
'value' => [
'shareType' => Share::SHARE_TYPE_EMAIL,
'shareWith' => $search,
],
];
}
$this->reachedEndFor[] = 'emails';
return $result;
}
protected function getLookup($search) {
$isEnabled = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no');
$lookupServerUrl = $this->config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com');
$lookupServerUrl = rtrim($lookupServerUrl, '/');
$result = [];
if($isEnabled === 'yes') {
try {
$client = $this->clientService->newClient();
$response = $client->get(
$lookupServerUrl . '/users?search=' . urlencode($search),
[
'timeout' => 10,
'connect_timeout' => 3,
]
);
$body = json_decode($response->getBody(), true);
$result = [];
foreach ($body as $lookup) {
$result[] = [
'label' => $lookup['federationId'],
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $lookup['federationId'],
],
'extra' => $lookup,
];
}
} catch (\Exception $e) {}
}
$this->result['lookup'] = $result;
}
/**
* Check if a given user is already part of the result
* Method to get out the static call for better testing
*
* @param string $userId
* @param string $itemType
* @return bool
*/
protected function hasUserInResult($userId) {
foreach ($this->result['exact']['users'] as $result) {
if ($result['value']['shareWith'] === $userId) {
return true;
}
protected function isRemoteSharingAllowed($itemType) {
try {
// FIXME: static foo makes unit testing unnecessarily difficult
$backend = \OC\Share\Share::getBackend($itemType);
return $backend->isShareTypeAllowed(Share::SHARE_TYPE_REMOTE);
} catch (\Exception $e) {
return false;
}
foreach ($this->result['users'] as $result) {
if ($result['value']['shareWith'] === $userId) {
return true;
}
}
return false;
}
/**
* Generates a bunch of pagination links for the current page
*

View File

@ -68,6 +68,10 @@ return array(
'OCP\\BackgroundJob\\IJobList' => $baseDir . '/lib/public/BackgroundJob/IJobList.php',
'OCP\\Capabilities\\ICapability' => $baseDir . '/lib/public/Capabilities/ICapability.php',
'OCP\\Capabilities\\IPublicCapability' => $baseDir . '/lib/public/Capabilities/IPublicCapability.php',
'OCP\\Collaboration\\Collaborators\\ISearch' => $baseDir . '/lib/public/Collaboration/Collaborators/ISearch.php',
'OCP\\Collaboration\\Collaborators\\ISearchPlugin' => $baseDir . '/lib/public/Collaboration/Collaborators/ISearchPlugin.php',
'OCP\\Collaboration\\Collaborators\\ISearchResult' => $baseDir . '/lib/public/Collaboration/Collaborators/ISearchResult.php',
'OCP\\Collaboration\\Collaborators\\SearchResultType' => $baseDir . '/lib/public/Collaboration/Collaborators/SearchResultType.php',
'OCP\\Command\\IBus' => $baseDir . '/lib/public/Command/IBus.php',
'OCP\\Command\\ICommand' => $baseDir . '/lib/public/Command/ICommand.php',
'OCP\\Comments\\CommentsEntityEvent' => $baseDir . '/lib/public/Comments/CommentsEntityEvent.php',
@ -384,6 +388,13 @@ return array(
'OC\\Cache\\CappedMemoryCache' => $baseDir . '/lib/private/Cache/CappedMemoryCache.php',
'OC\\Cache\\File' => $baseDir . '/lib/private/Cache/File.php',
'OC\\CapabilitiesManager' => $baseDir . '/lib/private/CapabilitiesManager.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\\MailPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/MailPlugin.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\\UserPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/UserPlugin.php',
'OC\\Command\\AsyncBus' => $baseDir . '/lib/private/Command/AsyncBus.php',
'OC\\Command\\CallableJob' => $baseDir . '/lib/private/Command/CallableJob.php',
'OC\\Command\\ClosureJob' => $baseDir . '/lib/private/Command/ClosureJob.php',

View File

@ -98,6 +98,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\BackgroundJob\\IJobList' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob/IJobList.php',
'OCP\\Capabilities\\ICapability' => __DIR__ . '/../../..' . '/lib/public/Capabilities/ICapability.php',
'OCP\\Capabilities\\IPublicCapability' => __DIR__ . '/../../..' . '/lib/public/Capabilities/IPublicCapability.php',
'OCP\\Collaboration\\Collaborators\\ISearch' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Collaborators/ISearch.php',
'OCP\\Collaboration\\Collaborators\\ISearchPlugin' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Collaborators/ISearchPlugin.php',
'OCP\\Collaboration\\Collaborators\\ISearchResult' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Collaborators/ISearchResult.php',
'OCP\\Collaboration\\Collaborators\\SearchResultType' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Collaborators/SearchResultType.php',
'OCP\\Command\\IBus' => __DIR__ . '/../../..' . '/lib/public/Command/IBus.php',
'OCP\\Command\\ICommand' => __DIR__ . '/../../..' . '/lib/public/Command/ICommand.php',
'OCP\\Comments\\CommentsEntityEvent' => __DIR__ . '/../../..' . '/lib/public/Comments/CommentsEntityEvent.php',
@ -414,6 +418,13 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/private/Cache/CappedMemoryCache.php',
'OC\\Cache\\File' => __DIR__ . '/../../..' . '/lib/private/Cache/File.php',
'OC\\CapabilitiesManager' => __DIR__ . '/../../..' . '/lib/private/CapabilitiesManager.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\\MailPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/MailPlugin.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\\UserPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/UserPlugin.php',
'OC\\Command\\AsyncBus' => __DIR__ . '/../../..' . '/lib/private/Command/AsyncBus.php',
'OC\\Command\\CallableJob' => __DIR__ . '/../../..' . '/lib/private/Command/CallableJob.php',
'OC\\Command\\ClosureJob' => __DIR__ . '/../../..' . '/lib/private/Command/ClosureJob.php',

View File

@ -165,6 +165,12 @@ class InfoParser {
if (isset($array['activity']['providers']['provider']) && is_array($array['activity']['providers']['provider'])) {
$array['activity']['providers'] = $array['activity']['providers']['provider'];
}
if (isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin'])
&& is_array($array['collaboration']['collaborators']['searchPlugins']['searchPlugin'])
&& !isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']['class'])
) {
$array['collaboration']['collaborators']['searchPlugins'] = $array['collaboration']['collaborators']['searchPlugins']['searchPlugin'];
}
if(!is_null($this->cache)) {
$this->cache->set($fileCacheKey, json_encode($array));

View File

@ -0,0 +1,124 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Collaboration\Collaborators;
use OCP\Collaboration\Collaborators\ISearchPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\IConfig;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUserSession;
use OCP\Share;
class GroupPlugin implements ISearchPlugin {
protected $shareeEnumeration;
protected $shareWithGroupOnly;
/** @var IGroupManager */
private $groupManager;
/** @var IConfig */
private $config;
/** @var IUserSession */
private $userSession;
public function __construct(IConfig $config, IGroupManager $groupManager, IUserSession $userSession) {
$this->groupManager = $groupManager;
$this->config = $config;
$this->userSession = $userSession;
$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';
}
public function search($search, $limit, $offset, ISearchResult $searchResult) {
$hasMoreResults = false;
$result = ['wide' => [], 'exact' => []];
$groups = $this->groupManager->search($search, $limit, $offset);
$groupIds = array_map(function (IGroup $group) { return $group->getGID(); }, $groups);
if (!$this->shareeEnumeration || sizeof($groups) < $limit) {
$hasMoreResults = true;
}
$userGroups = [];
if (!empty($groups) && $this->shareWithGroupOnly) {
// Intersect all the groups that match with the groups this user is a member of
$userGroups = $this->groupManager->getUserGroups($this->userSession->getUser());
$userGroups = array_map(function (IGroup $group) { return $group->getGID(); }, $userGroups);
$groupIds = array_intersect($groupIds, $userGroups);
}
$lowerSearch = strtolower($search);
foreach ($groups as $group) {
// FIXME: use a more efficient approach
$gid = $group->getGID();
if (!in_array($gid, $groupIds)) {
continue;
}
if (strtolower($gid) === $lowerSearch || strtolower($group->getDisplayName()) === $lowerSearch) {
$result['exact'][] = [
'label' => $group->getDisplayName(),
'value' => [
'shareType' => Share::SHARE_TYPE_GROUP,
'shareWith' => $gid,
],
];
} else {
$result['wide'][] = [
'label' => $group->getDisplayName(),
'value' => [
'shareType' => Share::SHARE_TYPE_GROUP,
'shareWith' => $gid,
],
];
}
}
if ($offset === 0 && empty($result['exact'])) {
// On page one we try if the search result has a direct hit on the
// user id and if so, we add that to the exact match list
$group = $this->groupManager->get($search);
if ($group instanceof IGroup && (!$this->shareWithGroupOnly || in_array($group->getGID(), $userGroups))) {
array_push($result['exact'], [
'label' => $group->getDisplayName(),
'value' => [
'shareType' => Share::SHARE_TYPE_GROUP,
'shareWith' => $group->getGID(),
],
]);
}
}
if (!$this->shareeEnumeration) {
$result['wide'] = [];
}
$type = new SearchResultType('groups');
$searchResult->addResultSet($type, $result['wide'], $result['exact']);
return $hasMoreResults;
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Collaboration\Collaborators;
use OCP\Collaboration\Collaborators\ISearchPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\Share;
class LookupPlugin implements ISearchPlugin {
/** @var IConfig */
private $config;
/** @var IClientService */
private $clientService;
public function __construct(IConfig $config, IClientService $clientService) {
$this->config = $config;
$this->clientService = $clientService;
}
public function search($search, $limit, $offset, ISearchResult $searchResult) {
if ($this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no') !== 'yes') {
return false;
}
$lookupServerUrl = $this->config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com');
$lookupServerUrl = rtrim($lookupServerUrl, '/');
$result = [];
try {
$client = $this->clientService->newClient();
$response = $client->get(
$lookupServerUrl . '/users?search=' . urlencode($search),
[
'timeout' => 10,
'connect_timeout' => 3,
]
);
$body = json_decode($response->getBody(), true);
foreach ($body as $lookup) {
$result[] = [
'label' => $lookup['federationId'],
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $lookup['federationId'],
],
'extra' => $lookup,
];
}
} catch (\Exception $e) {
}
$type = new SearchResultType('lookup');
$searchResult->addResultSet($type, $result, []);
return false;
}
}

View File

@ -0,0 +1,164 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Collaboration\Collaborators;
use OCP\Collaboration\Collaborators\ISearchPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IConfig;
use OCP\Share;
class MailPlugin implements ISearchPlugin {
protected $shareeEnumeration;
/** @var IManager */
private $contactsManager;
/** @var ICloudIdManager */
private $cloudIdManager;
/** @var IConfig */
private $config;
public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) {
$this->contactsManager = $contactsManager;
$this->cloudIdManager = $cloudIdManager;
$this->config = $config;
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
}
/**
* @param $search
* @param $limit
* @param $offset
* @param ISearchResult $searchResult
* @return bool
* @since 13.0.0
*/
public function search($search, $limit, $offset, ISearchResult $searchResult) {
$result = ['wide' => [], 'exact' => []];
$userType = new SearchResultType('users');
$emailType = new SearchResultType('emails');
// Search in contacts
//@todo Pagination missing
$addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN']);
$lowerSearch = strtolower($search);
foreach ($addressBookContacts as $contact) {
if (isset($contact['EMAIL'])) {
$emailAddresses = $contact['EMAIL'];
if (!is_array($emailAddresses)) {
$emailAddresses = [$emailAddresses];
}
foreach ($emailAddresses as $emailAddress) {
$exactEmailMatch = strtolower($emailAddress) === $lowerSearch;
if (isset($contact['isLocalSystemBook'])) {
if ($exactEmailMatch) {
try {
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
} catch (\InvalidArgumentException $e) {
continue;
}
if (!$searchResult->hasResult($userType, $cloud->getUser())) {
$singleResult = [[
'label' => $contact['FN'] . " ($emailAddress)",
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $cloud->getUser(),
],
]];
$searchResult->addResultSet($userType, [], $singleResult);
$searchResult->markExactIdMatch($emailType);
}
return false;
}
if ($this->shareeEnumeration) {
try {
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
} catch (\InvalidArgumentException $e) {
continue;
}
if (!$searchResult->hasResult($userType, $cloud->getUser())) {
$singleResult = [[
'label' => $contact['FN'] . " ($emailAddress)",
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $cloud->getUser(),
]],
];
$searchResult->addResultSet($userType, $singleResult, []);
}
}
continue;
}
if ($exactEmailMatch || strtolower($contact['FN']) === $lowerSearch) {
if ($exactEmailMatch) {
$searchResult->markExactIdMatch($emailType);
}
$result['exact'][] = [
'label' => $contact['FN'] . " ($emailAddress)",
'value' => [
'shareType' => Share::SHARE_TYPE_EMAIL,
'shareWith' => $emailAddress,
],
];
} else {
$result['wide'][] = [
'label' => $contact['FN'] . " ($emailAddress)",
'value' => [
'shareType' => Share::SHARE_TYPE_EMAIL,
'shareWith' => $emailAddress,
],
];
}
}
}
}
if (!$this->shareeEnumeration) {
$result['wide'] = [];
}
if (!$searchResult->hasExactIdMatch($emailType) && filter_var($search, FILTER_VALIDATE_EMAIL)) {
$result['exact'][] = [
'label' => $search,
'value' => [
'shareType' => Share::SHARE_TYPE_EMAIL,
'shareWith' => $search,
],
];
}
$searchResult->addResultSet($emailType, $result['wide'], $result['exact']);
return true;
}
}

View File

@ -0,0 +1,137 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Collaboration\Collaborators;
use OCP\Collaboration\Collaborators\ISearchPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IConfig;
use OCP\Share;
class RemotePlugin implements ISearchPlugin {
protected $shareeEnumeration;
/** @var IManager */
private $contactsManager;
/** @var ICloudIdManager */
private $cloudIdManager;
/** @var IConfig */
private $config;
public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) {
$this->contactsManager = $contactsManager;
$this->cloudIdManager = $cloudIdManager;
$this->config = $config;
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
}
public function search($search, $limit, $offset, ISearchResult $searchResult) {
$result = ['wide' => [], 'exact' => []];
$resultType = new SearchResultType('remotes');
// Search in contacts
//@todo Pagination missing
$addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN']);
foreach ($addressBookContacts as $contact) {
if (isset($contact['isLocalSystemBook'])) {
continue;
}
if (isset($contact['CLOUD'])) {
$cloudIds = $contact['CLOUD'];
if (!is_array($cloudIds)) {
$cloudIds = [$cloudIds];
}
$lowerSearch = strtolower($search);
foreach ($cloudIds as $cloudId) {
try {
list(, $serverUrl) = $this->splitUserRemote($cloudId);
} catch (\InvalidArgumentException $e) {
continue;
}
if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) {
if (strtolower($cloudId) === $lowerSearch) {
$searchResult->markExactIdMatch($resultType);
}
$result['exact'][] = [
'label' => $contact['FN'] . " ($cloudId)",
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $cloudId,
'server' => $serverUrl,
],
];
} else {
$result['wide'][] = [
'label' => $contact['FN'] . " ($cloudId)",
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $cloudId,
'server' => $serverUrl,
],
];
}
}
}
}
if (!$this->shareeEnumeration) {
$result['wide'] = [];
}
if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) {
$result['exact'][] = [
'label' => $search,
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $search,
],
];
}
$searchResult->addResultSet($resultType, $result['wide'], $result['exact']);
return true;
}
/**
* split user and remote from federated cloud id
*
* @param string $address federated share address
* @return array [user, remoteURL]
* @throws \InvalidArgumentException
*/
public function splitUserRemote($address) {
try {
$cloudId = $this->cloudIdManager->resolveCloudId($address);
return [$cloudId->getUser(), $cloudId->getRemote()];
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e);
}
}
}

View File

@ -0,0 +1,89 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Collaboration\Collaborators;
use OCP\Collaboration\Collaborators\ISearch;
use OCP\Collaboration\Collaborators\ISearchPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\IContainer;
use OCP\Share;
class Search implements ISearch {
/** @var IContainer */
private $c;
protected $pluginList = [];
public function __construct(IContainer $c) {
$this->c = $c;
}
public function search($search, array $shareTypes, $lookup, $limit, $offset) {
$hasMoreResults = false;
/** @var ISearchResult $searchResult */
$searchResult = $this->c->resolve(SearchResult::class);
foreach ($shareTypes as $type) {
if(!isset($this->pluginList[$type])) {
continue;
}
foreach ($this->pluginList[$type] as $plugin) {
/** @var ISearchPlugin $searchPlugin */
$searchPlugin = $this->c->resolve($plugin);
$hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult);
}
}
// Get from lookup server, not a separate share type
if ($lookup) {
$searchPlugin = $this->c->resolve(LookupPlugin::class);
$hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult);
}
// sanitizing, could go into the plugins as well
// if we have a exact match, either for the federated cloud id or for the
// email address we only return the exact match. It is highly unlikely
// that the exact same email address and federated cloud id exists
$emailType = new SearchResultType('emails');
$remoteType = new SearchResultType('remotes');
if($searchResult->hasExactIdMatch($emailType) && !$searchResult->hasExactIdMatch($remoteType)) {
$searchResult->unsetResult($remoteType);
} elseif (!$searchResult->hasExactIdMatch($emailType) && $searchResult->hasExactIdMatch($remoteType)) {
$searchResult->unsetResult($emailType);
}
return [$searchResult->asArray(), (bool)$hasMoreResults];
}
public function registerPlugin(array $pluginInfo) {
$shareType = constant(Share::class . '::' . $pluginInfo['shareType']);
if($shareType === null) {
throw new \InvalidArgumentException('Provided ShareType is invalid');
}
$this->pluginList[$shareType][] = $pluginInfo['class'];
}
}

View File

@ -0,0 +1,86 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Collaboration\Collaborators;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
class SearchResult implements ISearchResult {
protected $result = [
'exact' => [],
];
protected $exactIdMatches = [];
public function addResultSet(SearchResultType $type, array $matches, array $exactMatches = null) {
$type = $type->getLabel();
if(!isset($this->result[$type])) {
$this->result[$type] = [];
$this->result['exact'][$type] = [];
}
$this->result[$type] = array_merge($this->result[$type], $matches);
if(is_array($exactMatches)) {
$this->result['exact'][$type] = array_merge($this->result['exact'][$type], $exactMatches);
}
}
public function markExactIdMatch(SearchResultType $type) {
$this->exactIdMatches[$type->getLabel()] = 1;
}
public function hasExactIdMatch(SearchResultType$type) {
return isset($this->exactIdMatches[$type->getLabel()]);
}
public function hasResult(SearchResultType $type, $collaboratorId) {
$type = $type->getLabel();
if(!isset($this->result[$type])) {
return false;
}
$resultArrays = [$this->result['exact'][$type], $this->result[$type]];
foreach($resultArrays as $resultArray) {
if ($resultArray['value']['shareWith'] === $collaboratorId) {
return true;
}
}
return false;
}
public function asArray() {
return $this->result;
}
public function unsetResult(SearchResultType $type) {
$type = $type->getLabel();
$this->result[$type] = [];
if(isset($this->result['exact'][$type])) {
$this->result['exact'][$type] = [];
}
}
}

View File

@ -0,0 +1,149 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Collaboration\Collaborators;
use OCP\Collaboration\Collaborators\ISearchPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Share;
class UserPlugin implements ISearchPlugin {
/* @var bool */
protected $shareWithGroupOnly;
protected $shareeEnumeration;
/** @var IConfig */
private $config;
/** @var IGroupManager */
private $groupManager;
/** @var IUserSession */
private $userSession;
/** @var IUserManager */
private $userManager;
public function __construct(IConfig $config, IUserManager $userManager, IGroupManager $groupManager, IUserSession $userSession) {
$this->config = $config;
$this->groupManager = $groupManager;
$this->userSession = $userSession;
$this->userManager = $userManager;
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
}
public function search($search, $limit, $offset, ISearchResult $searchResult) {
$result = ['wide' => [], 'exact' => []];
$users = [];
$hasMoreResults = false;
$userGroups = [];
if ($this->shareWithGroupOnly) {
// Search in all the groups this user is part of
$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
foreach ($userGroups as $userGroup) {
$usersTmp = $this->groupManager->displayNamesInGroup($userGroup, $search, $limit, $offset);
foreach ($usersTmp as $uid => $userDisplayName) {
$users[$uid] = $userDisplayName;
}
}
} else {
// Search in all users
$usersTmp = $this->userManager->searchDisplayName($search, $limit, $offset);
foreach ($usersTmp as $user) {
$users[$user->getUID()] = $user->getDisplayName();
}
}
if (!$this->shareeEnumeration || sizeof($users) < $limit) {
$hasMoreResults = true;
}
$foundUserById = false;
$lowerSearch = strtolower($search);
foreach ($users as $uid => $userDisplayName) {
if (strtolower($uid) === $lowerSearch || strtolower($userDisplayName) === $lowerSearch) {
if (strtolower($uid) === $lowerSearch) {
$foundUserById = true;
}
$result['exact'][] = [
'label' => $userDisplayName,
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $uid,
],
];
} else {
$result['wide'][] = [
'label' => $userDisplayName,
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $uid,
],
];
}
}
if ($offset === 0 && !$foundUserById) {
// On page one we try if the search result has a direct hit on the
// user id and if so, we add that to the exact match list
$user = $this->userManager->get($search);
if ($user instanceof IUser) {
$addUser = true;
if ($this->shareWithGroupOnly) {
// Only add, if we have a common group
$commonGroups = array_intersect($userGroups, $this->groupManager->getUserGroupIds($user));
$addUser = !empty($commonGroups);
}
if ($addUser) {
array_push($result['exact'], [
'label' => $user->getDisplayName(),
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $user->getUID(),
],
]);
}
}
}
if (!$this->shareeEnumeration) {
$result['wide'] = [];
}
$type = new SearchResultType('users');
$searchResult->addResultSet($type, $result['wide'], $result['exact']);
return $hasMoreResults;
}
}

View File

@ -52,6 +52,10 @@ use OC\AppFramework\Http\Request;
use OC\AppFramework\Utility\SimpleContainer;
use OC\AppFramework\Utility\TimeFactory;
use OC\Authentication\LoginCredentials\Store;
use OC\Collaboration\Collaborators\GroupPlugin;
use OC\Collaboration\Collaborators\MailPlugin;
use OC\Collaboration\Collaborators\RemotePlugin;
use OC\Collaboration\Collaborators\UserPlugin;
use OC\Command\CronBus;
use OC\Contacts\ContactsMenu\ActionFactory;
use OC\Diagnostics\EventLogger;
@ -115,6 +119,7 @@ use OCP\Contacts\ContactsMenu\IActionFactory;
use OCP\Lock\ILockingProvider;
use OCP\RichObjectStrings\IValidator;
use OCP\Security\IContentSecurityPolicyManager;
use OCP\Share;
use OCP\Share\IShareHelper;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@ -993,6 +998,19 @@ class Server extends ServerContainer implements IServerContainer {
});
$this->registerAlias('ShareManager', \OCP\Share\IManager::class);
$this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function(Server $c) {
$instance = new Collaboration\Collaborators\Search($c);
// register default plugins
$instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserPlugin::class]);
$instance->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => GroupPlugin::class]);
$instance->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => MailPlugin::class]);
$instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => RemotePlugin::class]);
return $instance;
});
$this->registerAlias('CollaboratorSearch', \OCP\Collaboration\Collaborators\ISearch::class);
$this->registerService('SettingsManager', function (Server $c) {
$manager = new \OC\Settings\Manager(
$c->getLogger(),
@ -1776,6 +1794,13 @@ class Server extends ServerContainer implements IServerContainer {
return $this->query('ShareManager');
}
/**
* @return \OCP\Collaboration\Collaborators\ISearch
*/
public function getCollaboratorSearch() {
return $this->query('CollaboratorSearch');
}
/**
* Returns the LDAP Provider
*

View File

@ -174,6 +174,20 @@ class OC_App {
\OC::$server->getActivityManager()->registerProvider($provider);
}
}
if (!empty($info['collaboration']['plugins'])) {
// deal with one or many plugin entries
$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
foreach ($plugins as $plugin) {
if($plugin['@attributes']['type'] === 'collaborator-search') {
$pluginInfo = [
'shareType' => $plugin['@attributes']['share-type'],
'class' => $plugin['@value'],
];
\OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
}
}
}
}
/**

View File

@ -0,0 +1,50 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCP\Collaboration\Collaborators;
/**
* Interface ISearch
*
* @package OCP\Collaboration\Collaborators
* @since 13.0.0
*/
interface ISearch {
/**
* @param string $search
* @param array $shareTypes
* @param bool $lookup
* @param int $limit
* @param int $offset
* @return array with two elements, 1st ISearchResult as array, 2nd a bool indicating whether more result are available
* @since 13.0.0
*/
public function search($search, array $shareTypes, $lookup, $limit, $offset);
/**
* @param array $pluginInfo with keys 'shareType' containing the name of a corresponding constant in \OCP\Share and
* 'class' with the class name of the plugin
* @since 13.0.0
*/
public function registerPlugin(array $pluginInfo);
}

View File

@ -0,0 +1,42 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCP\Collaboration\Collaborators;
/**
* Interface ISearchPlugin
*
* @package OCP\Collaboration\Collaborators
* @since 13.0.0
*/
interface ISearchPlugin {
/**
* @param string $search
* @param int $limit
* @param int $offset
* @param ISearchResult $searchResult
* @return bool whether the plugin has more results
* @since 13.0.0
*/
public function search($search, $limit, $offset, ISearchResult $searchResult);
}

View File

@ -0,0 +1,73 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCP\Collaboration\Collaborators;
/**
* Interface ISearchResult
*
* @package OCP\Collaboration\Collaborators
* @since 13.0.0
*/
interface ISearchResult {
/**
* @param SearchResultType $type
* @param array $matches
* @param array|null $exactMatches
* @since 13.0.0
*/
public function addResultSet(SearchResultType $type, array $matches, array $exactMatches = null);
/**
* @param SearchResultType $type
* @param string $collaboratorId
* @return bool
* @since 13.0.0
*/
public function hasResult(SearchResultType $type, $collaboratorId);
/**
* @param SearchResultType $type
* @since 13.0.0
*/
public function unsetResult(SearchResultType $type);
/**
* @param SearchResultType $type
* @since 13.0.0
*/
public function markExactIdMatch(SearchResultType $type);
/**
* @param SearchResultType $type
* @return bool
* @since 13.0.0
*/
public function hasExactIdMatch(SearchResultType $type);
/**
* @return array
* @since 13.0.0
*/
public function asArray();
}

View File

@ -0,0 +1,73 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCP\Collaboration\Collaborators;
/**
* Class SearchResultType
*
* @package OCP\Collaboration\Collaborators
* @since 13.0.0
*/
class SearchResultType {
/** @var string */
protected $label;
/**
* SearchResultType constructor.
*
* @param string $label
* @since 13.0.0
*/
public function __construct($label) {
$this->label = $this->getValidatedType($label);
}
/**
* @return string
* @since 13.0.0
*/
public function getLabel() {
return $this->label;
}
/**
* @param $type
* @return string
* @throws \InvalidArgumentException
* @since 13.0.0
*/
protected function getValidatedType($type) {
$type = trim(strval($type));
if($type === '') {
throw new \InvalidArgumentException('Type must not be empty');
}
if($type === 'exact') {
throw new \InvalidArgumentException('Provided type is a reserved word');
}
return $type;
}
}

View File

@ -0,0 +1,491 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Test\Collaboration\Collaborators;
use OC\Collaboration\Collaborators\GroupPlugin;
use OC\Collaboration\Collaborators\SearchResult;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\IConfig;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Share;
use Test\TestCase;
class GroupPluginTest extends TestCase {
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
protected $config;
/** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */
protected $groupManager;
/** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
protected $session;
/** @var ISearchResult */
protected $searchResult;
/** @var GroupPlugin */
protected $plugin;
/** @var int */
protected $limit = 2;
/** @var int */
protected $offset = 0;
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject */
protected $user;
public function setUp() {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->session = $this->createMock(IUserSession::class);
$this->searchResult = new SearchResult();
$this->user = $this->getUserMock('admin', 'Administrator');
}
public function instantiatePlugin() {
// cannot be done within setUp, because dependent mocks needs to be set
// up with configuration etc. first
$this->plugin = new GroupPlugin(
$this->config,
$this->groupManager,
$this->session
);
}
public function getUserMock($uid, $displayName) {
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->willReturn($uid);
$user->expects($this->any())
->method('getDisplayName')
->willReturn($displayName);
return $user;
}
/**
* @param string $gid
* @param null $displayName
* @return IGroup|\PHPUnit_Framework_MockObject_MockObject
*/
protected function getGroupMock($gid, $displayName = null) {
$group = $this->createMock(IGroup::class);
$group->expects($this->any())
->method('getGID')
->willReturn($gid);
if (is_null($displayName)) {
// note: this is how the Group class behaves
$displayName = $gid;
}
$group->expects($this->any())
->method('getDisplayName')
->willReturn($displayName);
return $group;
}
public function dataGetGroups() {
return [
['test', false, true, [], [], [], [], true, false],
['test', false, false, [], [], [], [], true, false],
// group without display name
[
'test', false, true,
[$this->getGroupMock('test1')],
[],
[],
[['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]],
true,
false,
],
// group with display name, search by id
[
'test', false, true,
[$this->getGroupMock('test1', 'Test One')],
[],
[],
[['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]],
true,
false,
],
// group with display name, search by display name
[
'one', false, true,
[$this->getGroupMock('test1', 'Test One')],
[],
[],
[['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]],
true,
false,
],
// group with display name, search by display name, exact expected
[
'Test One', false, true,
[$this->getGroupMock('test1', 'Test One')],
[],
[['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]],
[],
true,
false,
],
[
'test', false, false,
[$this->getGroupMock('test1')],
[],
[],
[],
true,
false,
],
[
'test', false, true,
[
$this->getGroupMock('test'),
$this->getGroupMock('test1'),
],
[],
[['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]],
[['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]],
false,
false,
],
[
'test', false, false,
[
$this->getGroupMock('test'),
$this->getGroupMock('test1'),
],
[],
[['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]],
[],
true,
false,
],
[
'test', false, true,
[
$this->getGroupMock('test0'),
$this->getGroupMock('test1'),
],
[],
[],
[
['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']],
['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']],
],
false,
null,
],
[
'test', false, false,
[
$this->getGroupMock('test0'),
$this->getGroupMock('test1'),
],
[],
[],
[],
true,
null,
],
[
'test', false, true,
[
$this->getGroupMock('test0'),
$this->getGroupMock('test1'),
],
[],
[
['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']],
],
[
['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']],
['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']],
],
false,
$this->getGroupMock('test'),
],
[
'test', false, false,
[
$this->getGroupMock('test0'),
$this->getGroupMock('test1'),
],
[],
[
['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']],
],
[],
true,
$this->getGroupMock('test'),
],
['test', true, true, [], [], [], [], true, false],
['test', true, false, [], [], [], [], true, false],
[
'test', true, true,
[
$this->getGroupMock('test1'),
$this->getGroupMock('test2'),
],
[$this->getGroupMock('test1')],
[],
[['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]],
false,
false,
],
[
'test', true, false,
[
$this->getGroupMock('test1'),
$this->getGroupMock('test2'),
],
[$this->getGroupMock('test1')],
[],
[],
true,
false,
],
[
'test', true, true,
[
$this->getGroupMock('test'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test')],
[['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]],
[],
false,
false,
],
[
'test', true, false,
[
$this->getGroupMock('test'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test')],
[['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]],
[],
true,
false,
],
[
'test', true, true,
[
$this->getGroupMock('test'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test1')],
[],
[['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]],
false,
false,
],
[
'test', true, false,
[
$this->getGroupMock('test'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test1')],
[],
[],
true,
false,
],
[
'test', true, true,
[
$this->getGroupMock('test'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')],
[['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]],
[['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]],
false,
false,
],
[
'test', true, false,
[
$this->getGroupMock('test'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')],
[['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]],
[],
true,
false,
],
[
'test', true, true,
[
$this->getGroupMock('test0'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')],
[],
[
['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']],
['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']],
],
false,
null,
],
[
'test', true, false,
[
$this->getGroupMock('test0'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')],
[],
[],
true,
null,
],
[
'test', true, true,
[
$this->getGroupMock('test0'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')],
[
['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']],
],
[
['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']],
['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']],
],
false,
$this->getGroupMock('test'),
],
[
'test', true, false,
[
$this->getGroupMock('test0'),
$this->getGroupMock('test1'),
],
[$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')],
[
['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']],
],
[],
true,
$this->getGroupMock('test'),
],
];
}
/**
* @dataProvider dataGetGroups
*
* @param string $searchTerm
* @param bool $shareWithGroupOnly
* @param bool $shareeEnumeration
* @param array $groupResponse
* @param array $userGroupsResponse
* @param array $exactExpected
* @param array $expected
* @param bool $reachedEnd
* @param bool|IGroup $singleGroup
*/
public function testSearch(
$searchTerm,
$shareWithGroupOnly,
$shareeEnumeration,
array $groupResponse,
array $userGroupsResponse,
array $exactExpected,
array $expected,
$reachedEnd,
$singleGroup
) {
$this->config->expects($this->any())
->method('getAppValue')
->willReturnCallback(
function($appName, $key, $default)
use ($shareWithGroupOnly, $shareeEnumeration)
{
if ($appName === 'core' && $key === 'shareapi_only_share_with_group_members') {
return $shareWithGroupOnly ? 'yes' : 'no';
} else if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') {
return $shareeEnumeration ? 'yes' : 'no';
}
return $default;
}
);
$this->instantiatePlugin();
$this->groupManager->expects($this->once())
->method('search')
->with($searchTerm, $this->limit, $this->offset)
->willReturn($groupResponse);
if ($singleGroup !== false) {
$this->groupManager->expects($this->once())
->method('get')
->with($searchTerm)
->willReturn($singleGroup);
}
if ($shareWithGroupOnly) {
$this->session->expects($this->any())
->method('getUser')
->willReturn($this->user);
$numGetUserGroupsCalls = empty($groupResponse) ? 0 : 1;
$this->groupManager->expects($this->exactly($numGetUserGroupsCalls))
->method('getUserGroups')
->with($this->user)
->willReturn($userGroupsResponse);
}
$moreResults = $this->plugin->search($searchTerm, $this->limit, $this->offset, $this->searchResult);
$result = $this->searchResult->asArray();
$this->assertEquals($exactExpected, $result['exact']['groups']);
$this->assertEquals($expected, $result['groups']);
$this->assertSame($reachedEnd, $moreResults);
}
}

View File

@ -0,0 +1,180 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Test\Collaboration\Collaborators;
use OC\Collaboration\Collaborators\LookupPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
use OCP\IConfig;
use OCP\Share;
use Test\TestCase;
class LookupPluginTest extends TestCase {
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
protected $config;
/** @var IClientService|\PHPUnit_Framework_MockObject_MockObject */
protected $clientService;
/** @var LookupPlugin */
protected $plugin;
public function setUp() {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->clientService = $this->createMock(IClientService::class);
$this->plugin = new LookupPlugin($this->config, $this->clientService);
}
/**
* @dataProvider searchDataProvider
* @param array $searchParams
*/
public function testSearch(array $searchParams) {
$type = new SearchResultType('lookup');
/** @var ISearchResult|\PHPUnit_Framework_MockObject_MockObject $searchResult */
$searchResult = $this->createMock(ISearchResult::class);
$searchResult->expects($this->once())
->method('addResultSet')
->with($type, $searchParams['expectedResult'], []);
$this->config->expects($this->once())
->method('getAppValue')
->with('files_sharing', 'lookupServerEnabled', 'no')
->willReturn('yes');
$this->config->expects($this->once())
->method('getSystemValue')
->with('lookup_server', 'https://lookup.nextcloud.com')
->willReturn($searchParams['server']);
$response = $this->createMock(IResponse::class);
$response->expects($this->once())
->method('getBody')
->willReturn(json_encode($searchParams['resultBody']));
$client = $this->createMock(IClient::class);
$client->expects($this->once())
->method('get')
->willReturnCallback(function($url) use ($searchParams, $response) {
$this->assertSame(strpos($url, $searchParams['server'] . '/users?search='), 0);
$this->assertNotFalse(strpos($url, urlencode($searchParams['search'])));
return $response;
});
$this->clientService->expects($this->once())
->method('newClient')
->willReturn($client);
$moreResults = $this->plugin->search(
$searchParams['search'],
$searchParams['limit'],
$searchParams['offset'],
$searchResult
);
$this->assertFalse($moreResults);
}
public function testSearchLookupServerDisabled() {
$this->config->expects($this->once())
->method('getAppValue')
->with('files_sharing', 'lookupServerEnabled', 'no')
->willReturn('no');
/** @var ISearchResult|\PHPUnit_Framework_MockObject_MockObject $searchResult */
$searchResult = $this->createMock(ISearchResult::class);
$searchResult->expects($this->never())
->method('addResultSet');
$searchResult->expects($this->never())
->method('markExactIdMatch');
$this->assertFalse($this->plugin->search('irr', 10, 0, $searchResult));
}
public function searchDataProvider() {
$fedIDs = [
'foo@enceladus.moon',
'foobar@enceladus.moon',
'foongus@enceladus.moon',
];
return [
// #0, standard search with results
[[
'search' => 'foo',
'limit' => 10,
'offset' => 0,
'server' => 'https://lookup.example.io',
'resultBody' => [
[ 'federationId' => $fedIDs[0] ],
[ 'federationId' => $fedIDs[1] ],
[ 'federationId' => $fedIDs[2] ],
],
'expectedResult' => [
[
'label' => $fedIDs[0],
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $fedIDs[0]
],
'extra' => ['federationId' => $fedIDs[0]],
],
[
'label' => $fedIDs[1],
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $fedIDs[1]
],
'extra' => ['federationId' => $fedIDs[1]],
],
[
'label' => $fedIDs[2],
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $fedIDs[2]
],
'extra' => ['federationId' => $fedIDs[2]],
],
]
]],
// #1, search without results
[[
'search' => 'foo',
'limit' => 10,
'offset' => 0,
'server' => 'https://lookup.example.io',
'resultBody' => [],
'expectedResult' => [],
]],
];
}
}

View File

@ -0,0 +1,336 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Test\Collaboration\Collaborators;
use OC\Collaboration\Collaborators\MailPlugin;
use OC\Collaboration\Collaborators\SearchResult;
use OC\Federation\CloudIdManager;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IConfig;
use OCP\Share;
use Test\TestCase;
class MailPluginTest extends TestCase {
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
protected $config;
/** @var IManager|\PHPUnit_Framework_MockObject_MockObject */
protected $contactsManager;
/** @var ICloudIdManager|\PHPUnit_Framework_MockObject_MockObject */
protected $cloudIdManager;
/** @var MailPlugin */
protected $plugin;
/** @var SearchResult */
protected $searchResult;
public function setUp() {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->contactsManager = $this->createMock(IManager::class);
$this->cloudIdManager = new CloudIdManager();
$this->searchResult = new SearchResult();
}
public function instantiatePlugin() {
$this->plugin = new MailPlugin($this->contactsManager, $this->cloudIdManager, $this->config);
}
/**
* @dataProvider dataGetEmail
*
* @param string $searchTerm
* @param array $contacts
* @param bool $shareeEnumeration
* @param array $expected
* @param bool $reachedEnd
*/
public function testSearch($searchTerm, $contacts, $shareeEnumeration, $expected, $exactIdMatch, $reachedEnd) {
$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();
$this->contactsManager->expects($this->any())
->method('search')
->with($searchTerm, ['EMAIL', 'FN'])
->willReturn($contacts);
$moreResults = $this->plugin->search($searchTerm, 0, 0, $this->searchResult);
$result = $this->searchResult->asArray();
$this->assertSame($exactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails')));
$this->assertEquals($expected, $result);
$this->assertSame($reachedEnd, $moreResults);
}
public function dataGetEmail() {
return [
['test', [], true, ['emails' => [], 'exact' => ['emails' => []]], false, true],
['test', [], false, ['emails' => [], 'exact' => ['emails' => []]], false, true],
[
'test@remote.com',
[],
true,
['emails' => [], 'exact' => ['emails' => [['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]],
false,
true,
],
[ // no valid email address
'test@remote',
[],
true,
['emails' => [], 'exact' => ['emails' => []]],
false,
true,
],
[
'test@remote.com',
[],
false,
['emails' => [], 'exact' => ['emails' => [['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]],
false,
true,
],
[
'test',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
true,
['emails' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]], 'exact' => ['emails' => []]],
false,
true,
],
[
'test',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
false,
['emails' => [], 'exact' => ['emails' => []]],
false,
true,
],
[
'test@remote.com',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
true,
['emails' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]], 'exact' => ['emails' => [['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]],
false,
true,
],
[
'test@remote.com',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
false,
['emails' => [], 'exact' => ['emails' => [['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]],
false,
true,
],
[
'username@localhost',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
true,
['emails' => [], 'exact' => ['emails' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]]]],
true,
true,
],
[
'username@localhost',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
false,
['emails' => [], 'exact' => ['emails' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]]]],
true,
true,
],
// contact with space
[
'user name@localhost',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'FN' => 'User Name @ Localhost',
'EMAIL' => [
'user name@localhost',
],
],
],
false,
['emails' => [], 'exact' => ['emails' => [['label' => 'User Name @ Localhost (user name@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'user name@localhost']]]]],
true,
true,
],
// remote with space, no contact
[
'user space@remote.com',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'EMAIL' => [
],
],
[
'FN' => 'User @ Localhost',
'EMAIL' => [
'username@localhost',
],
],
],
false,
['emails' => [], 'exact' => ['emails' => []]],
false,
true,
],
// Local user found by email
[
'test@example.com',
[
[
'FN' => 'User',
'EMAIL' => ['test@example.com'],
'CLOUD' => ['test@localhost'],
'isLocalSystemBook' => true,
]
],
false,
['users' => [], 'exact' => ['users' => [['label' => 'User (test@example.com)','value' => ['shareType' => 0, 'shareWith' => 'test'],]]]],
true,
false,
]
];
}
}

View File

@ -0,0 +1,388 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Test\Collaboration\Collaborators;
use OC\Collaboration\Collaborators\RemotePlugin;
use OC\Collaboration\Collaborators\SearchResult;
use OC\Federation\CloudIdManager;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IConfig;
use OCP\Share;
use Test\TestCase;
class RemotePluginTest extends TestCase {
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
protected $config;
/** @var IManager|\PHPUnit_Framework_MockObject_MockObject */
protected $contactsManager;
/** @var ICloudIdManager|\PHPUnit_Framework_MockObject_MockObject */
protected $cloudIdManager;
/** @var RemotePlugin */
protected $plugin;
/** @var SearchResult */
protected $searchResult;
public function setUp() {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->contactsManager = $this->createMock(IManager::class);
$this->cloudIdManager = new CloudIdManager();
$this->searchResult = new SearchResult();
}
public function instantiatePlugin() {
$this->plugin = new RemotePlugin($this->contactsManager, $this->cloudIdManager, $this->config);
}
/**
* @dataProvider dataGetRemote
*
* @param string $searchTerm
* @param array $contacts
* @param bool $shareeEnumeration
* @param array $expected
* @param bool $exactIdMatch
* @param bool $reachedEnd
*/
public function testSearch($searchTerm, array $contacts, $shareeEnumeration, array $expected, $exactIdMatch, $reachedEnd) {
$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();
$this->contactsManager->expects($this->any())
->method('search')
->with($searchTerm, ['CLOUD', 'FN'])
->willReturn($contacts);
$moreResults = $this->plugin->search($searchTerm, 0, 0, $this->searchResult);
$result = $this->searchResult->asArray();
$this->assertSame($exactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('remotes')));
$this->assertEquals($expected, $result);
$this->assertSame($reachedEnd, $moreResults);
}
/**
* @dataProvider dataTestSplitUserRemote
*
* @param string $remote
* @param string $expectedUser
* @param string $expectedUrl
*/
public function testSplitUserRemote($remote, $expectedUser, $expectedUrl) {
$this->instantiatePlugin();
list($remoteUser, $remoteUrl) = $this->plugin->splitUserRemote($remote);
$this->assertSame($expectedUser, $remoteUser);
$this->assertSame($expectedUrl, $remoteUrl);
}
/**
* @dataProvider dataTestSplitUserRemoteError
*
* @param string $id
* @expectedException \Exception
*/
public function testSplitUserRemoteError($id) {
$this->instantiatePlugin();
$this->plugin->splitUserRemote($id);
}
public function dataGetRemote() {
return [
['test', [], true, ['remotes' => [], 'exact' => ['remotes' => []]], false, true],
['test', [], false, ['remotes' => [], 'exact' => ['remotes' => []]], false, true],
[
'test@remote',
[],
true,
['remotes' => [], 'exact' => ['remotes' => [['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']]]]],
false,
true,
],
[
'test@remote',
[],
false,
['remotes' => [], 'exact' => ['remotes' => [['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']]]]],
false,
true,
],
[
'test',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'CLOUD' => [
],
],
[
'FN' => 'User @ Localhost',
'CLOUD' => [
'username@localhost',
],
],
],
true,
['remotes' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]], 'exact' => ['remotes' => []]],
false,
true,
],
[
'test',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'CLOUD' => [
],
],
[
'FN' => 'User @ Localhost',
'CLOUD' => [
'username@localhost',
],
],
],
false,
['remotes' => [], 'exact' => ['remotes' => []]],
false,
true,
],
[
'test@remote',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'CLOUD' => [
],
],
[
'FN' => 'User @ Localhost',
'CLOUD' => [
'username@localhost',
],
],
],
true,
['remotes' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]], 'exact' => ['remotes' => [['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']]]]],
false,
true,
],
[
'test@remote',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'CLOUD' => [
],
],
[
'FN' => 'User @ Localhost',
'CLOUD' => [
'username@localhost',
],
],
],
false,
['remotes' => [], 'exact' => ['remotes' => [['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']]]]],
false,
true,
],
[
'username@localhost',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'CLOUD' => [
],
],
[
'FN' => 'User @ Localhost',
'CLOUD' => [
'username@localhost',
],
],
],
true,
['remotes' => [], 'exact' => ['remotes' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]]]],
true,
true,
],
[
'username@localhost',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'CLOUD' => [
],
],
[
'FN' => 'User @ Localhost',
'CLOUD' => [
'username@localhost',
],
],
],
false,
['remotes' => [], 'exact' => ['remotes' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]]]],
true,
true,
],
// contact with space
[
'user name@localhost',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'CLOUD' => [
],
],
[
'FN' => 'User Name @ Localhost',
'CLOUD' => [
'user name@localhost',
],
],
],
false,
['remotes' => [], 'exact' => ['remotes' => [['label' => 'User Name @ Localhost (user name@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'user name@localhost', 'server' => 'localhost']]]]],
true,
true,
],
// remote with space, no contact
[
'user space@remote',
[
[
'FN' => 'User3 @ Localhost',
],
[
'FN' => 'User2 @ Localhost',
'CLOUD' => [
],
],
[
'FN' => 'User @ Localhost',
'CLOUD' => [
'username@localhost',
],
],
],
false,
['remotes' => [], 'exact' => ['remotes' => [['label' => 'user space@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'user space@remote']]]]],
false,
true,
],
];
}
public function dataTestSplitUserRemote() {
$userPrefix = ['user@name', 'username'];
$protocols = ['', 'http://', 'https://'];
$remotes = [
'localhost',
'local.host',
'dev.local.host',
'dev.local.host/path',
'dev.local.host/at@inpath',
'127.0.0.1',
'::1',
'::192.0.2.128',
'::192.0.2.128/at@inpath',
];
$testCases = [];
foreach ($userPrefix as $user) {
foreach ($remotes as $remote) {
foreach ($protocols as $protocol) {
$baseUrl = $user . '@' . $protocol . $remote;
$testCases[] = [$baseUrl, $user, $protocol . $remote];
$testCases[] = [$baseUrl . '/', $user, $protocol . $remote];
$testCases[] = [$baseUrl . '/index.php', $user, $protocol . $remote];
$testCases[] = [$baseUrl . '/index.php/s/token', $user, $protocol . $remote];
}
}
}
return $testCases;
}
public function dataTestSplitUserRemoteError() {
return array(
// Invalid path
array('user@'),
// Invalid user
array('@server'),
array('us/er@server'),
array('us:er@server'),
// Invalid splitting
array('user'),
array(''),
array('us/erserver'),
array('us:erserver'),
);
}
}

View File

@ -0,0 +1,219 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Test\Collaboration\Collaborators;
use OC\Collaboration\Collaborators\Search;
use OC\Collaboration\Collaborators\SearchResult;
use OCP\Collaboration\Collaborators\ISearch;
use OCP\Collaboration\Collaborators\ISearchPlugin;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\IContainer;
use OCP\Share;
use Test\TestCase;
class SearchTest extends TestCase {
/** @var IContainer|\PHPUnit_Framework_MockObject_MockObject */
protected $container;
/** @var ISearch */
protected $search;
public function setUp() {
parent::setUp();
$this->container = $this->createMock(IContainer::class);
$this->search = new Search($this->container);
}
/**
* @dataProvider dataSearchSharees
*
* @param string $searchTerm
* @param array $shareTypes
* @param int $page
* @param int $perPage
* @param array $mockedUserResult
* @param array $mockedGroupsResult
* @param array $mockedRemotesResult
* @param array $expected
* @param bool $expectedMoreResults
*/
public function testSearch(
$searchTerm,
array $shareTypes,
$page,
$perPage,
array $mockedUserResult,
array $mockedGroupsResult,
array $mockedRemotesResult,
array $expected,
$expectedMoreResults
) {
$searchResult = new SearchResult();
$userPlugin = $this->createMock(ISearchPlugin::class);
$userPlugin->expects($this->any())
->method('search')
->willReturnCallback(function() use ($searchResult, $mockedUserResult, $expectedMoreResults) {
$type = new SearchResultType('users');
$searchResult->addResultSet($type, $mockedUserResult);
return $expectedMoreResults;
});
$groupPlugin = $this->createMock(ISearchPlugin::class);
$groupPlugin->expects($this->any())
->method('search')
->willReturnCallback(function() use ($searchResult, $mockedGroupsResult, $expectedMoreResults) {
$type = new SearchResultType('groups');
$searchResult->addResultSet($type, $mockedGroupsResult);
return $expectedMoreResults;
});
$remotePlugin = $this->createMock(ISearchPlugin::class);
$remotePlugin->expects($this->any())
->method('search')
->willReturnCallback(function() use ($searchResult, $mockedRemotesResult, $expectedMoreResults) {
if($mockedRemotesResult !== null) {
$type = new SearchResultType('remotes');
$searchResult->addResultSet($type, $mockedRemotesResult['results'], $mockedRemotesResult['exact']);
if($mockedRemotesResult['exactIdMatch'] === true) {
$searchResult->markExactIdMatch($type);
}
}
return $expectedMoreResults;
});
$this->container->expects($this->any())
->method('resolve')
->willReturnCallback(function($class) use ($searchResult, $userPlugin, $groupPlugin, $remotePlugin) {
if($class === SearchResult::class) {
return $searchResult;
} elseif ($class === $userPlugin) {
return $userPlugin;
} elseif ($class === $groupPlugin) {
return $groupPlugin;
} elseif ($class === $remotePlugin) {
return $remotePlugin;
}
return null;
});
$this->search->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => $userPlugin]);
$this->search->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => $groupPlugin]);
$this->search->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => $remotePlugin]);
list($results, $moreResults) = $this->search->search($searchTerm, $shareTypes, false, $perPage, $perPage * ($page - 1));
$this->assertEquals($expected, $results);
$this->assertSame($expectedMoreResults, $moreResults);
}
public function dataSearchSharees() {
return [
[
'test', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, [], [], ['results' => [], 'exact' => [], 'exactIdMatch' => false],
[
'exact' => ['users' => [], 'groups' => [], 'remotes' => []],
'users' => [],
'groups' => [],
'remotes' => [],
], false
],
[
'test', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, [], [], ['results' => [], 'exact' => [], 'exactIdMatch' => false],
[
'exact' => ['users' => [], 'groups' => [], 'remotes' => []],
'users' => [],
'groups' => [],
'remotes' => [],
], false
],
[
'test', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, [
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
], [
['label' => 'testgroup1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'testgroup1']],
], [
'results' => [['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']]], 'exact' => [], 'exactIdMatch' => false,
],
[
'exact' => ['users' => [], 'groups' => [], 'remotes' => []],
'users' => [
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
],
'groups' => [
['label' => 'testgroup1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'testgroup1']],
],
'remotes' => [
['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']],
],
], true,
],
// No groups requested
[
'test', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_REMOTE], 1, 2, [
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
], [], [
'results' => [['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']]], 'exact' => [], 'exactIdMatch' => false
],
[
'exact' => ['users' => [], 'remotes' => []],
'users' => [
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
],
'remotes' => [
['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']],
],
], false,
],
// Share type restricted to user - Only one user
[
'test', [Share::SHARE_TYPE_USER], 1, 2, [
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
], [], [],
[
'exact' => ['users' => []],
'users' => [
['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
],
], false,
],
// Share type restricted to user - Multipage result
[
'test', [Share::SHARE_TYPE_USER], 1, 2, [
['label' => 'test 1', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
['label' => 'test 2', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']],
], [], [],
[
'exact' => ['users' => []],
'users' => [
['label' => 'test 1', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
['label' => 'test 2', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']],
],
], true,
],
];
}
}

View File

@ -0,0 +1,445 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Test\Collaboration\Collaborators;
use OC\Collaboration\Collaborators\SearchResult;
use OC\Collaboration\Collaborators\UserPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Share;
use Test\TestCase;
class UserPluginTest extends TestCase {
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
protected $config;
/** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
protected $userManager;
/** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */
protected $groupManager;
/** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
protected $session;
/** @var UserPlugin */
protected $plugin;
/** @var ISearchResult */
protected $searchResult;
/** @var int */
protected $limit = 2;
/** @var int */
protected $offset = 0;
/** @var IUser|\PHPUnit_Framework_MockObject_MockObject */
protected $user;
public function setUp() {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->session = $this->createMock(IUserSession::class);
$this->searchResult = new SearchResult();
$this->user = $this->getUserMock('admin', 'Administrator');
}
public function instantiatePlugin() {
// cannot be done within setUp, because dependent mocks needs to be set
// up with configuration etc. first
$this->plugin = new UserPlugin(
$this->config,
$this->userManager,
$this->groupManager,
$this->session
);
}
public function getUserMock($uid, $displayName) {
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->willReturn($uid);
$user->expects($this->any())
->method('getDisplayName')
->willReturn($displayName);
return $user;
}
public function dataGetUsers() {
return [
['test', false, true, [], [], [], [], true, false],
['test', false, false, [], [], [], [], true, false],
['test', true, true, [], [], [], [], true, false],
['test', true, false, [], [], [], [], true, false],
[
'test', false, true, [], [],
[
['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']],
], [], true, $this->getUserMock('test', 'Test')
],
[
'test', false, false, [], [],
[
['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']],
], [], true, $this->getUserMock('test', 'Test')
],
[
'test', true, true, [], [],
[], [], true, $this->getUserMock('test', 'Test')
],
[
'test', true, false, [], [],
[], [], true, $this->getUserMock('test', 'Test')
],
[
'test', true, true, ['test-group'], [['test-group', 'test', 2, 0, []]],
[
['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']],
], [], true, $this->getUserMock('test', 'Test')
],
[
'test', true, false, ['test-group'], [['test-group', 'test', 2, 0, []]],
[
['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']],
], [], true, $this->getUserMock('test', 'Test')
],
[
'test',
false,
true,
[],
[
$this->getUserMock('test1', 'Test One'),
],
[],
[
['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
],
true,
false,
],
[
'test',
false,
false,
[],
[
$this->getUserMock('test1', 'Test One'),
],
[],
[],
true,
false,
],
[
'test',
false,
true,
[],
[
$this->getUserMock('test1', 'Test One'),
$this->getUserMock('test2', 'Test Two'),
],
[],
[
['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
['label' => 'Test Two', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']],
],
false,
false,
],
[
'test',
false,
false,
[],
[
$this->getUserMock('test1', 'Test One'),
$this->getUserMock('test2', 'Test Two'),
],
[],
[],
true,
false,
],
[
'test',
false,
true,
[],
[
$this->getUserMock('test0', 'Test'),
$this->getUserMock('test1', 'Test One'),
$this->getUserMock('test2', 'Test Two'),
],
[
['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test0']],
],
[
['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
['label' => 'Test Two', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']],
],
false,
false,
],
[
'test',
false,
false,
[],
[
$this->getUserMock('test0', 'Test'),
$this->getUserMock('test1', 'Test One'),
$this->getUserMock('test2', 'Test Two'),
],
[
['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test0']],
],
[],
true,
false,
],
[
'test',
true,
true,
['abc', 'xyz'],
[
['abc', 'test', 2, 0, ['test1' => 'Test One']],
['xyz', 'test', 2, 0, []],
],
[],
[
['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
],
true,
false,
],
[
'test',
true,
false,
['abc', 'xyz'],
[
['abc', 'test', 2, 0, ['test1' => 'Test One']],
['xyz', 'test', 2, 0, []],
],
[],
[],
true,
false,
],
[
'test',
true,
true,
['abc', 'xyz'],
[
['abc', 'test', 2, 0, [
'test1' => 'Test One',
'test2' => 'Test Two',
]],
['xyz', 'test', 2, 0, [
'test1' => 'Test One',
'test2' => 'Test Two',
]],
],
[],
[
['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']],
['label' => 'Test Two', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']],
],
false,
false,
],
[
'test',
true,
false,
['abc', 'xyz'],
[
['abc', 'test', 2, 0, [
'test1' => 'Test One',
'test2' => 'Test Two',
]],
['xyz', 'test', 2, 0, [
'test1' => 'Test One',
'test2' => 'Test Two',
]],
],
[],
[],
true,
false,
],
[
'test',
true,
true,
['abc', 'xyz'],
[
['abc', 'test', 2, 0, [
'test' => 'Test One',
]],
['xyz', 'test', 2, 0, [
'test2' => 'Test Two',
]],
],
[
['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']],
],
[
['label' => 'Test Two', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']],
],
false,
false,
],
[
'test',
true,
false,
['abc', 'xyz'],
[
['abc', 'test', 2, 0, [
'test' => 'Test One',
]],
['xyz', 'test', 2, 0, [
'test2' => 'Test Two',
]],
],
[
['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']],
],
[],
true,
false,
],
];
}
/**
* @dataProvider dataGetUsers
*
* @param string $searchTerm
* @param bool $shareWithGroupOnly
* @param bool $shareeEnumeration
* @param array $groupResponse
* @param array $userResponse
* @param array $exactExpected
* @param array $expected
* @param bool $reachedEnd
* @param bool|IUser $singleUser
*/
public function testSearch(
$searchTerm,
$shareWithGroupOnly,
$shareeEnumeration,
array $groupResponse,
array $userResponse,
array $exactExpected,
array $expected,
$reachedEnd,
$singleUser
) {
$this->config->expects($this->any())
->method('getAppValue')
->willReturnCallback(
function($appName, $key, $default)
use ($shareWithGroupOnly, $shareeEnumeration)
{
if ($appName === 'core' && $key === 'shareapi_only_share_with_group_members') {
return $shareWithGroupOnly ? 'yes' : 'no';
} else if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') {
return $shareeEnumeration ? 'yes' : 'no';
}
return $default;
}
);
$this->instantiatePlugin();
$this->session->expects($this->any())
->method('getUser')
->willReturn($this->user);
if(!$shareWithGroupOnly) {
$this->userManager->expects($this->once())
->method('searchDisplayName')
->with($searchTerm, $this->limit, $this->offset)
->willReturn($userResponse);
} else {
if ($singleUser !== false) {
$this->groupManager->expects($this->exactly(2))
->method('getUserGroupIds')
->withConsecutive(
$this->user,
$singleUser
)
->willReturn($groupResponse);
} else {
$this->groupManager->expects($this->once())
->method('getUserGroupIds')
->with($this->user)
->willReturn($groupResponse);
}
$this->groupManager->expects($this->exactly(sizeof($groupResponse)))
->method('displayNamesInGroup')
->with($this->anything(), $searchTerm, $this->limit, $this->offset)
->willReturnMap($userResponse);
}
if ($singleUser !== false) {
$this->userManager->expects($this->once())
->method('get')
->with($searchTerm)
->willReturn($singleUser);
}
$moreResults = $this->plugin->search($searchTerm, $this->limit, $this->offset, $this->searchResult);
$result = $this->searchResult->asArray();
$this->assertEquals($exactExpected, $result['exact']['users']);
$this->assertEquals($expected, $result['users']);
$this->assertSame($reachedEnd, $moreResults);
}
}