aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/composer/composer/autoload_classmap.php6
-rw-r--r--lib/composer/composer/autoload_static.php6
-rw-r--r--lib/l10n/zh_HK.js4
-rw-r--r--lib/l10n/zh_HK.json4
-rw-r--r--lib/private/Accounts/AccountManager.php14
-rw-r--r--lib/private/App/AppStore/Fetcher/AppFetcher.php4
-rw-r--r--lib/private/AppFramework/Utility/SimpleContainer.php4
-rw-r--r--lib/private/Collaboration/Collaborators/MailPlugin.php38
-rw-r--r--lib/private/Collaboration/Collaborators/UserPlugin.php62
-rw-r--r--lib/private/Contacts/ContactsMenu/ContactsStore.php113
-rw-r--r--lib/private/Files/Cache/Cache.php47
-rw-r--r--lib/private/Files/Cache/FailedCache.php5
-rw-r--r--lib/private/Files/Cache/MoveFromCacheTrait.php36
-rw-r--r--lib/private/Files/Cache/QuerySearchHelper.php5
-rw-r--r--lib/private/Files/Cache/Wrapper/CacheJail.php113
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php26
-rw-r--r--lib/private/Files/ObjectStore/S3ObjectTrait.php2
-rw-r--r--lib/private/Files/SimpleFS/SimpleFile.php4
-rw-r--r--lib/private/Files/Storage/Wrapper/Jail.php2
-rw-r--r--lib/private/Files/View.php5
-rw-r--r--lib/private/InitialStateService.php2
-rw-r--r--lib/private/KnownUser/KnownUser.php46
-rw-r--r--lib/private/KnownUser/KnownUserMapper.php87
-rw-r--r--lib/private/KnownUser/KnownUserService.php87
-rw-r--r--lib/private/Lockdown/Filesystem/NullCache.php5
-rw-r--r--lib/private/Search/Provider/File.php22
-rw-r--r--lib/private/Share20/Manager.php9
-rw-r--r--lib/private/Share20/Share.php12
-rw-r--r--lib/private/URLGenerator.php7
-rw-r--r--lib/private/User/Backend.php4
-rw-r--r--lib/private/User/Database.php48
-rw-r--r--lib/private/User/Manager.php37
-rw-r--r--lib/public/Files/Cache/ICache.php11
-rw-r--r--lib/public/IUserManager.php12
-rw-r--r--lib/public/Share/IManager.php16
-rw-r--r--lib/public/User/Backend/IGetRealUIDBackend.php2
-rw-r--r--lib/public/User/Backend/ISearchKnownUsersBackend.php43
-rw-r--r--lib/public/UserInterface.php4
38 files changed, 805 insertions, 149 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 2e6b68f35bb..86d24ad031c 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -534,6 +534,7 @@ return array(
'OCP\\User\\Backend\\IGetRealUIDBackend' => $baseDir . '/lib/public/User/Backend/IGetRealUIDBackend.php',
'OCP\\User\\Backend\\IPasswordConfirmationBackend' => $baseDir . '/lib/public/User/Backend/IPasswordConfirmationBackend.php',
'OCP\\User\\Backend\\IProvideAvatarBackend' => $baseDir . '/lib/public/User/Backend/IProvideAvatarBackend.php',
+ 'OCP\\User\\Backend\\ISearchKnownUsersBackend' => $baseDir . '/lib/public/User/Backend/ISearchKnownUsersBackend.php',
'OCP\\User\\Backend\\ISetDisplayNameBackend' => $baseDir . '/lib/public/User/Backend/ISetDisplayNameBackend.php',
'OCP\\User\\Backend\\ISetPasswordBackend' => $baseDir . '/lib/public/User/Backend/ISetPasswordBackend.php',
'OCP\\User\\Events\\BeforePasswordUpdatedEvent' => $baseDir . '/lib/public/User/Events/BeforePasswordUpdatedEvent.php',
@@ -948,6 +949,8 @@ return array(
'OC\\Core\\Migrations\\Version21000Date20201120141228' => $baseDir . '/core/Migrations/Version21000Date20201120141228.php',
'OC\\Core\\Migrations\\Version21000Date20201202095923' => $baseDir . '/core/Migrations/Version21000Date20201202095923.php',
'OC\\Core\\Migrations\\Version21000Date20210119195004' => $baseDir . '/core/Migrations/Version21000Date20210119195004.php',
+ 'OC\\Core\\Migrations\\Version21000Date20210309185126' => $baseDir . '/core/Migrations/Version21000Date20210309185126.php',
+ 'OC\\Core\\Migrations\\Version21000Date20210309185127' => $baseDir . '/core/Migrations/Version21000Date20210309185127.php',
'OC\\Core\\Migrations\\Version22000Date20210216080825' => $baseDir . '/core/Migrations/Version22000Date20210216080825.php',
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
@@ -1165,6 +1168,9 @@ return array(
'OC\\IntegrityCheck\\Helpers\\FileAccessHelper' => $baseDir . '/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php',
'OC\\IntegrityCheck\\Iterator\\ExcludeFileByNameFilterIterator' => $baseDir . '/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php',
'OC\\IntegrityCheck\\Iterator\\ExcludeFoldersByPathFilterIterator' => $baseDir . '/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php',
+ 'OC\\KnownUser\\KnownUser' => $baseDir . '/lib/private/KnownUser/KnownUser.php',
+ 'OC\\KnownUser\\KnownUserMapper' => $baseDir . '/lib/private/KnownUser/KnownUserMapper.php',
+ 'OC\\KnownUser\\KnownUserService' => $baseDir . '/lib/private/KnownUser/KnownUserService.php',
'OC\\L10N\\Factory' => $baseDir . '/lib/private/L10N/Factory.php',
'OC\\L10N\\L10N' => $baseDir . '/lib/private/L10N/L10N.php',
'OC\\L10N\\L10NString' => $baseDir . '/lib/private/L10N/L10NString.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 0a7ebd3ea38..5cf2b9a2cb0 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -563,6 +563,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\User\\Backend\\IGetRealUIDBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IGetRealUIDBackend.php',
'OCP\\User\\Backend\\IPasswordConfirmationBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IPasswordConfirmationBackend.php',
'OCP\\User\\Backend\\IProvideAvatarBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/IProvideAvatarBackend.php',
+ 'OCP\\User\\Backend\\ISearchKnownUsersBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISearchKnownUsersBackend.php',
'OCP\\User\\Backend\\ISetDisplayNameBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetDisplayNameBackend.php',
'OCP\\User\\Backend\\ISetPasswordBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetPasswordBackend.php',
'OCP\\User\\Events\\BeforePasswordUpdatedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforePasswordUpdatedEvent.php',
@@ -977,6 +978,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Migrations\\Version21000Date20201120141228' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20201120141228.php',
'OC\\Core\\Migrations\\Version21000Date20201202095923' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20201202095923.php',
'OC\\Core\\Migrations\\Version21000Date20210119195004' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20210119195004.php',
+ 'OC\\Core\\Migrations\\Version21000Date20210309185126' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20210309185126.php',
+ 'OC\\Core\\Migrations\\Version21000Date20210309185127' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20210309185127.php',
'OC\\Core\\Migrations\\Version22000Date20210216080825' => __DIR__ . '/../../..' . '/core/Migrations/Version22000Date20210216080825.php',
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
@@ -1194,6 +1197,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\IntegrityCheck\\Helpers\\FileAccessHelper' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php',
'OC\\IntegrityCheck\\Iterator\\ExcludeFileByNameFilterIterator' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php',
'OC\\IntegrityCheck\\Iterator\\ExcludeFoldersByPathFilterIterator' => __DIR__ . '/../../..' . '/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php',
+ 'OC\\KnownUser\\KnownUser' => __DIR__ . '/../../..' . '/lib/private/KnownUser/KnownUser.php',
+ 'OC\\KnownUser\\KnownUserMapper' => __DIR__ . '/../../..' . '/lib/private/KnownUser/KnownUserMapper.php',
+ 'OC\\KnownUser\\KnownUserService' => __DIR__ . '/../../..' . '/lib/private/KnownUser/KnownUserService.php',
'OC\\L10N\\Factory' => __DIR__ . '/../../..' . '/lib/private/L10N/Factory.php',
'OC\\L10N\\L10N' => __DIR__ . '/../../..' . '/lib/private/L10N/L10N.php',
'OC\\L10N\\L10NString' => __DIR__ . '/../../..' . '/lib/private/L10N/L10NString.php',
diff --git a/lib/l10n/zh_HK.js b/lib/l10n/zh_HK.js
index ba913a5ab16..7e278f02ad6 100644
--- a/lib/l10n/zh_HK.js
+++ b/lib/l10n/zh_HK.js
@@ -80,7 +80,7 @@ OC.L10N.register(
"Dot files are not allowed" : "不允許小數點開頭的檔案",
"Empty filename is not allowed" : "不允許空白的檔名",
"App \"%s\" cannot be installed because appinfo file cannot be read." : "應用程式 \"%s\" 無法安裝,因為無法讀取 appinfo 檔案。",
- "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "應用程式 \"%s\" 無法安裝,因為該應用程式不相容於目前版本的伺服器。",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "應用程式 \"%s\" 無法安裝,因為該應用程式不相容於目前伺服器的版本。",
"__language_name__" : "正體中文(香港)",
"This is an automatically sent email, please do not reply." : "此為自動寄送的電子郵件,請勿回覆。",
"Help" : "說明",
@@ -212,7 +212,7 @@ OC.L10N.register(
"PHP modules have been installed, but they are still listed as missing?" : "你已經安裝了指定的 PHP 模組,可是還是顯示為找不到嗎?",
"Please ask your server administrator to restart the web server." : "請聯絡您的系統管理員重新啟動網頁伺服器",
"PostgreSQL >= 9 required" : "需要 PostgreSQL 版本 >= 9",
- "Please upgrade your database version" : "請升級您的資料庫版本",
+ "Please upgrade your database version" : "請升級您數據庫的版本",
"Your data directory is readable by other users" : "您的資料目錄可以被其他用戶讀取",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "請將該目錄權限設定為 0770 ,以免其他用戶讀取目錄列表",
"Your data directory must be an absolute path" : "您的資料目錄必須為絕對路徑",
diff --git a/lib/l10n/zh_HK.json b/lib/l10n/zh_HK.json
index a398d9718de..049293eeb90 100644
--- a/lib/l10n/zh_HK.json
+++ b/lib/l10n/zh_HK.json
@@ -78,7 +78,7 @@
"Dot files are not allowed" : "不允許小數點開頭的檔案",
"Empty filename is not allowed" : "不允許空白的檔名",
"App \"%s\" cannot be installed because appinfo file cannot be read." : "應用程式 \"%s\" 無法安裝,因為無法讀取 appinfo 檔案。",
- "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "應用程式 \"%s\" 無法安裝,因為該應用程式不相容於目前版本的伺服器。",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "應用程式 \"%s\" 無法安裝,因為該應用程式不相容於目前伺服器的版本。",
"__language_name__" : "正體中文(香港)",
"This is an automatically sent email, please do not reply." : "此為自動寄送的電子郵件,請勿回覆。",
"Help" : "說明",
@@ -210,7 +210,7 @@
"PHP modules have been installed, but they are still listed as missing?" : "你已經安裝了指定的 PHP 模組,可是還是顯示為找不到嗎?",
"Please ask your server administrator to restart the web server." : "請聯絡您的系統管理員重新啟動網頁伺服器",
"PostgreSQL >= 9 required" : "需要 PostgreSQL 版本 >= 9",
- "Please upgrade your database version" : "請升級您的資料庫版本",
+ "Please upgrade your database version" : "請升級您數據庫的版本",
"Your data directory is readable by other users" : "您的資料目錄可以被其他用戶讀取",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "請將該目錄權限設定為 0770 ,以免其他用戶讀取目錄列表",
"Your data directory must be an absolute path" : "您的資料目錄必須為絕對路徑",
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php
index 05feaf87b8f..c5a0f21319e 100644
--- a/lib/private/Accounts/AccountManager.php
+++ b/lib/private/Accounts/AccountManager.php
@@ -229,19 +229,23 @@ class AccountManager implements IAccountManager {
}
public function searchUsers(string $property, array $values): array {
+ $chunks = array_chunk($values, 500);
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from($this->dataTable)
->where($query->expr()->eq('name', $query->createNamedParameter($property)))
- ->andWhere($query->expr()->in('value', $query->createNamedParameter($values, IQueryBuilder::PARAM_STR_ARRAY)));
+ ->andWhere($query->expr()->in('value', $query->createParameter('values')));
- $result = $query->execute();
$matches = [];
+ foreach ($chunks as $chunk) {
+ $query->setParameter('values', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
+ $result = $query->execute();
- while ($row = $result->fetch()) {
- $matches[$row['value']] = $row['uid'];
+ while ($row = $result->fetch()) {
+ $matches[$row['value']] = $row['uid'];
+ }
+ $result->closeCursor();
}
- $result->closeCursor();
return $matches;
}
diff --git a/lib/private/App/AppStore/Fetcher/AppFetcher.php b/lib/private/App/AppStore/Fetcher/AppFetcher.php
index 4dc517879e8..416e55463cf 100644
--- a/lib/private/App/AppStore/Fetcher/AppFetcher.php
+++ b/lib/private/App/AppStore/Fetcher/AppFetcher.php
@@ -86,6 +86,10 @@ class AppFetcher extends Fetcher {
protected function fetch($ETag, $content, $allowUnstable = false) {
/** @var mixed[] $response */
$response = parent::fetch($ETag, $content);
+
+ if (empty($response)) {
+ return [];
+ }
$allowPreReleases = $allowUnstable || $this->getChannel() === 'beta' || $this->getChannel() === 'daily';
$allowNightly = $allowUnstable || $this->getChannel() === 'daily';
diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php
index 2aa5da116e6..84c4c3447a2 100644
--- a/lib/private/AppFramework/Utility/SimpleContainer.php
+++ b/lib/private/AppFramework/Utility/SimpleContainer.php
@@ -52,11 +52,11 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
$this->container = new Container();
}
- public function get($id) {
+ public function get(string $id) {
return $this->query($id);
}
- public function has($id): bool {
+ public function has(string $id): bool {
// If a service is no registered but is an existing class, we can probably load it
return isset($this->container[$id]) || class_exists($id);
}
diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php
index 7bdd29afc4e..240e16374d5 100644
--- a/lib/private/Collaboration/Collaborators/MailPlugin.php
+++ b/lib/private/Collaboration/Collaborators/MailPlugin.php
@@ -27,6 +27,7 @@
namespace OC\Collaboration\Collaborators;
+use OC\KnownUser\KnownUserService;
use OCP\Collaboration\Collaborators\ISearchPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
@@ -40,8 +41,16 @@ use OCP\IUserSession;
use OCP\Share\IShare;
class MailPlugin implements ISearchPlugin {
- protected $shareeEnumeration;
+ /* @var bool */
protected $shareWithGroupOnly;
+ /* @var bool */
+ protected $shareeEnumeration;
+ /* @var bool */
+ protected $shareeEnumerationInGroupOnly;
+ /* @var bool */
+ protected $shareeEnumerationPhone;
+ /* @var bool */
+ protected $shareeEnumerationFullMatch;
/** @var IManager */
private $contactsManager;
@@ -52,20 +61,29 @@ class MailPlugin implements ISearchPlugin {
/** @var IGroupManager */
private $groupManager;
-
+ /** @var KnownUserService */
+ private $knownUserService;
/** @var IUserSession */
private $userSession;
- public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IGroupManager $groupManager, IUserSession $userSession) {
+ public function __construct(IManager $contactsManager,
+ ICloudIdManager $cloudIdManager,
+ IConfig $config,
+ IGroupManager $groupManager,
+ KnownUserService $knownUserService,
+ IUserSession $userSession) {
$this->contactsManager = $contactsManager;
$this->cloudIdManager = $cloudIdManager;
$this->config = $config;
$this->groupManager = $groupManager;
+ $this->knownUserService = $knownUserService;
$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';
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
+ $this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
+ $this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
}
/**
@@ -77,6 +95,8 @@ class MailPlugin implements ISearchPlugin {
* @since 13.0.0
*/
public function search($search, $limit, $offset, ISearchResult $searchResult) {
+ $currentUserId = $this->userSession->getUser()->getUID();
+
$result = $userResults = ['wide' => [], 'exact' => []];
$userType = new SearchResultType('users');
$emailType = new SearchResultType('emails');
@@ -120,7 +140,7 @@ class MailPlugin implements ISearchPlugin {
continue;
}
}
- if ($exactEmailMatch) {
+ if ($exactEmailMatch && $this->shareeEnumerationFullMatch) {
try {
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
} catch (\InvalidArgumentException $e) {
@@ -152,8 +172,12 @@ class MailPlugin implements ISearchPlugin {
continue;
}
- $addToWide = !$this->shareeEnumerationInGroupOnly;
- if ($this->shareeEnumerationInGroupOnly) {
+ $addToWide = !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone);
+ if (!$addToWide && $this->shareeEnumerationPhone && $this->knownUserService->isKnownToUser($currentUserId, $contact['UID'])) {
+ $addToWide = true;
+ }
+
+ if (!$addToWide && $this->shareeEnumerationInGroupOnly) {
$addToWide = false;
$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
foreach ($userGroups as $userGroup) {
@@ -181,7 +205,7 @@ class MailPlugin implements ISearchPlugin {
}
if ($exactEmailMatch
- || isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch) {
+ || (isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch)) {
if ($exactEmailMatch) {
$searchResult->markExactIdMatch($emailType);
}
diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php
index d832a42000c..12ed3e98932 100644
--- a/lib/private/Collaboration/Collaborators/UserPlugin.php
+++ b/lib/private/Collaboration/Collaborators/UserPlugin.php
@@ -32,6 +32,7 @@
namespace OC\Collaboration\Collaborators;
+use OC\KnownUser\KnownUserService;
use OCP\Collaboration\Collaborators\ISearchPlugin;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
@@ -46,8 +47,14 @@ use OCP\UserStatus\IManager as IUserStatusManager;
class UserPlugin implements ISearchPlugin {
/* @var bool */
protected $shareWithGroupOnly;
+ /* @var bool */
protected $shareeEnumeration;
+ /* @var bool */
protected $shareeEnumerationInGroupOnly;
+ /* @var bool */
+ protected $shareeEnumerationPhone;
+ /* @var bool */
+ protected $shareeEnumerationFullMatch;
/** @var IConfig */
private $config;
@@ -57,33 +64,30 @@ class UserPlugin implements ISearchPlugin {
private $userSession;
/** @var IUserManager */
private $userManager;
+ /** @var KnownUserService */
+ private $knownUserService;
/** @var IUserStatusManager */
private $userStatusManager;
- /**
- * UserPlugin constructor.
- *
- * @param IConfig $config
- * @param IUserManager $userManager
- * @param IGroupManager $groupManager
- * @param IUserSession $userSession
- * @param IUserStatusManager $userStatusManager
- */
public function __construct(IConfig $config,
IUserManager $userManager,
IGroupManager $groupManager,
IUserSession $userSession,
+ KnownUserService $knownUserService,
IUserStatusManager $userStatusManager) {
$this->config = $config;
$this->groupManager = $groupManager;
$this->userSession = $userSession;
$this->userManager = $userManager;
+ $this->knownUserService = $knownUserService;
$this->userStatusManager = $userStatusManager;
$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';
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
+ $this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
+ $this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
}
public function search($search, $limit, $offset, ISearchResult $searchResult) {
@@ -91,8 +95,9 @@ class UserPlugin implements ISearchPlugin {
$users = [];
$hasMoreResults = false;
+ $currentUserId = $this->userSession->getUser()->getUID();
$currentUserGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
- if ($this->shareWithGroupOnly) {
+ if ($this->shareWithGroupOnly || $this->shareeEnumerationInGroupOnly) {
// Search in all the groups this user is part of
foreach ($currentUserGroups as $userGroupId) {
$usersInGroup = $this->groupManager->displayNamesInGroup($userGroupId, $search, $limit, $offset);
@@ -109,9 +114,32 @@ class UserPlugin implements ISearchPlugin {
$hasMoreResults = true;
}
}
+
+ if (!$this->shareWithGroupOnly && $this->shareeEnumerationPhone) {
+ $usersTmp = $this->userManager->searchKnownUsersByDisplayName($currentUserId, $search, $limit, $offset);
+ if (!empty($usersTmp)) {
+ foreach ($usersTmp as $user) {
+ if ($user->isEnabled()) { // Don't keep deactivated users
+ $users[$user->getUID()] = $user;
+ }
+ }
+
+ uasort($users, function ($a, $b) {
+ /**
+ * @var \OC\User\User $a
+ * @var \OC\User\User $b
+ */
+ return strcasecmp($a->getDisplayName(), $b->getDisplayName());
+ });
+ }
+ }
} else {
// Search in all users
- $usersTmp = $this->userManager->searchDisplayName($search, $limit, $offset);
+ if ($this->shareeEnumerationPhone) {
+ $usersTmp = $this->userManager->searchKnownUsersByDisplayName($currentUserId, $search, $limit, $offset);
+ } else {
+ $usersTmp = $this->userManager->searchDisplayName($search, $limit, $offset);
+ }
foreach ($usersTmp as $user) {
if ($user->isEnabled()) { // Don't keep deactivated users
$users[$user->getUID()] = $user;
@@ -148,6 +176,7 @@ class UserPlugin implements ISearchPlugin {
if (
+ $this->shareeEnumerationFullMatch &&
$lowerSearch !== '' && (strtolower($uid) === $lowerSearch ||
strtolower($userDisplayName) === $lowerSearch ||
strtolower($userEmail) === $lowerSearch)
@@ -168,11 +197,16 @@ class UserPlugin implements ISearchPlugin {
];
} else {
$addToWideResults = false;
- if ($this->shareeEnumeration && !$this->shareeEnumerationInGroupOnly) {
+ if ($this->shareeEnumeration &&
+ !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone)) {
+ $addToWideResults = true;
+ }
+
+ if ($this->shareeEnumerationPhone && $this->knownUserService->isKnownToUser($currentUserId, $user->getUID())) {
$addToWideResults = true;
}
- if ($this->shareeEnumerationInGroupOnly) {
+ if (!$addToWideResults && $this->shareeEnumerationInGroupOnly) {
$commonGroups = array_intersect($currentUserGroups, $this->groupManager->getUserGroupIds($user));
if (!empty($commonGroups)) {
$addToWideResults = true;
@@ -195,7 +229,7 @@ class UserPlugin implements ISearchPlugin {
}
}
- if ($offset === 0 && !$foundUserById) {
+ if ($this->shareeEnumerationFullMatch && $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);
diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php
index e2bd7edc63d..69f26c7969f 100644
--- a/lib/private/Contacts/ContactsMenu/ContactsStore.php
+++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php
@@ -31,6 +31,7 @@
namespace OC\Contacts\ContactsMenu;
+use OC\KnownUser\KnownUserService;
use OCP\Contacts\ContactsMenu\IContactsStore;
use OCP\Contacts\ContactsMenu\IEntry;
use OCP\Contacts\IManager;
@@ -53,20 +54,19 @@ class ContactsStore implements IContactsStore {
/** @var IGroupManager */
private $groupManager;
- /**
- * @param IManager $contactsManager
- * @param IConfig $config
- * @param IUserManager $userManager
- * @param IGroupManager $groupManager
- */
+ /** @var KnownUserService */
+ private $knownUserService;
+
public function __construct(IManager $contactsManager,
IConfig $config,
IUserManager $userManager,
- IGroupManager $groupManager) {
+ IGroupManager $groupManager,
+ KnownUserService $knownUserService) {
$this->contactsManager = $contactsManager;
$this->config = $config;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
+ $this->knownUserService = $knownUserService;
}
/**
@@ -103,7 +103,7 @@ class ContactsStore implements IContactsStore {
}
/**
- * Filters the contacts. Applies 3 filters:
+ * Filters the contacts. Applied filters:
* 1. filter the current user
* 2. if the `shareapi_allow_share_dialog_user_enumeration` config option is
* enabled it will filter all local users
@@ -122,20 +122,22 @@ class ContactsStore implements IContactsStore {
array $entries,
$filter) {
$disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes';
- $restrictEnumeration = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
+ $restrictEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
+ $restrictEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
+ $allowEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes';
// whether to filter out local users
$skipLocal = false;
- // whether to filter out all users which doesn't have the same group as the current user
- $ownGroupsOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes' || $restrictEnumeration;
+ // whether to filter out all users which don't have a common group as the current user
+ $ownGroupsOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
$selfGroups = $this->groupManager->getUserGroupIds($self);
if ($excludedGroups) {
$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
$decodedExcludeGroups = json_decode($excludedGroups, true);
- $excludeGroupsList = ($decodedExcludeGroups !== null) ? $decodedExcludeGroups : [];
+ $excludeGroupsList = $decodedExcludeGroups ?? [];
if (count(array_intersect($excludeGroupsList, $selfGroups)) !== 0) {
// a group of the current user is excluded -> filter all local users
@@ -145,47 +147,80 @@ class ContactsStore implements IContactsStore {
$selfUID = $self->getUID();
- return array_values(array_filter($entries, function (IEntry $entry) use ($self, $skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $filter) {
- if ($skipLocal && $entry->getProperty('isLocalSystemBook') === true) {
+ return array_values(array_filter($entries, function (IEntry $entry) use ($skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $restrictEnumerationGroup, $restrictEnumerationPhone, $allowEnumerationFullMatch, $filter) {
+ if ($entry->getProperty('UID') === $selfUID) {
return false;
}
- // Prevent enumerating local users
- if ($disallowEnumeration && $entry->getProperty('isLocalSystemBook')) {
- $filterUser = true;
+ if ($entry->getProperty('isLocalSystemBook')) {
+ if ($skipLocal) {
+ return false;
+ }
+
+ $checkedCommonGroupAlready = false;
- $mailAddresses = $entry->getEMailAddresses();
- foreach ($mailAddresses as $mailAddress) {
- if ($mailAddress === $filter) {
- $filterUser = false;
- break;
+ // Prevent enumerating local users
+ if ($disallowEnumeration) {
+ if (!$allowEnumerationFullMatch) {
+ return false;
}
- }
- if ($entry->getProperty('UID') && $entry->getProperty('UID') === $filter) {
- $filterUser = false;
- }
+ $filterOutUser = true;
- if ($filterUser) {
- return false;
- }
- }
+ $mailAddresses = $entry->getEMailAddresses();
+ foreach ($mailAddresses as $mailAddress) {
+ if ($mailAddress === $filter) {
+ $filterOutUser = false;
+ break;
+ }
+ }
- if ($ownGroupsOnly && $entry->getProperty('isLocalSystemBook') === true) {
- $uid = $this->userManager->get($entry->getProperty('UID'));
+ if ($entry->getProperty('UID') && $entry->getProperty('UID') === $filter) {
+ $filterOutUser = false;
+ }
- if ($uid === null) {
- return false;
+ if ($filterOutUser) {
+ return false;
+ }
+ } elseif ($restrictEnumerationPhone || $restrictEnumerationGroup) {
+ $canEnumerate = false;
+ if ($restrictEnumerationPhone) {
+ $canEnumerate = $this->knownUserService->isKnownToUser($selfUID, $entry->getProperty('UID'));
+ }
+
+ if (!$canEnumerate && $restrictEnumerationGroup) {
+ $user = $this->userManager->get($entry->getProperty('UID'));
+
+ if ($user === null) {
+ return false;
+ }
+
+ $contactGroups = $this->groupManager->getUserGroupIds($user);
+ $canEnumerate = !empty(array_intersect($contactGroups, $selfGroups));
+ $checkedCommonGroupAlready = true;
+ }
+
+ if (!$canEnumerate) {
+ return false;
+ }
}
- $contactGroups = $this->groupManager->getUserGroupIds($uid);
- if (count(array_intersect($contactGroups, $selfGroups)) === 0) {
- // no groups in common, so shouldn't see the contact
- return false;
+ if ($ownGroupsOnly && !$checkedCommonGroupAlready) {
+ $user = $this->userManager->get($entry->getProperty('UID'));
+
+ if (!$user instanceof IUser) {
+ return false;
+ }
+
+ $contactGroups = $this->groupManager->getUserGroupIds($user);
+ if (empty(array_intersect($contactGroups, $selfGroups))) {
+ // no groups in common, so shouldn't see the contact
+ return false;
+ }
}
}
- return $entry->getProperty('UID') !== $selfUID;
+ return true;
}));
}
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php
index 2513abd525f..a18fddcbdeb 100644
--- a/lib/private/Files/Cache/Cache.php
+++ b/lib/private/Files/Cache/Cache.php
@@ -121,7 +121,7 @@ class Cache implements ICache {
$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
}
- private function getQueryBuilder() {
+ protected function getQueryBuilder() {
return new CacheQueryBuilder(
$this->connection,
\OC::$server->getSystemConfig(),
@@ -629,7 +629,7 @@ class Cache implements ICache {
/**
* Move a file or folder in the cache
*
- * @param \OCP\Files\Cache\ICache $sourceCache
+ * @param ICache $sourceCache
* @param string $sourcePath
* @param string $targetPath
* @throws \OC\DatabaseException
@@ -1076,4 +1076,47 @@ class Cache implements ICache {
public function normalize($path) {
return trim(\OC_Util::normalizeUnicode($path), '/');
}
+
+ /**
+ * Copy a file or folder in the cache
+ *
+ * @param ICache $sourceCache
+ * @param ICacheEntry $sourceEntry
+ * @param string $targetPath
+ * @return int fileid of copied entry
+ */
+ public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
+ if ($sourceEntry->getId() < 0) {
+ throw new \RuntimeException("Invalid source cache entry on copyFromCache");
+ }
+ $data = $this->cacheEntryToArray($sourceEntry);
+ $fileId = $this->put($targetPath, $data);
+ if ($fileId <= 0) {
+ throw new \RuntimeException("Failed to copy to " . $targetPath . " from cache with source data " . json_encode($data) . " ");
+ }
+ if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
+ $folderContent = $sourceCache->getFolderContentsById($sourceEntry->getId());
+ foreach ($folderContent as $subEntry) {
+ $subTargetPath = $targetPath . '/' . $subEntry->getName();
+ $this->copyFromCache($sourceCache, $subEntry, $subTargetPath);
+ }
+ }
+ return $fileId;
+ }
+
+ private function cacheEntryToArray(ICacheEntry $entry): array {
+ return [
+ 'size' => $entry->getSize(),
+ 'mtime' => $entry->getMTime(),
+ 'storage_mtime' => $entry->getStorageMTime(),
+ 'mimetype' => $entry->getMimeType(),
+ 'mimepart' => $entry->getMimePart(),
+ 'etag' => $entry->getEtag(),
+ 'permissions' => $entry->getPermissions(),
+ 'encrypted' => $entry->isEncrypted(),
+ 'creation_time' => $entry->getCreationTime(),
+ 'upload_time' => $entry->getUploadTime(),
+ 'metadata_etag' => $entry->getMetadataEtag(),
+ ];
+ }
}
diff --git a/lib/private/Files/Cache/FailedCache.php b/lib/private/Files/Cache/FailedCache.php
index b57ca2b2572..d9315ec50d5 100644
--- a/lib/private/Files/Cache/FailedCache.php
+++ b/lib/private/Files/Cache/FailedCache.php
@@ -24,6 +24,7 @@ namespace OC\Files\Cache;
use OCP\Constants;
use OCP\Files\Cache\ICache;
+use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Search\ISearchQuery;
/**
@@ -134,4 +135,8 @@ class FailedCache implements ICache {
public function normalize($path) {
return $path;
}
+
+ public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
+ throw new \Exception("Invalid cache");
+ }
}
diff --git a/lib/private/Files/Cache/MoveFromCacheTrait.php b/lib/private/Files/Cache/MoveFromCacheTrait.php
index 0f42b00df1c..2f5bbfb7eea 100644
--- a/lib/private/Files/Cache/MoveFromCacheTrait.php
+++ b/lib/private/Files/Cache/MoveFromCacheTrait.php
@@ -41,6 +41,8 @@ trait MoveFromCacheTrait {
*/
abstract public function put($file, array $data);
+ abstract public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int;
+
/**
* Move a file or folder in the cache
*
@@ -55,38 +57,4 @@ trait MoveFromCacheTrait {
$sourceCache->remove($sourcePath);
}
-
- /**
- * Copy a file or folder in the cache
- *
- * @param \OCP\Files\Cache\ICache $sourceCache
- * @param ICacheEntry $sourceEntry
- * @param string $targetPath
- */
- public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, $targetPath) {
- $this->put($targetPath, $this->cacheEntryToArray($sourceEntry));
- if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
- $folderContent = $sourceCache->getFolderContentsById($sourceEntry->getId());
- foreach ($folderContent as $subEntry) {
- $subTargetPath = $targetPath . '/' . $subEntry->getName();
- $this->copyFromCache($sourceCache, $subEntry, $subTargetPath);
- }
- }
- }
-
- private function cacheEntryToArray(ICacheEntry $entry) {
- return [
- 'size' => $entry->getSize(),
- 'mtime' => $entry->getMTime(),
- 'storage_mtime' => $entry->getStorageMTime(),
- 'mimetype' => $entry->getMimeType(),
- 'mimepart' => $entry->getMimePart(),
- 'etag' => $entry->getEtag(),
- 'permissions' => $entry->getPermissions(),
- 'encrypted' => $entry->isEncrypted(),
- 'creation_time' => $entry->getCreationTime(),
- 'upload_time' => $entry->getUploadTime(),
- 'metadata_etag' => $entry->getMetadataEtag(),
- ];
- }
}
diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php
index da1f5729d3c..fc4b582cbce 100644
--- a/lib/private/Files/Cache/QuerySearchHelper.php
+++ b/lib/private/Files/Cache/QuerySearchHelper.php
@@ -166,6 +166,9 @@ class QuerySearchHelper {
$field = 'tag.category';
} elseif ($field === 'fileid') {
$field = 'file.fileid';
+ } elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL) {
+ $field = 'path_hash';
+ $value = md5((string)$value);
}
return [$field, $value, $type];
}
@@ -175,6 +178,7 @@ class QuerySearchHelper {
'mimetype' => 'string',
'mtime' => 'integer',
'name' => 'string',
+ 'path' => 'string',
'size' => 'integer',
'tagname' => 'string',
'favorite' => 'boolean',
@@ -184,6 +188,7 @@ class QuerySearchHelper {
'mimetype' => ['eq', 'like'],
'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'],
'name' => ['eq', 'like'],
+ 'path' => ['eq', 'like'],
'size' => ['eq', 'gt', 'lt', 'gte', 'lte'],
'tagname' => ['eq', 'like'],
'favorite' => ['eq'],
diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php
index 6c1c17be028..fe3124301ba 100644
--- a/lib/private/Files/Cache/Wrapper/CacheJail.php
+++ b/lib/private/Files/Cache/Wrapper/CacheJail.php
@@ -29,8 +29,13 @@
namespace OC\Files\Cache\Wrapper;
use OC\Files\Cache\Cache;
+use OC\Files\Search\SearchBinaryOperator;
+use OC\Files\Search\SearchComparison;
use OC\Files\Search\SearchQuery;
+use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Cache\ICacheEntry;
+use OCP\Files\Search\ISearchBinaryOperator;
+use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchQuery;
/**
@@ -41,6 +46,7 @@ class CacheJail extends CacheWrapper {
* @var string
*/
protected $root;
+ protected $unjailedRoot;
/**
* @param \OCP\Files\Cache\ICache $cache
@@ -49,12 +55,29 @@ class CacheJail extends CacheWrapper {
public function __construct($cache, $root) {
parent::__construct($cache);
$this->root = $root;
+ $this->connection = \OC::$server->getDatabaseConnection();
+ $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
+
+ if ($cache instanceof CacheJail) {
+ $this->unjailedRoot = $cache->getSourcePath($root);
+ } else {
+ $this->unjailedRoot = $root;
+ }
}
protected function getRoot() {
return $this->root;
}
+ /**
+ * Get the root path with any nested jails resolved
+ *
+ * @return string
+ */
+ protected function getGetUnjailedRoot() {
+ return $this->unjailedRoot;
+ }
+
protected function getSourcePath($path) {
if ($path === '') {
return $this->getRoot();
@@ -65,16 +88,20 @@ class CacheJail extends CacheWrapper {
/**
* @param string $path
+ * @param null|string $root
* @return null|string the jailed path or null if the path is outside the jail
*/
- protected function getJailedPath($path) {
- if ($this->getRoot() === '') {
+ protected function getJailedPath(string $path, string $root = null) {
+ if ($root === null) {
+ $root = $this->getRoot();
+ }
+ if ($root === '') {
return $path;
}
- $rootLength = strlen($this->getRoot()) + 1;
- if ($path === $this->getRoot()) {
+ $rootLength = strlen($root) + 1;
+ if ($path === $root) {
return '';
- } elseif (substr($path, 0, $rootLength) === $this->getRoot() . '/') {
+ } elseif (substr($path, 0, $rootLength) === $root . '/') {
return substr($path, $rootLength);
} else {
return null;
@@ -92,11 +119,6 @@ class CacheJail extends CacheWrapper {
return $entry;
}
- protected function filterCacheEntry($entry) {
- $rootLength = strlen($this->getRoot()) + 1;
- return $rootLength === 1 || ($entry['path'] === $this->getRoot()) || (substr($entry['path'], 0, $rootLength) === $this->getRoot() . '/');
- }
-
/**
* get the stored metadata of a file or folder
*
@@ -209,9 +231,10 @@ class CacheJail extends CacheWrapper {
}
private function formatSearchResults($results) {
- $results = array_filter($results, [$this, 'filterCacheEntry']);
- $results = array_values($results);
- return array_map([$this, 'formatCacheEntry'], $results);
+ return array_map(function ($entry) {
+ $entry['path'] = $this->getJailedPath($entry['path'], $this->getGetUnjailedRoot());
+ return $entry;
+ }, $results);
}
/**
@@ -221,7 +244,29 @@ class CacheJail extends CacheWrapper {
* @return array an array of file data
*/
public function search($pattern) {
- $results = $this->getCache()->search($pattern);
+ // normalize pattern
+ $pattern = $this->normalize($pattern);
+
+ if ($pattern === '%%') {
+ return [];
+ }
+
+ $query = $this->getQueryBuilder();
+ $query->selectFileCache()
+ ->whereStorageId()
+ ->andWhere($query->expr()->orX(
+ $query->expr()->like('path', $query->createNamedParameter($this->getGetUnjailedRoot() . '/%')),
+ $query->expr()->eq('path_hash', $query->createNamedParameter(md5($this->getGetUnjailedRoot()))),
+ ))
+ ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
+
+ $result = $query->execute();
+ $files = $result->fetchAll();
+ $result->closeCursor();
+
+ $results = array_map(function (array $data) {
+ return self::cacheEntryFromData($data, $this->mimetypeLoader);
+ }, $files);
return $this->formatSearchResults($results);
}
@@ -232,12 +277,48 @@ class CacheJail extends CacheWrapper {
* @return array
*/
public function searchByMime($mimetype) {
- $results = $this->getCache()->searchByMime($mimetype);
+ $mimeId = $this->mimetypeLoader->getId($mimetype);
+
+ $query = $this->getQueryBuilder();
+ $query->selectFileCache()
+ ->whereStorageId()
+ ->andWhere($query->expr()->orX(
+ $query->expr()->like('path', $query->createNamedParameter($this->getGetUnjailedRoot() . '/%')),
+ $query->expr()->eq('path_hash', $query->createNamedParameter(md5($this->getGetUnjailedRoot()))),
+ ));
+
+ if (strpos($mimetype, '/')) {
+ $query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
+ } else {
+ $query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
+ }
+
+ $result = $query->execute();
+ $files = $result->fetchAll();
+ $result->closeCursor();
+
+ $results = array_map(function (array $data) {
+ return self::cacheEntryFromData($data, $this->mimetypeLoader);
+ }, $files);
return $this->formatSearchResults($results);
}
public function searchQuery(ISearchQuery $query) {
- $simpleQuery = new SearchQuery($query->getSearchOperation(), 0, 0, $query->getOrder(), $query->getUser());
+ $prefixFilter = new SearchComparison(
+ ISearchComparison::COMPARE_LIKE,
+ 'path',
+ $this->getGetUnjailedRoot() . '/%'
+ );
+ $rootFilter = new SearchComparison(
+ ISearchComparison::COMPARE_EQUAL,
+ 'path',
+ $this->getGetUnjailedRoot()
+ );
+ $operation = new SearchBinaryOperator(
+ ISearchBinaryOperator::OPERATOR_AND,
+ [new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, [$prefixFilter, $rootFilter]) , $query->getSearchOperation()]
+ );
+ $simpleQuery = new SearchQuery($operation, 0, 0, $query->getOrder(), $query->getUser());
$results = $this->getCache()->searchQuery($simpleQuery);
$results = $this->formatSearchResults($results);
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index 3378f00c4dd..5d2cbe61ab6 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -33,12 +33,14 @@ namespace OC\Files\ObjectStore;
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\CountWrapper;
use Icewind\Streams\IteratorDirectory;
+use OC\Files\Cache\Cache;
use OC\Files\Cache\CacheEntry;
use OC\Files\Storage\PolyFill\CopyDirectory;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\FileInfo;
use OCP\Files\NotFoundException;
use OCP\Files\ObjectStore\IObjectStore;
+use OCP\Files\Storage\IStorage;
class ObjectStoreStorage extends \OC\Files\Storage\Common {
use CopyDirectory;
@@ -530,6 +532,19 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
return $this->objectStore;
}
+ public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
+ if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
+ /** @var ObjectStoreStorage $sourceStorage */
+ if ($sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()) {
+ $sourceEntry = $sourceStorage->getCache()->get($sourceInternalPath);
+ $this->copyInner($sourceEntry, $targetInternalPath);
+ return true;
+ }
+ }
+
+ return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+ }
+
public function copy($path1, $path2) {
$path1 = $this->normalizePath($path1);
$path2 = $this->normalizePath($path2);
@@ -567,14 +582,13 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
$sourceUrn = $this->getURN($sourceEntry->getId());
- $cache->copyFromCache($cache, $sourceEntry, $to);
- $targetEntry = $cache->get($to);
-
- if (!$targetEntry) {
- throw new \Exception('Target not in cache after copy');
+ if (!$cache instanceof Cache) {
+ throw new \Exception("Invalid source cache for object store copy");
}
- $targetUrn = $this->getURN($targetEntry->getId());
+ $targetId = $cache->copyFromCache($cache, $sourceEntry, $to);
+
+ $targetUrn = $this->getURN($targetId);
try {
$this->objectStore->copyObject($sourceUrn, $targetUrn);
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index 80b8a6f132d..4d6ac3608df 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -104,8 +104,6 @@ trait S3ObjectTrait {
throw $e;
}
}
-
- fclose($countStream);
}
/**
diff --git a/lib/private/Files/SimpleFS/SimpleFile.php b/lib/private/Files/SimpleFS/SimpleFile.php
index 108f53ee9c7..0d3cbc7b743 100644
--- a/lib/private/Files/SimpleFS/SimpleFile.php
+++ b/lib/private/Files/SimpleFS/SimpleFile.php
@@ -180,4 +180,8 @@ class SimpleFile implements ISimpleFile {
public function write() {
return $this->file->fopen('w');
}
+
+ public function getId(): int {
+ return $this->file->getId();
+ }
}
diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php
index b7933708da8..276f00af33c 100644
--- a/lib/private/Files/Storage/Wrapper/Jail.php
+++ b/lib/private/Files/Storage/Wrapper/Jail.php
@@ -48,7 +48,7 @@ class Jail extends Wrapper {
protected $rootPath;
/**
- * @param array $arguments ['storage' => $storage, 'mask' => $root]
+ * @param array $arguments ['storage' => $storage, 'root' => $root]
*
* $storage: The storage that will be wrapper
* $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index ed7d6c26318..2223858b7a9 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -1643,6 +1643,7 @@ class View {
$mount = $this->getMount('');
$mountPoint = $mount->getMountPoint();
$storage = $mount->getStorage();
+ $userManager = \OC::$server->getUserManager();
if ($storage) {
$cache = $storage->getCache('');
@@ -1652,7 +1653,7 @@ class View {
$internalPath = $result['path'];
$path = $mountPoint . $result['path'];
$result['path'] = substr($mountPoint . $result['path'], $rootLength);
- $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
+ $owner = $userManager->get($storage->getOwner($internalPath));
$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
}
}
@@ -1671,7 +1672,7 @@ class View {
$internalPath = $result['path'];
$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
$path = rtrim($mountPoint . $internalPath, '/');
- $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
+ $owner = $userManager->get($storage->getOwner($internalPath));
$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
}
}
diff --git a/lib/private/InitialStateService.php b/lib/private/InitialStateService.php
index bbab831b915..bbc893269f7 100644
--- a/lib/private/InitialStateService.php
+++ b/lib/private/InitialStateService.php
@@ -69,7 +69,7 @@ class InitialStateService implements IInitialStateService {
return;
}
- $this->logger->warning('Invalid data provided to provideInitialState by ' . $appName);
+ $this->logger->warning('Invalid '. $key . ' data provided to provideInitialState by ' . $appName);
}
public function provideLazyInitialState(string $appName, string $key, Closure $closure): void {
diff --git a/lib/private/KnownUser/KnownUser.php b/lib/private/KnownUser/KnownUser.php
new file mode 100644
index 00000000000..939c9199c79
--- /dev/null
+++ b/lib/private/KnownUser/KnownUser.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Joas Schilling <coding@schilljs.com>
+ *
+ * @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\KnownUser;
+
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * @method void setKnownTo(string $knownTo)
+ * @method string getKnownTo()
+ * @method void setKnownUser(string $knownUser)
+ * @method string getKnownUser()
+ */
+class KnownUser extends Entity {
+
+ /** @var string */
+ protected $knownTo;
+
+ /** @var string */
+ protected $knownUser;
+
+ public function __construct() {
+ $this->addType('knownTo', 'string');
+ $this->addType('knownUser', 'string');
+ }
+}
diff --git a/lib/private/KnownUser/KnownUserMapper.php b/lib/private/KnownUser/KnownUserMapper.php
new file mode 100644
index 00000000000..e77e4752702
--- /dev/null
+++ b/lib/private/KnownUser/KnownUserMapper.php
@@ -0,0 +1,87 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Joas Schilling <coding@schilljs.com>
+ *
+ * @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\KnownUser;
+
+use OCP\AppFramework\Db\QBMapper;
+use OCP\IDBConnection;
+
+/**
+ * @method KnownUser mapRowToEntity(array $row)
+ */
+class KnownUserMapper extends QBMapper {
+
+ /**
+ * @param IDBConnection $db
+ */
+ public function __construct(IDBConnection $db) {
+ parent::__construct($db, 'known_users', KnownUser::class);
+ }
+
+ /**
+ * @param string $knownTo
+ * @return int Number of deleted entities
+ */
+ public function deleteKnownTo(string $knownTo): int {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->eq('known_to', $query->createNamedParameter($knownTo)));
+
+ return (int) $query->execute();
+ }
+
+ /**
+ * @param string $knownUser
+ * @return int Number of deleted entities
+ */
+ public function deleteKnownUser(string $knownUser): int {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->eq('known_user', $query->createNamedParameter($knownUser)));
+
+ return (int) $query->execute();
+ }
+
+ /**
+ * Returns all "known users" for the given "known to" user
+ *
+ * @param string $knownTo
+ * @return KnownUser[]
+ */
+ public function getKnownUsers(string $knownTo): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('known_to', $query->createNamedParameter($knownTo)));
+
+ return $this->findEntities($query);
+ }
+
+ public function createKnownUserFromRow(array $row): KnownUser {
+ return $this->mapRowToEntity([
+ 'id' => $row['s_id'],
+ 'known_to' => $row['known_to'],
+ 'known_user' => $row['known_user'],
+ ]);
+ }
+}
diff --git a/lib/private/KnownUser/KnownUserService.php b/lib/private/KnownUser/KnownUserService.php
new file mode 100644
index 00000000000..96af21c836f
--- /dev/null
+++ b/lib/private/KnownUser/KnownUserService.php
@@ -0,0 +1,87 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Joas Schilling <coding@schilljs.com>
+ *
+ * @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\KnownUser;
+
+class KnownUserService {
+ /** @var KnownUserMapper */
+ protected $mapper;
+ /** @var array */
+ protected $knownUsers = [];
+
+ public function __construct(KnownUserMapper $mapper) {
+ $this->mapper = $mapper;
+ }
+
+ /**
+ * Delete all matches where the given user is the owner of the phonebook
+ *
+ * @param string $knownTo
+ * @return int Number of deleted matches
+ */
+ public function deleteKnownTo(string $knownTo): int {
+ return $this->mapper->deleteKnownTo($knownTo);
+ }
+
+ /**
+ * Delete all matches where the given user is the one in the phonebook
+ *
+ * @param string $contactUserId
+ * @return int Number of deleted matches
+ */
+ public function deleteByContactUserId(string $contactUserId): int {
+ return $this->mapper->deleteKnownUser($contactUserId);
+ }
+
+ /**
+ * Store a match because $knownTo has $contactUserId in his phonebook
+ *
+ * @param string $knownTo User id of the owner of the phonebook
+ * @param string $contactUserId User id of the contact in the phonebook
+ */
+ public function storeIsKnownToUser(string $knownTo, string $contactUserId): void {
+ $entity = new KnownUser();
+ $entity->setKnownTo($knownTo);
+ $entity->setKnownUser($contactUserId);
+ $this->mapper->insert($entity);
+ }
+
+ /**
+ * Check if $contactUserId is in the phonebook of $knownTo
+ *
+ * @param string $knownTo User id of the owner of the phonebook
+ * @param string $contactUserId User id of the contact in the phonebook
+ * @return bool
+ */
+ public function isKnownToUser(string $knownTo, string $contactUserId): bool {
+ if (!isset($this->knownUsers[$knownTo])) {
+ $entities = $this->mapper->getKnownUsers($knownTo);
+ $this->knownUsers[$knownTo] = [];
+ foreach ($entities as $entity) {
+ $this->knownUsers[$knownTo][$entity->getKnownUser()] = true;
+ }
+ }
+
+ return isset($this->knownUsers[$knownTo][$contactUserId]);
+ }
+}
diff --git a/lib/private/Lockdown/Filesystem/NullCache.php b/lib/private/Lockdown/Filesystem/NullCache.php
index 396bf68d5df..267b76ab8fb 100644
--- a/lib/private/Lockdown/Filesystem/NullCache.php
+++ b/lib/private/Lockdown/Filesystem/NullCache.php
@@ -26,6 +26,7 @@ namespace OC\Lockdown\Filesystem;
use OC\Files\Cache\CacheEntry;
use OCP\Constants;
use OCP\Files\Cache\ICache;
+use OCP\Files\Cache\ICacheEntry;
use OCP\Files\FileInfo;
use OCP\Files\Search\ISearchQuery;
@@ -122,4 +123,8 @@ class NullCache implements ICache {
public function normalize($path) {
return $path;
}
+
+ public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
+ throw new \OC\ForbiddenException('This request is not allowed to access the filesystem');
+ }
}
diff --git a/lib/private/Search/Provider/File.php b/lib/private/Search/Provider/File.php
index b4e35d374ca..4125b1f7d70 100644
--- a/lib/private/Search/Provider/File.php
+++ b/lib/private/Search/Provider/File.php
@@ -30,23 +30,33 @@
namespace OC\Search\Provider;
use OC\Files\Filesystem;
+use OCP\Search\PagedProvider;
/**
* Provide search results from the 'files' app
* @deprecated 20.0.0
*/
-class File extends \OCP\Search\Provider {
+class File extends PagedProvider {
/**
* Search for files and folders matching the given query
+ *
* @param string $query
+ * @param int|null $limit
+ * @param int|null $offset
* @return \OCP\Search\Result[]
* @deprecated 20.0.0
*/
- public function search($query) {
+ public function search($query, int $limit = null, int $offset = null) {
+ if ($offset === null) {
+ $offset = 0;
+ }
\OC_Util::setupFS();
$files = Filesystem::search($query);
$results = [];
+ if ($limit !== null) {
+ $files = array_slice($files, $offset, $offset + $limit);
+ }
// edit results
foreach ($files as $fileData) {
// skip versions
@@ -79,4 +89,12 @@ class File extends \OCP\Search\Provider {
// return
return $results;
}
+
+ public function searchPaged($query, $page, $size) {
+ if ($size === 0) {
+ return $this->search($query);
+ } else {
+ return $this->search($query, $size, ($page - 1) * $size);
+ }
+ }
}
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index 5c8dba5915a..ce1ec1d60f6 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -1829,6 +1829,15 @@ class Manager implements IManager {
$this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
}
+ public function limitEnumerationToPhone(): bool {
+ return $this->allowEnumeration() &&
+ $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
+ }
+
+ public function allowEnumerationFullMatch(): bool {
+ return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
+ }
+
/**
* Copied from \OC_Util::isSharingDisabledForUser
*
diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php
index 69f36edf4f3..87f77701ee2 100644
--- a/lib/private/Share20/Share.php
+++ b/lib/private/Share20/Share.php
@@ -30,8 +30,9 @@
namespace OC\Share20;
-use OCP\Files\Cache\ICacheEntry;
use OCP\Files\File;
+use OCP\Files\Cache\ICacheEntry;
+use OCP\Files\FileInfo;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
@@ -233,8 +234,13 @@ class Share implements \OCP\Share\IShare {
*/
public function getNodeType() {
if ($this->nodeType === null) {
- $node = $this->getNode();
- $this->nodeType = $node instanceof File ? 'file' : 'folder';
+ if ($this->getNodeCacheEntry()) {
+ $info = $this->getNodeCacheEntry();
+ $this->nodeType = $info->getMimeType() === FileInfo::MIMETYPE_FOLDER ? 'folder' : 'file';
+ } else {
+ $node = $this->getNode();
+ $this->nodeType = $node instanceof File ? 'file' : 'folder';
+ }
}
return $this->nodeType;
diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php
index f0de31568f4..6c68f5d805f 100644
--- a/lib/private/URLGenerator.php
+++ b/lib/private/URLGenerator.php
@@ -59,6 +59,8 @@ class URLGenerator implements IURLGenerator {
private $request;
/** @var Router */
private $router;
+ /** @var null|string */
+ private $baseUrl = null;
public function __construct(IConfig $config,
ICacheFactory $cacheFactory,
@@ -269,6 +271,9 @@ class URLGenerator implements IURLGenerator {
* @return string base url of the current request
*/
public function getBaseUrl(): string {
- return $this->request->getServerProtocol() . '://' . $this->request->getServerHost() . \OC::$WEBROOT;
+ if ($this->baseUrl === null) {
+ $this->baseUrl = $this->request->getServerProtocol() . '://' . $this->request->getServerHost() . \OC::$WEBROOT;
+ }
+ return $this->baseUrl;
}
}
diff --git a/lib/private/User/Backend.php b/lib/private/User/Backend.php
index c87dc5d2d50..d70d13673cb 100644
--- a/lib/private/User/Backend.php
+++ b/lib/private/User/Backend.php
@@ -143,8 +143,8 @@ abstract class Backend implements UserInterface {
* Get a list of all display names and user ids.
*
* @param string $search
- * @param string|null $limit
- * @param string|null $offset
+ * @param int|null $limit
+ * @param int|null $offset
* @return array an array of all displayNames (value) and the corresponding uids (key)
*/
public function getDisplayNames($search = '', $limit = null, $offset = null) {
diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php
index 85e22d196e4..6c04a1b900e 100644
--- a/lib/private/User/Database.php
+++ b/lib/private/User/Database.php
@@ -70,6 +70,7 @@ use OCP\User\Backend\ICreateUserBackend;
use OCP\User\Backend\IGetDisplayNameBackend;
use OCP\User\Backend\IGetHomeBackend;
use OCP\User\Backend\IGetRealUIDBackend;
+use OCP\User\Backend\ISearchKnownUsersBackend;
use OCP\User\Backend\ISetDisplayNameBackend;
use OCP\User\Backend\ISetPasswordBackend;
@@ -84,6 +85,7 @@ class Database extends ABackend implements
ICheckPasswordBackend,
IGetHomeBackend,
ICountUsersBackend,
+ ISearchKnownUsersBackend,
IGetRealUIDBackend {
/** @var CappedMemoryCache */
private $cache;
@@ -254,8 +256,8 @@ class Database extends ABackend implements
* Get a list of all display names and user ids.
*
* @param string $search
- * @param string|null $limit
- * @param string|null $offset
+ * @param int|null $limit
+ * @param int|null $offset
* @return array an array of all displayNames (value) and the corresponding uids (key)
*/
public function getDisplayNames($search = '', $limit = null, $offset = null) {
@@ -277,7 +279,47 @@ class Database extends ABackend implements
->orWhere($query->expr()->iLike('displayname', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
->orWhere($query->expr()->iLike('configvalue', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
->orderBy($query->func()->lower('displayname'), 'ASC')
- ->orderBy('uid_lower', 'ASC')
+ ->addOrderBy('uid_lower', 'ASC')
+ ->setMaxResults($limit)
+ ->setFirstResult($offset);
+
+ $result = $query->execute();
+ $displayNames = [];
+ while ($row = $result->fetch()) {
+ $displayNames[(string)$row['uid']] = (string)$row['displayname'];
+ }
+
+ return $displayNames;
+ }
+
+ /**
+ * @param string $searcher
+ * @param string $pattern
+ * @param int|null $limit
+ * @param int|null $offset
+ * @return array
+ * @since 21.0.1
+ */
+ public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
+ $limit = $this->fixLimit($limit);
+
+ $this->fixDI();
+
+ $query = $this->dbConn->getQueryBuilder();
+
+ $query->select('u.uid', 'u.displayname')
+ ->from($this->table, 'u')
+ ->leftJoin('u', 'known_users', 'k', $query->expr()->andX(
+ $query->expr()->eq('k.known_user', 'u.uid'),
+ $query->expr()->eq('k.known_to', $query->createNamedParameter($searcher))
+ ))
+ ->where($query->expr()->eq('k.known_to', $query->createNamedParameter($searcher)))
+ ->andWhere($query->expr()->orX(
+ $query->expr()->iLike('u.uid', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($pattern) . '%')),
+ $query->expr()->iLike('u.displayname', $query->createNamedParameter('%' . $this->dbConn->escapeLikeParameter($pattern) . '%'))
+ ))
+ ->orderBy('u.displayname', 'ASC')
+ ->addOrderBy('u.uid_lower', 'ASC')
->setMaxResults($limit)
->setFirstResult($offset);
diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php
index 036d2703d35..59c007b6b59 100644
--- a/lib/private/User/Manager.php
+++ b/lib/private/User/Manager.php
@@ -47,6 +47,7 @@ use OCP\IUserBackend;
use OCP\IUserManager;
use OCP\Support\Subscription\IRegistry;
use OCP\User\Backend\IGetRealUIDBackend;
+use OCP\User\Backend\ISearchKnownUsersBackend;
use OCP\User\Events\BeforeUserCreatedEvent;
use OCP\User\Events\UserCreatedEvent;
use OCP\UserInterface;
@@ -330,6 +331,41 @@ class Manager extends PublicEmitter implements IUserManager {
}
/**
+ * Search known users (from phonebook sync) by displayName
+ *
+ * @param string $searcher
+ * @param string $pattern
+ * @param int|null $limit
+ * @param int|null $offset
+ * @return IUser[]
+ */
+ public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
+ $users = [];
+ foreach ($this->backends as $backend) {
+ if ($backend instanceof ISearchKnownUsersBackend) {
+ $backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
+ } else {
+ // Better than nothing, but filtering after pagination can remove lots of results.
+ $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
+ }
+ if (is_array($backendUsers)) {
+ foreach ($backendUsers as $uid => $displayName) {
+ $users[] = $this->getUserObject($uid, $backend);
+ }
+ }
+ }
+
+ usort($users, function ($a, $b) {
+ /**
+ * @var IUser $a
+ * @var IUser $b
+ */
+ return strcasecmp($a->getDisplayName(), $b->getDisplayName());
+ });
+ return $users;
+ }
+
+ /**
* @param string $uid
* @param string $password
* @throws \InvalidArgumentException
@@ -681,6 +717,7 @@ class Manager extends PublicEmitter implements IUserManager {
if (\in_array($uid, [
'.htaccess',
'files_external',
+ '__groupfolders',
'.ocdata',
'owncloud.log',
'nextcloud.log',
diff --git a/lib/public/Files/Cache/ICache.php b/lib/public/Files/Cache/ICache.php
index 95ca29c2aa8..323359dcf68 100644
--- a/lib/public/Files/Cache/ICache.php
+++ b/lib/public/Files/Cache/ICache.php
@@ -180,6 +180,17 @@ interface ICache {
public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath);
/**
+ * Copy a file or folder in the cache
+ *
+ * @param ICache $sourceCache
+ * @param ICacheEntry $sourceEntry
+ * @param string $targetPath
+ * @return int fileid of copied entry
+ * @since 22.0.0
+ */
+ public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int;
+
+ /**
* Get the scan status of a file
*
* - ICache::NOT_FOUND: File is not in the cache
diff --git a/lib/public/IUserManager.php b/lib/public/IUserManager.php
index e8a7fc7827d..6963bb5ddbc 100644
--- a/lib/public/IUserManager.php
+++ b/lib/public/IUserManager.php
@@ -127,6 +127,18 @@ interface IUserManager {
public function searchDisplayName($pattern, $limit = null, $offset = null);
/**
+ * Search known users (from phonebook sync) by displayName
+ *
+ * @param string $searcher
+ * @param string $pattern
+ * @param int|null $limit
+ * @param int|null $offset
+ * @return IUser[]
+ * @since 21.0.1
+ */
+ public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
+
+ /**
* @param string $uid
* @param string $password
* @throws \InvalidArgumentException
diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php
index 635ccc1483d..606e6429918 100644
--- a/lib/public/Share/IManager.php
+++ b/lib/public/Share/IManager.php
@@ -385,6 +385,22 @@ interface IManager {
public function limitEnumerationToGroups(): bool;
/**
+ * Check if user enumeration is limited to the phonebook matches
+ *
+ * @return bool
+ * @since 21.0.1
+ */
+ public function limitEnumerationToPhone(): bool;
+
+ /**
+ * Check if user enumeration is allowed to return on full match
+ *
+ * @return bool
+ * @since 21.0.1
+ */
+ public function allowEnumerationFullMatch(): bool;
+
+ /**
* Check if sharing is disabled for the given user
*
* @param string $userId
diff --git a/lib/public/User/Backend/IGetRealUIDBackend.php b/lib/public/User/Backend/IGetRealUIDBackend.php
index d724426e176..cc290eb6dc0 100644
--- a/lib/public/User/Backend/IGetRealUIDBackend.php
+++ b/lib/public/User/Backend/IGetRealUIDBackend.php
@@ -33,7 +33,7 @@ interface IGetRealUIDBackend {
/**
* Some backends accept different UIDs than what is the internal UID to be used.
- * For example the database backend accepts differnt cased UIDs in all the functions
+ * For example the database backend accepts different cased UIDs in all the functions
* but the internal UID that is to be used should be correctly cased.
*
* This little function makes sure that the used UID will be correct hen using the user object
diff --git a/lib/public/User/Backend/ISearchKnownUsersBackend.php b/lib/public/User/Backend/ISearchKnownUsersBackend.php
new file mode 100644
index 00000000000..89c7a49cd30
--- /dev/null
+++ b/lib/public/User/Backend/ISearchKnownUsersBackend.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2021 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\User\Backend;
+
+/**
+ * @since 21.0.1
+ */
+interface ISearchKnownUsersBackend {
+
+ /**
+ * @param string $searcher
+ * @param string $pattern
+ * @param int|null $limit
+ * @param int|null $offset
+ * @return array
+ * @since 21.0.1
+ */
+ public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
+}
diff --git a/lib/public/UserInterface.php b/lib/public/UserInterface.php
index 0479041e8de..42a18cca103 100644
--- a/lib/public/UserInterface.php
+++ b/lib/public/UserInterface.php
@@ -92,8 +92,8 @@ interface UserInterface {
* Get a list of all display names and user ids.
*
* @param string $search
- * @param string|null $limit
- * @param string|null $offset
+ * @param int|null $limit
+ * @param int|null $offset
* @return array an array of all displayNames (value) and the corresponding uids (key)
* @since 4.5.0
*/