diff options
Diffstat (limited to 'lib/private')
78 files changed, 1417 insertions, 885 deletions
diff --git a/lib/private/Accounts/Account.php b/lib/private/Accounts/Account.php index 540d15cd4b9..d3287b219d0 100644 --- a/lib/private/Accounts/Account.php +++ b/lib/private/Accounts/Account.php @@ -72,6 +72,28 @@ class Account implements IAccount { }); } + public function setAllPropertiesFromJson(array $properties): IAccount { + foreach ($properties as $propertyName => $propertyObject) { + if ($this->isCollection($propertyName)) { + $collection = new AccountPropertyCollection($propertyName); + /** @var array<int, IAccountProperty> $collectionProperties */ + $collectionProperties = []; + /** @var array<int, array<string, string>> $propertyObject */ + foreach ($propertyObject as ['value' => $value, 'scope' => $scope, 'verified' => $verified, 'verificationData' => $verificationData]) { + $collectionProperties[] = new AccountProperty($collection->getName(), $value, $scope, $verified, $verificationData); + } + $collection->setProperties($collectionProperties); + $this->setPropertyCollection($collection); + } else { + /** @var array<string, string> $propertyObject */ + ['value' => $value, 'scope' => $scope, 'verified' => $verified, 'verificationData' => $verificationData] = $propertyObject; + $this->setProperty($propertyName, $value, $scope, $verified, $verificationData); + } + } + + return $this; + } + public function getAllProperties(): Generator { foreach ($this->properties as $propertyObject) { if ($propertyObject instanceof IAccountProperty) { @@ -104,9 +126,16 @@ class Account implements IAccount { return $result; } - /** @return IAccountPropertyCollection[]|IAccountProperty[] */ + /** @return array<string, IAccountProperty|array<int, IAccountProperty>> */ public function jsonSerialize(): array { - return $this->properties; + $properties = $this->properties; + foreach ($properties as $propertyName => $propertyObject) { + if ($propertyObject instanceof IAccountPropertyCollection) { + // Override collection serialization to discard duplicate name + $properties[$propertyName] = $propertyObject->jsonSerialize()[$propertyName]; + } + } + return $properties; } public function getUser(): IUser { diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index 7f855c07d46..127adc9ef38 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -40,6 +40,7 @@ use libphonenumber\NumberParseException; use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; +use OC\Profile\TProfileHelper; use OCA\Settings\BackgroundJobs\VerifyUserData; use OCP\Accounts\IAccount; use OCP\Accounts\IAccountManager; @@ -79,6 +80,8 @@ use function json_last_error; class AccountManager implements IAccountManager { use TAccountsHelper; + use TProfileHelper; + /** @var IDBConnection database connection */ private $connection; @@ -722,7 +725,7 @@ class AccountManager implements IAccountManager { [ 'name' => self::PROPERTY_PROFILE_ENABLED, - 'value' => '1', + 'value' => $this->isProfileEnabledByDefault($this->config) ? '1' : '0', ], ]; } diff --git a/lib/private/App/AppStore/Fetcher/AppFetcher.php b/lib/private/App/AppStore/Fetcher/AppFetcher.php index 6c201978e1f..579f350b5bb 100644 --- a/lib/private/App/AppStore/Fetcher/AppFetcher.php +++ b/lib/private/App/AppStore/Fetcher/AppFetcher.php @@ -90,8 +90,8 @@ class AppFetcher extends Fetcher { return []; } - $allowPreReleases = $allowUnstable || $this->getChannel() === 'beta' || $this->getChannel() === 'daily'; - $allowNightly = $allowUnstable || $this->getChannel() === 'daily'; + $allowPreReleases = $allowUnstable || $this->getChannel() === 'beta' || $this->getChannel() === 'daily' || $this->getChannel() === 'git'; + $allowNightly = $allowUnstable || $this->getChannel() === 'daily' || $this->getChannel() === 'git'; foreach ($response['data'] as $dataKey => $app) { $releases = []; @@ -183,7 +183,9 @@ class AppFetcher extends Fetcher { public function get($allowUnstable = false) { - $apps = parent::get($allowUnstable); + $allowPreReleases = $allowUnstable || $this->getChannel() === 'beta' || $this->getChannel() === 'daily' || $this->getChannel() === 'git'; + + $apps = parent::get($allowPreReleases); $allowList = $this->config->getSystemValue('appsallowlist'); // If the admin specified a allow list, filter apps from the appstore diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index e2d6d49a845..1324d7f3056 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -45,17 +45,84 @@ class AppConfig implements IAppConfig { /** @var array[] */ protected $sensitiveValues = [ + 'circles' => [ + '/^local_gskey$/', + ], 'external' => [ '/^sites$/', ], + 'integration_discourse' => [ + '/^private_key$/', + '/^public_key$/', + ], + 'integration_dropbox' => [ + '/^client_id$/', + '/^client_secret$/', + ], + 'integration_github' => [ + '/^client_id$/', + '/^client_secret$/', + ], + 'integration_gitlab' => [ + '/^client_id$/', + '/^client_secret$/', + '/^oauth_instance_url$/', + ], + 'integration_google' => [ + '/^client_id$/', + '/^client_secret$/', + ], + 'integration_jira' => [ + '/^client_id$/', + '/^client_secret$/', + '/^forced_instance_url$/', + ], + 'integration_onedrive' => [ + '/^client_id$/', + '/^client_secret$/', + ], + 'integration_openproject' => [ + '/^client_id$/', + '/^client_secret$/', + '/^oauth_instance_url$/', + ], + 'integration_reddit' => [ + '/^client_id$/', + '/^client_secret$/', + ], + 'integration_suitecrm' => [ + '/^client_id$/', + '/^client_secret$/', + '/^oauth_instance_url$/', + ], + 'integration_twitter' => [ + '/^consumer_key$/', + '/^consumer_secret$/', + '/^followed_user$/', + ], + 'integration_zammad' => [ + '/^client_id$/', + '/^client_secret$/', + '/^oauth_instance_url$/', + ], + 'notify_push' => [ + '/^cookie$/', + ], 'spreed' => [ '/^bridge_bot_password/', '/^signaling_servers$/', '/^signaling_ticket_secret$/', + '/^sip_bridge_dialin_info$/', + '/^sip_bridge_shared_secret$/', '/^stun_servers$/', '/^turn_servers$/', '/^turn_server_secret$/', ], + 'support' => [ + '/^last_response$/', + '/^potential_subscription_key$/', + '/^subscription_key$/', + ], 'theming' => [ '/^imprintUrl$/', '/^privacyUrl$/', diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index 6e05b7fdc88..b6378830732 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -30,6 +30,7 @@ declare(strict_types=1); namespace OC\AppFramework\Bootstrap; +use OCP\Diagnostics\IEventLogger; use function class_exists; use function class_implements; use function in_array; @@ -58,6 +59,9 @@ class Coordinator { /** @var IEventDispatcher */ private $eventDispatcher; + /** @var IEventLogger */ + private $eventLogger; + /** @var LoggerInterface */ private $logger; @@ -72,12 +76,14 @@ class Coordinator { Registry $registry, IManager $dashboardManager, IEventDispatcher $eventListener, + IEventLogger $eventLogger, LoggerInterface $logger ) { $this->serverContainer = $container; $this->registry = $registry; $this->dashboardManager = $dashboardManager; $this->eventDispatcher = $eventListener; + $this->eventLogger = $eventLogger; $this->logger = $logger; } @@ -126,7 +132,9 @@ class Coordinator { continue; } + $this->eventLogger->start('bootstrap:register_app_' . $appId, ''); $application->register($this->registrationContext->for($appId)); + $this->eventLogger->end('bootstrap:register_app_' . $appId); } } catch (Throwable $e) { $this->logger->emergency('Error during app service registration: ' . $e->getMessage(), [ @@ -172,6 +180,7 @@ class Coordinator { * the instance was already created for register, but any other * (legacy) code will now do their magic via the constructor. */ + $this->eventLogger->start('bootstrap:boot_app_' . $appId, ''); try { /** @var App $application */ $application = $this->serverContainer->query($applicationClassName); @@ -189,6 +198,7 @@ class Coordinator { 'exception' => $e, ]); } + $this->eventLogger->end('bootstrap:boot_app_' . $appId); } public function isBootable(string $appId) { diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 293b9e47b25..e06d5226a28 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -46,6 +46,7 @@ use OC\AppFramework\Middleware\SessionMiddleware; use OC\AppFramework\ScopedPsrLogger; use OC\AppFramework\Utility\SimpleContainer; use OC\Core\Middleware\TwoFactorMiddleware; +use OC\Diagnostics\EventLogger; use OC\Log\PsrLoggerAdapter; use OC\ServerContainer; use OC\Settings\AuthorizedGroupMapper; @@ -184,7 +185,8 @@ class DIContainer extends SimpleContainer implements IAppContainer { $c->get(IRequest::class), $c->get(IConfig::class), $c->get(IDBConnection::class), - $c->get(LoggerInterface::class) + $c->get(LoggerInterface::class), + $c->get(EventLogger::class) ); }); diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php index 0f12dbda629..21d61bc95aa 100644 --- a/lib/private/AppFramework/Http/Dispatcher.php +++ b/lib/private/AppFramework/Http/Dispatcher.php @@ -39,6 +39,7 @@ use OC\DB\ConnectionAdapter; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\Response; +use OCP\Diagnostics\IEventLogger; use OCP\IConfig; use OCP\IRequest; use Psr\Log\LoggerInterface; @@ -69,6 +70,9 @@ class Dispatcher { /** @var LoggerInterface */ private $logger; + /** @var IEventLogger */ + private $eventLogger; + /** * @param Http $protocol the http protocol with contains all status headers * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which @@ -79,6 +83,7 @@ class Dispatcher { * @param IConfig $config * @param ConnectionAdapter $connection * @param LoggerInterface $logger + * @param IEventLogger $eventLogger */ public function __construct(Http $protocol, MiddlewareDispatcher $middlewareDispatcher, @@ -86,7 +91,8 @@ class Dispatcher { IRequest $request, IConfig $config, ConnectionAdapter $connection, - LoggerInterface $logger) { + LoggerInterface $logger, + IEventLogger $eventLogger) { $this->protocol = $protocol; $this->middlewareDispatcher = $middlewareDispatcher; $this->reflector = $reflector; @@ -94,6 +100,7 @@ class Dispatcher { $this->config = $config; $this->connection = $connection; $this->logger = $logger; + $this->eventLogger = $eventLogger; } @@ -214,7 +221,9 @@ class Dispatcher { $arguments[] = $value; } + $this->eventLogger->start('controller:' . get_class($controller) . '::' . $methodName, 'App framework controller execution'); $response = \call_user_func_array([$controller, $methodName], $arguments); + $this->eventLogger->end('controller:' . get_class($controller) . '::' . $methodName); // format response if ($response instanceof DataResponse || !($response instanceof Response)) { diff --git a/lib/private/Archive/Archive.php b/lib/private/Archive/Archive.php index 141db11f419..cef306230fd 100644 --- a/lib/private/Archive/Archive.php +++ b/lib/private/Archive/Archive.php @@ -30,97 +30,84 @@ namespace OC\Archive; abstract class Archive { - /** - * @param $source - */ - abstract public function __construct($source); + abstract public function __construct(string $source); + /** * add an empty folder to the archive - * @param string $path - * @return bool */ - abstract public function addFolder($path); + abstract public function addFolder(string $path): bool; + /** * add a file to the archive - * @param string $path * @param string $source either a local file or string data - * @return bool */ - abstract public function addFile($path, $source = ''); + abstract public function addFile(string $path, string $source = ''): bool; + /** * rename a file or folder in the archive - * @param string $source - * @param string $dest - * @return bool */ - abstract public function rename($source, $dest); + abstract public function rename(string $source, string $dest): bool; + /** * get the uncompressed size of a file in the archive - * @param string $path - * @return int + * @return int|false */ - abstract public function filesize($path); + abstract public function filesize(string $path); + /** * get the last modified time of a file in the archive - * @param string $path - * @return int + * @return int|false */ - abstract public function mtime($path); + abstract public function mtime(string $path); + /** * get the files in a folder * @param string $path * @return array */ - abstract public function getFolder($path); + abstract public function getFolder(string $path): array; + /** * get all files in the archive - * @return array */ - abstract public function getFiles(); + abstract public function getFiles(): array; + /** * get the content of a file - * @param string $path - * @return string + * @return string|false */ - abstract public function getFile($path); + abstract public function getFile(string $path); + /** * extract a single file from the archive - * @param string $path - * @param string $dest - * @return bool */ - abstract public function extractFile($path, $dest); + abstract public function extractFile(string $path, string $dest): bool; + /** * extract the archive - * @param string $dest - * @return bool */ - abstract public function extract($dest); + abstract public function extract(string $dest): bool; + /** * check if a file or folder exists in the archive - * @param string $path - * @return bool */ - abstract public function fileExists($path); + abstract public function fileExists(string $path): bool; + /** * remove a file or folder from the archive - * @param string $path - * @return bool */ - abstract public function remove($path); + abstract public function remove(string $path): bool; + /** * get a file handler - * @param string $path - * @param string $mode * @return bool|resource */ - abstract public function getStream($path, $mode); + abstract public function getStream(string $path, string $mode); + /** * add a folder and all its content - * @param string $path - * @param string $source */ - public function addRecursive($path, $source) { + public function addRecursive(string $path, string $source): void { $dh = opendir($source); if (is_resource($dh)) { $this->addFolder($path); diff --git a/lib/private/Archive/TAR.php b/lib/private/Archive/TAR.php index a024ce84b2f..79c09cbe9e2 100644 --- a/lib/private/Archive/TAR.php +++ b/lib/private/Archive/TAR.php @@ -39,19 +39,26 @@ class TAR extends Archive { public const GZIP = 1; public const BZIP = 2; - private $fileList; - private $cachedHeaders; + /** + * @var string[]|false + */ + private $fileList = false; + /** + * @var array|false + */ + private $cachedHeaders = false; /** - * @var \Archive_Tar tar + * @var \Archive_Tar */ private $tar = null; - private $path; /** - * @param string $source + * @var string */ - public function __construct($source) { + private $path; + + public function __construct(string $source) { $types = [null, 'gz', 'bz2']; $this->path = $source; $this->tar = new \Archive_Tar($source, $types[self::getTarType($source)]); @@ -59,11 +66,8 @@ class TAR extends Archive { /** * try to detect the type of tar compression - * - * @param string $file - * @return integer */ - public static function getTarType($file) { + public static function getTarType(string $file): int { if (strpos($file, '.')) { $extension = substr($file, strrpos($file, '.')); switch ($extension) { @@ -85,11 +89,8 @@ class TAR extends Archive { /** * add an empty folder to the archive - * - * @param string $path - * @return bool */ - public function addFolder($path) { + public function addFolder(string $path): bool { $tmpBase = \OC::$server->getTempManager()->getTemporaryFolder(); $path = rtrim($path, '/') . '/'; if ($this->fileExists($path)) { @@ -113,11 +114,9 @@ class TAR extends Archive { /** * add a file to the archive * - * @param string $path * @param string $source either a local file or string data - * @return bool */ - public function addFile($path, $source = '') { + public function addFile(string $path, string $source = ''): bool { if ($this->fileExists($path)) { $this->remove($path); } @@ -132,12 +131,8 @@ class TAR extends Archive { /** * rename a file or folder in the archive - * - * @param string $source - * @param string $dest - * @return bool */ - public function rename($source, $dest) { + public function rename(string $source, string $dest): bool { //no proper way to delete, rename entire archive, rename file and remake archive $tmp = \OC::$server->getTempManager()->getTemporaryFolder(); $this->tar->extract($tmp); @@ -152,10 +147,7 @@ class TAR extends Archive { return true; } - /** - * @param string $file - */ - private function getHeader($file) { + private function getHeader(string $file): ?array { if (!$this->cachedHeaders) { $this->cachedHeaders = $this->tar->listContent(); } @@ -174,32 +166,27 @@ class TAR extends Archive { /** * get the uncompressed size of a file in the archive * - * @param string $path - * @return int + * @return int|false */ - public function filesize($path) { + public function filesize(string $path) { $stat = $this->getHeader($path); - return $stat['size']; + return $stat['size'] ?? false; } /** * get the last modified time of a file in the archive * - * @param string $path - * @return int + * @return int|false */ - public function mtime($path) { + public function mtime(string $path) { $stat = $this->getHeader($path); - return $stat['mtime']; + return $stat['mtime'] ?? false; } /** * get the files in a folder - * - * @param string $path - * @return array */ - public function getFolder($path) { + public function getFolder(string $path): array { $files = $this->getFiles(); $folderContent = []; $pathLength = strlen($path); @@ -222,10 +209,8 @@ class TAR extends Archive { /** * get all files in the archive - * - * @return array */ - public function getFiles() { + public function getFiles(): array { if ($this->fileList) { return $this->fileList; } @@ -243,21 +228,21 @@ class TAR extends Archive { /** * get the content of a file * - * @param string $path - * @return string + * @return string|false */ - public function getFile($path) { - return $this->tar->extractInString($path); + public function getFile(string $path) { + $string = $this->tar->extractInString($path); + if (is_string($string)) { + return $string; + } else { + return false; + } } /** * extract a single file from the archive - * - * @param string $path - * @param string $dest - * @return bool */ - public function extractFile($path, $dest) { + public function extractFile(string $path, string $dest): bool { $tmp = \OC::$server->getTempManager()->getTemporaryFolder(); if (!$this->fileExists($path)) { return false; @@ -276,21 +261,15 @@ class TAR extends Archive { /** * extract the archive - * - * @param string $dest - * @return bool */ - public function extract($dest) { + public function extract(string $dest): bool { return $this->tar->extract($dest); } /** * check if a file or folder exists in the archive - * - * @param string $path - * @return bool */ - public function fileExists($path) { + public function fileExists(string $path): bool { $files = $this->getFiles(); if ((array_search($path, $files) !== false) or (array_search($path . '/', $files) !== false)) { return true; @@ -312,11 +291,8 @@ class TAR extends Archive { /** * remove a file or folder from the archive - * - * @param string $path - * @return bool */ - public function remove($path) { + public function remove(string $path): bool { if (!$this->fileExists($path)) { return false; } @@ -336,13 +312,12 @@ class TAR extends Archive { /** * get a file handler * - * @param string $path - * @param string $mode * @return bool|resource */ - public function getStream($path, $mode) { - if (strrpos($path, '.') !== false) { - $ext = substr($path, strrpos($path, '.')); + public function getStream(string $path, string $mode) { + $lastPoint = strrpos($path, '.'); + if ($lastPoint !== false) { + $ext = substr($path, $lastPoint); } else { $ext = ''; } @@ -365,7 +340,7 @@ class TAR extends Archive { /** * write back temporary files */ - public function writeBack($tmpFile, $path) { + public function writeBack(string $tmpFile, string $path): void { $this->addFile($path, $tmpFile); unlink($tmpFile); } @@ -373,7 +348,7 @@ class TAR extends Archive { /** * reopen the archive to ensure everything is written */ - private function reopen() { + private function reopen(): void { if ($this->tar) { $this->tar->_close(); $this->tar = null; diff --git a/lib/private/Archive/ZIP.php b/lib/private/Archive/ZIP.php index e058d476021..ca9a046ab83 100644 --- a/lib/private/Archive/ZIP.php +++ b/lib/private/Archive/ZIP.php @@ -32,41 +32,42 @@ namespace OC\Archive; use Icewind\Streams\CallbackWrapper; -use OCP\ILogger; +use Psr\Log\LoggerInterface; class ZIP extends Archive { /** * @var \ZipArchive zip */ - private $zip = null; - private $path; + private $zip; /** - * @param string $source + * @var string */ - public function __construct($source) { + private $path; + + public function __construct(string $source) { $this->path = $source; $this->zip = new \ZipArchive(); if ($this->zip->open($source, \ZipArchive::CREATE)) { } else { - \OCP\Util::writeLog('files_archive', 'Error while opening archive '.$source, ILogger::WARN); + \OC::$server->get(LoggerInterface::class)->warning('Error while opening archive '.$source, ['app' => 'files_archive']); } } + /** * add an empty folder to the archive * @param string $path * @return bool */ - public function addFolder($path) { + public function addFolder(string $path): bool { return $this->zip->addEmptyDir($path); } + /** * add a file to the archive - * @param string $path * @param string $source either a local file or string data - * @return bool */ - public function addFile($path, $source = '') { + public function addFile(string $path, string $source = ''): bool { if ($source and $source[0] == '/' and file_exists($source)) { $result = $this->zip->addFile($source, $path); } else { @@ -78,40 +79,37 @@ class ZIP extends Archive { } return $result; } + /** * rename a file or folder in the archive - * @param string $source - * @param string $dest - * @return boolean|null */ - public function rename($source, $dest) { + public function rename(string $source, string $dest): bool { $source = $this->stripPath($source); $dest = $this->stripPath($dest); - $this->zip->renameName($source, $dest); + return $this->zip->renameName($source, $dest); } + /** * get the uncompressed size of a file in the archive - * @param string $path - * @return int + * @return int|false */ - public function filesize($path) { + public function filesize(string $path) { $stat = $this->zip->statName($path); - return $stat['size']; + return $stat['size'] ?? false; } + /** * get the last modified time of a file in the archive - * @param string $path - * @return int + * @return int|false */ - public function mtime($path) { + public function mtime(string $path) { return filemtime($this->path); } + /** * get the files in a folder - * @param string $path - * @return array */ - public function getFolder($path) { + public function getFolder(string $path): array { $files = $this->getFiles(); $folderContent = []; $pathLength = strlen($path); @@ -124,11 +122,11 @@ class ZIP extends Archive { } return $folderContent; } + /** * get all files in the archive - * @return array */ - public function getFiles() { + public function getFiles(): array { $fileCount = $this->zip->numFiles; $files = []; for ($i = 0;$i < $fileCount;$i++) { @@ -136,67 +134,65 @@ class ZIP extends Archive { } return $files; } + /** * get the content of a file - * @param string $path - * @return string + * @return string|false */ - public function getFile($path) { + public function getFile(string $path) { return $this->zip->getFromName($path); } + /** * extract a single file from the archive - * @param string $path - * @param string $dest - * @return boolean|null */ - public function extractFile($path, $dest) { + public function extractFile(string $path, string $dest): bool { $fp = $this->zip->getStream($path); - file_put_contents($dest, $fp); + if ($fp === false) { + return false; + } + return file_put_contents($dest, $fp) !== false; } + /** * extract the archive - * @param string $dest - * @return bool */ - public function extract($dest) { + public function extract(string $dest): bool { return $this->zip->extractTo($dest); } + /** * check if a file or folder exists in the archive - * @param string $path - * @return bool */ - public function fileExists($path) { + public function fileExists(string $path): bool { return ($this->zip->locateName($path) !== false) or ($this->zip->locateName($path.'/') !== false); } + /** * remove a file or folder from the archive - * @param string $path - * @return bool */ - public function remove($path) { + public function remove(string $path): bool { if ($this->fileExists($path.'/')) { return $this->zip->deleteName($path.'/'); } else { return $this->zip->deleteName($path); } } + /** * get a file handler - * @param string $path - * @param string $mode * @return bool|resource */ - public function getStream($path, $mode) { + public function getStream(string $path, string $mode) { if ($mode == 'r' or $mode == 'rb') { return $this->zip->getStream($path); } else { //since we can't directly get a writable stream, //make a temp copy of the file and put it back //in the archive when the stream is closed - if (strrpos($path, '.') !== false) { - $ext = substr($path, strrpos($path, '.')); + $lastPoint = strrpos($path, '.'); + if ($lastPoint !== false) { + $ext = substr($path, $lastPoint); } else { $ext = ''; } @@ -214,16 +210,12 @@ class ZIP extends Archive { /** * write back temporary files */ - public function writeBack($tmpFile, $path) { + public function writeBack(string $tmpFile, string $path): void { $this->addFile($path, $tmpFile); unlink($tmpFile); } - /** - * @param string $path - * @return string - */ - private function stripPath($path) { + private function stripPath(string $path): string { if (!$path || $path[0] == '/') { return substr($path, 1); } else { diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 04781457a7a..d2ee47cf380 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -97,12 +97,17 @@ class PublicKeyTokenProvider implements IProvider { $tokenHash = $this->hashToken($tokenId); if (isset($this->cache[$tokenHash])) { + if ($this->cache[$tokenHash] instanceof DoesNotExistException) { + $ex = $this->cache[$tokenHash]; + throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex); + } $token = $this->cache[$tokenHash]; } else { try { $token = $this->mapper->getToken($this->hashToken($tokenId)); $this->cache[$token->getToken()] = $token; } catch (DoesNotExistException $ex) { + $this->cache[$tokenHash] = $ex; throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex); } } diff --git a/lib/private/Avatar/AvatarManager.php b/lib/private/Avatar/AvatarManager.php index c3afd8094c7..77138085dc9 100644 --- a/lib/private/Avatar/AvatarManager.php +++ b/lib/private/Avatar/AvatarManager.php @@ -43,6 +43,7 @@ use OCP\Accounts\PropertyDoesNotExistException; use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\Files\StorageNotAvailableException; use OCP\IAvatar; use OCP\IAvatarManager; use OCP\IConfig; @@ -173,7 +174,7 @@ class AvatarManager implements IAvatarManager { $folder->delete(); } catch (NotFoundException $e) { $this->logger->debug("No cache for the user $userId. Ignoring avatar deletion"); - } catch (NotPermittedException $e) { + } catch (NotPermittedException | StorageNotAvailableException $e) { $this->logger->error("Unable to delete user avatars for $userId. gnoring avatar deletion"); } catch (NoUserException $e) { $this->logger->debug("User $userId not found. gnoring avatar deletion"); diff --git a/lib/private/BackgroundJob/Job.php b/lib/private/BackgroundJob/Job.php index 6f85e3382ed..399ff05134e 100644 --- a/lib/private/BackgroundJob/Job.php +++ b/lib/private/BackgroundJob/Job.php @@ -29,6 +29,9 @@ use OCP\BackgroundJob\IJob; use OCP\BackgroundJob\IJobList; use OCP\ILogger; +/** + * @deprecated internal class, use \OCP\BackgroundJob\Job + */ abstract class Job implements IJob { /** @var int */ protected $id; diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php index 16dfa7b58a6..21af79c4686 100644 --- a/lib/private/BackgroundJob/JobList.php +++ b/lib/private/BackgroundJob/JobList.php @@ -29,6 +29,7 @@ */ namespace OC\BackgroundJob; +use Doctrine\DBAL\Platforms\MySQLPlatform; use OCP\AppFramework\QueryException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\AutoloadNotAllowedException; @@ -111,10 +112,25 @@ class JobList implements IJobList { $query->delete('jobs') ->where($query->expr()->eq('class', $query->createNamedParameter($class))); if (!is_null($argument)) { - $argument = json_encode($argument); - $query->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument))); + $argumentJson = json_encode($argument); + $query->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argumentJson)))); + } + + // Add galera safe delete chunking if using mysql + // Stops us hitting wsrep_max_ws_rows when large row counts are deleted + if ($this->connection->getDatabasePlatform() instanceof MySQLPlatform) { + // Then use chunked delete + $max = IQueryBuilder::MAX_ROW_DELETION; + + $query->setMaxResults($max); + + do { + $deleted = $query->execute(); + } while ($deleted === $max); + } else { + // Dont use chunked delete - let the DB handle the large row count natively + $query->execute(); } - $query->execute(); } /** diff --git a/lib/private/BackgroundJob/Legacy/QueuedJob.php b/lib/private/BackgroundJob/Legacy/QueuedJob.php index d68fd217fdd..fd001738d4f 100644 --- a/lib/private/BackgroundJob/Legacy/QueuedJob.php +++ b/lib/private/BackgroundJob/Legacy/QueuedJob.php @@ -23,6 +23,9 @@ */ namespace OC\BackgroundJob\Legacy; +/** + * @deprecated internal class, use \OCP\BackgroundJob\QueuedJob + */ class QueuedJob extends \OC\BackgroundJob\QueuedJob { public function run($argument) { $class = $argument['klass']; diff --git a/lib/private/BackgroundJob/Legacy/RegularJob.php b/lib/private/BackgroundJob/Legacy/RegularJob.php index e3286536c9a..0f337a35eb0 100644 --- a/lib/private/BackgroundJob/Legacy/RegularJob.php +++ b/lib/private/BackgroundJob/Legacy/RegularJob.php @@ -24,6 +24,9 @@ namespace OC\BackgroundJob\Legacy; use OCP\AutoloadNotAllowedException; +/** + * @deprecated internal class, use \OCP\BackgroundJob\QueuedJob + */ class RegularJob extends \OC\BackgroundJob\Job { public function run($argument) { try { diff --git a/lib/private/BackgroundJob/QueuedJob.php b/lib/private/BackgroundJob/QueuedJob.php index 176227e73db..28d86481e62 100644 --- a/lib/private/BackgroundJob/QueuedJob.php +++ b/lib/private/BackgroundJob/QueuedJob.php @@ -32,6 +32,8 @@ use OCP\ILogger; * create a background job that is to be executed once * * @package OC\BackgroundJob + * + * @deprecated internal class, use \OCP\BackgroundJob\QueuedJob */ abstract class QueuedJob extends Job { /** diff --git a/lib/private/BackgroundJob/TimedJob.php b/lib/private/BackgroundJob/TimedJob.php index 62b9bab7ccc..0f0951e1aec 100644 --- a/lib/private/BackgroundJob/TimedJob.php +++ b/lib/private/BackgroundJob/TimedJob.php @@ -34,6 +34,8 @@ use OCP\ILogger; * create a background job that is to be executed at an interval * * @package OC\BackgroundJob + * + * @deprecated internal class, use \OCP\BackgroundJob\TimedJob */ abstract class TimedJob extends Job { protected $interval = 0; diff --git a/lib/private/Cache/CappedMemoryCache.php b/lib/private/Cache/CappedMemoryCache.php index 584a53f0ff2..9260bf1f6b3 100644 --- a/lib/private/Cache/CappedMemoryCache.php +++ b/lib/private/Cache/CappedMemoryCache.php @@ -27,30 +27,42 @@ use OCP\ICache; * In-memory cache with a capacity limit to keep memory usage in check * * Uses a simple FIFO expiry mechanism + * @template T */ class CappedMemoryCache implements ICache, \ArrayAccess { private $capacity; + /** @var T[] */ private $cache = []; public function __construct($capacity = 512) { $this->capacity = $capacity; } - public function hasKey($key) { + public function hasKey($key): bool { return isset($this->cache[$key]); } + /** + * @return ?T + */ public function get($key) { - return isset($this->cache[$key]) ? $this->cache[$key] : null; + return $this->cache[$key] ?? null; } - public function set($key, $value, $ttl = 0) { + /** + * @param string $key + * @param T $value + * @param int $ttl + * @return bool + */ + public function set($key, $value, $ttl = 0): bool { if (is_null($key)) { $this->cache[] = $value; } else { $this->cache[$key] = $value; } $this->garbageCollect(); + return true; } public function remove($key) { @@ -68,13 +80,18 @@ class CappedMemoryCache implements ICache, \ArrayAccess { } /** - * @return mixed + * @return T */ #[\ReturnTypeWillChange] public function &offsetGet($offset) { return $this->cache[$offset]; } + /** + * @param string $key + * @param T $value + * @return void + */ public function offsetSet($offset, $value): void { $this->set($offset, $value); } @@ -83,6 +100,9 @@ class CappedMemoryCache implements ICache, \ArrayAccess { $this->remove($offset); } + /** + * @return T[] + */ public function getData() { return $this->cache; } diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 7c312b18f7d..384187accf3 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -611,13 +611,16 @@ class Manager implements ICommentsManager { $query->select('*') ->from('comments') - ->where($query->expr()->iLike('message', $query->createNamedParameter( - '%' . $this->dbConn->escapeLikeParameter($search). '%' - ))) ->orderBy('creation_timestamp', 'DESC') ->addOrderBy('id', 'DESC') ->setMaxResults($limit); + if ($search !== '') { + $query->where($query->expr()->iLike('message', $query->createNamedParameter( + '%' . $this->dbConn->escapeLikeParameter($search). '%' + ))); + } + if ($objectType !== '') { $query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType))); } diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index a27c2ae455a..5b7a942a244 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -32,7 +32,7 @@ namespace OC\Contacts\ContactsMenu; use OC\KnownUser\KnownUserService; -use OCP\Accounts\IAccountManager; +use OC\Profile\ProfileManager; use OCP\Contacts\ContactsMenu\IContactsStore; use OCP\Contacts\ContactsMenu\IEntry; use OCP\Contacts\IManager; @@ -44,10 +44,6 @@ use OCP\IUserManager; use OCP\L10N\IFactory as IL10NFactory; class ContactsStore implements IContactsStore { - use \OC\Profile\TProfileHelper; - - /** @var IAccountManager */ - private $accountManager; /** @var IManager */ private $contactsManager; @@ -55,6 +51,9 @@ class ContactsStore implements IContactsStore { /** @var IConfig */ private $config; + /** @var ProfileManager */ + private $profileManager; + /** @var IUserManager */ private $userManager; @@ -71,18 +70,18 @@ class ContactsStore implements IContactsStore { private $l10nFactory; public function __construct( - IAccountManager $accountManager, IManager $contactsManager, IConfig $config, + ProfileManager $profileManager, IUserManager $userManager, IURLGenerator $urlGenerator, IGroupManager $groupManager, KnownUserService $knownUserService, IL10NFactory $l10nFactory ) { - $this->accountManager = $accountManager; $this->contactsManager = $contactsManager; $this->config = $config; + $this->profileManager = $profileManager; $this->userManager = $userManager; $this->urlGenerator = $urlGenerator; $this->groupManager = $groupManager; @@ -335,10 +334,9 @@ class ContactsStore implements IContactsStore { // Provide profile parameters for core/src/OC/contactsmenu/contact.handlebars template if (isset($contact['UID']) && isset($contact['FN'])) { $targetUserId = $contact['UID']; - $user = $this->userManager->get($targetUserId); - if (!empty($user)) { - $account = $this->accountManager->getAccount($user); - if ($this->isProfileEnabled($account)) { + $targetUser = $this->userManager->get($targetUserId); + if (!empty($targetUser)) { + if ($this->profileManager->isProfileEnabled($targetUser)) { $entry->setProfileTitle($this->l10nFactory->get('lib')->t('View profile')); $entry->setProfileUrl($this->urlGenerator->linkToRouteAbsolute('core.ProfilePage.index', ['targetUserId' => $targetUserId])); } diff --git a/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php b/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php index 15d24fc7773..e654319c3fa 100644 --- a/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php +++ b/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php @@ -24,7 +24,7 @@ namespace OC\Contacts\ContactsMenu\Providers; -use OCP\Accounts\IAccountManager; +use OC\Profile\ProfileManager; use OCP\Contacts\ContactsMenu\IActionFactory; use OCP\Contacts\ContactsMenu\IEntry; use OCP\Contacts\ContactsMenu\IProvider; @@ -33,14 +33,13 @@ use OCP\IUserManager; use OCP\L10N\IFactory as IL10NFactory; class ProfileProvider implements IProvider { - use \OC\Profile\TProfileHelper; - - /** @var IAccountManager */ - private $accountManager; /** @var IActionFactory */ private $actionFactory; + /** @var ProfileManager */ + private $profileManager; + /** @var IL10NFactory */ private $l10nFactory; @@ -51,21 +50,21 @@ class ProfileProvider implements IProvider { private $userManager; /** - * @param IAccountManager $accountManager * @param IActionFactory $actionFactory + * @param ProfileManager $profileManager * @param IL10NFactory $l10nFactory * @param IURLGenerator $urlGenerator * @param IUserManager $userManager */ public function __construct( - IAccountManager $accountManager, IActionFactory $actionFactory, + ProfileManager $profileManager, IL10NFactory $l10nFactory, IURLGenerator $urlGenerator, IUserManager $userManager ) { - $this->accountManager = $accountManager; $this->actionFactory = $actionFactory; + $this->profileManager = $profileManager; $this->l10nFactory = $l10nFactory; $this->urlGenerator = $urlGenerator; $this->userManager = $userManager; @@ -78,8 +77,7 @@ class ProfileProvider implements IProvider { $targetUserId = $entry->getProperty('UID'); $targetUser = $this->userManager->get($targetUserId); if (!empty($targetUser)) { - $account = $this->accountManager->getAccount($targetUser); - if ($this->isProfileEnabled($account)) { + if ($this->profileManager->isProfileEnabled($targetUser)) { $iconUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/profile.svg')); $profileActionText = $this->l10nFactory->get('lib')->t('View profile'); $profileUrl = $this->urlGenerator->linkToRouteAbsolute('core.ProfilePage.index', ['targetUserId' => $targetUserId]); diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index b8d1a747fa2..e32c530c19e 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -81,7 +81,17 @@ class Connection extends \Doctrine\DBAL\Connection { */ public function connect() { try { - return parent::connect(); + if ($this->_conn) { + return parent::connect(); + } + + // Only trigger the event logger for the initial connect call + $eventLogger = \OC::$server->getEventLogger(); + $eventLogger->start('connect:db', 'db connection opened'); + $status = parent::connect(); + $eventLogger->end('connect:db'); + + return $status; } catch (Exception $e) { // throw a new exception to prevent leaking info from the stacktrace throw new Exception('Failed to connect to the database: ' . $e->getMessage(), $e->getCode()); @@ -538,12 +548,20 @@ class Connection extends \Doctrine\DBAL\Connection { * Migrate the database to the given schema * * @param Schema $toSchema + * @param bool $dryRun If true, will return the sql queries instead of running them. * * @throws Exception + * + * @return string|null Returns a string only if $dryRun is true. */ - public function migrateToSchema(Schema $toSchema) { + public function migrateToSchema(Schema $toSchema, bool $dryRun = false) { $migrator = $this->getMigrator(); - $migrator->migrate($toSchema); + + if ($dryRun) { + return $migrator->generateChangeScript($toSchema); + } else { + $migrator->migrate($toSchema); + } } private function getMigrator() { diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index 6ad74a58591..e7361cb4cab 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -634,6 +634,8 @@ class MigrationService { if ($isUsingDefaultName && \strlen($table->getName()) - $prefixLength >= 23) { throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.'); } + } elseif (!$primaryKey instanceof Index) { + throw new \InvalidArgumentException('Table "' . $table->getName() . '" has no primary key and therefor will not behave sane in clustered setups.'); } } diff --git a/lib/private/Diagnostics/Event.php b/lib/private/Diagnostics/Event.php index 95b737155dc..0c3aa6ae29c 100644 --- a/lib/private/Diagnostics/Event.php +++ b/lib/private/Diagnostics/Event.php @@ -100,4 +100,8 @@ class Event implements IEvent { } return $this->end - $this->start; } + + public function __toString() { + return $this->getId() . ' ' . $this->getDescription() . ' ' . $this->getDuration(); + } } diff --git a/lib/private/Diagnostics/EventLogger.php b/lib/private/Diagnostics/EventLogger.php index 4f3343ec625..35cef0be3f5 100644 --- a/lib/private/Diagnostics/EventLogger.php +++ b/lib/private/Diagnostics/EventLogger.php @@ -24,25 +24,59 @@ */ namespace OC\Diagnostics; +use OC\Log; +use OC\SystemConfig; +use OCP\Diagnostics\IEvent; use OCP\Diagnostics\IEventLogger; +use Psr\Log\LoggerInterface; class EventLogger implements IEventLogger { - /** - * @var \OC\Diagnostics\Event[] - */ + + /** @var Event[] */ private $events = []; - + + /** @var SystemConfig */ + private $config; + + /** @var LoggerInterface */ + private $logger; + + /** @var Log */ + private $internalLogger; + /** * @var bool - Module needs to be activated by some app */ private $activated = false; + public function __construct(SystemConfig $config, LoggerInterface $logger, Log $internalLogger) { + $this->config = $config; + $this->logger = $logger; + $this->internalLogger = $internalLogger; + + if ($this->isLoggingActivated()) { + $this->activate(); + } + } + + public function isLoggingActivated(): bool { + $systemValue = (bool)$this->config->getValue('diagnostics.logging', false); + + if ($systemValue && $this->config->getValue('debug', false)) { + return true; + } + + $isDebugLevel = $this->internalLogger->getLogLevel([]) === Log::DEBUG; + return $systemValue && $isDebugLevel; + } + /** * @inheritdoc */ - public function start($id, $description) { + public function start($id, $description = '') { if ($this->activated) { $this->events[$id] = new Event($id, $description, microtime(true)); + $this->writeLog($this->events[$id]); } } @@ -53,6 +87,7 @@ class EventLogger implements IEventLogger { if ($this->activated && isset($this->events[$id])) { $timing = $this->events[$id]; $timing->end(microtime(true)); + $this->writeLog($timing); } } @@ -63,6 +98,7 @@ class EventLogger implements IEventLogger { if ($this->activated) { $this->events[$id] = new Event($id, $description, $start); $this->events[$id]->end($end); + $this->writeLog($this->events[$id]); } } @@ -72,11 +108,29 @@ class EventLogger implements IEventLogger { public function getEvents() { return $this->events; } - + /** * @inheritdoc */ public function activate() { $this->activated = true; } + + private function writeLog(IEvent $event) { + if ($this->activated) { + if ($event->getEnd() === null) { + return; + } + $duration = $event->getDuration(); + $timeInMs = round($duration * 1000, 4); + + $loggingMinimum = (int)$this->config->getValue('diagnostics.logging.threshold', 0); + if ($loggingMinimum > 0 && $timeInMs < $loggingMinimum) { + return; + } + + $message = microtime() . ' - ' . $event->getId() . ': ' . $timeInMs . ' (' . $event->getDescription() . ')'; + $this->logger->debug($message, ['app' => 'diagnostics']); + } + } } diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php index bd8db2c8a12..af412dd129b 100644 --- a/lib/private/Files/Cache/Scanner.php +++ b/lib/private/Files/Cache/Scanner.php @@ -36,7 +36,6 @@ namespace OC\Files\Cache; use Doctrine\DBAL\Exception; -use OC\Files\Filesystem; use OC\Files\Storage\Wrapper\Jail; use OC\Files\Storage\Wrapper\Encoding; use OC\Hooks\BasicEmitter; @@ -140,8 +139,8 @@ class Scanner extends BasicEmitter implements IScanner { return null; } } - // only proceed if $file is not a partial file nor a blacklisted file - if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file)) { + // only proceed if $file is not a partial file, blacklist is handled by the storage + if (!self::isPartialFile($file)) { //acquire a lock if ($lock) { diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index 33785607ef7..2de2c2f84d7 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -158,7 +158,7 @@ class Storage { } /** - * @return array|null [ available, last_checked ] + * @return array [ available, last_checked ] */ public function getAvailability() { if ($row = self::getStorageById($this->storageId)) { @@ -167,7 +167,10 @@ class Storage { 'last_checked' => $row['last_checked'] ]; } else { - return null; + return [ + 'available' => true, + 'last_checked' => time(), + ]; } } diff --git a/lib/private/Files/Config/CachedMountFileInfo.php b/lib/private/Files/Config/CachedMountFileInfo.php index 7fdc26f7d53..11a9a505808 100644 --- a/lib/private/Files/Config/CachedMountFileInfo.php +++ b/lib/private/Files/Config/CachedMountFileInfo.php @@ -27,15 +27,23 @@ use OCP\Files\Config\ICachedMountFileInfo; use OCP\IUser; class CachedMountFileInfo extends CachedMountInfo implements ICachedMountFileInfo { - /** @var string */ - private $internalPath; + private string $internalPath; - public function __construct(IUser $user, $storageId, $rootId, $mountPoint, $mountId, $rootInternalPath, $internalPath) { - parent::__construct($user, $storageId, $rootId, $mountPoint, $mountId, $rootInternalPath); + public function __construct( + IUser $user, + int $storageId, + int $rootId, + string $mountPoint, + ?int $mountId, + string $mountProvider, + string $rootInternalPath, + string $internalPath + ) { + parent::__construct($user, $storageId, $rootId, $mountPoint, $mountProvider, $mountId, $rootInternalPath); $this->internalPath = $internalPath; } - public function getInternalPath() { + public function getInternalPath(): string { if ($this->getRootInternalPath()) { return substr($this->internalPath, strlen($this->getRootInternalPath()) + 1); } else { @@ -43,7 +51,7 @@ class CachedMountFileInfo extends CachedMountInfo implements ICachedMountFileInf } } - public function getPath() { + public function getPath(): string { return $this->getMountPoint() . $this->getInternalPath(); } } diff --git a/lib/private/Files/Config/CachedMountInfo.php b/lib/private/Files/Config/CachedMountInfo.php index e7e10cfe2af..43c9fae63ec 100644 --- a/lib/private/Files/Config/CachedMountInfo.php +++ b/lib/private/Files/Config/CachedMountInfo.php @@ -28,35 +28,13 @@ use OCP\Files\Node; use OCP\IUser; class CachedMountInfo implements ICachedMountInfo { - /** - * @var IUser - */ - protected $user; - - /** - * @var int - */ - protected $storageId; - - /** - * @var int - */ - protected $rootId; - - /** - * @var string - */ - protected $mountPoint; - - /** - * @var int|null - */ - protected $mountId; - - /** - * @var string - */ - protected $rootInternalPath; + protected IUser $user; + protected int $storageId; + protected int $rootId; + protected string $mountPoint; + protected ?int $mountId; + protected string $rootInternalPath; + protected string $mountProvider; /** * CachedMountInfo constructor. @@ -68,40 +46,52 @@ class CachedMountInfo implements ICachedMountInfo { * @param int|null $mountId * @param string $rootInternalPath */ - public function __construct(IUser $user, $storageId, $rootId, $mountPoint, $mountId = null, $rootInternalPath = '') { + public function __construct( + IUser $user, + int $storageId, + int $rootId, + string $mountPoint, + string $mountProvider, + int $mountId = null, + string $rootInternalPath = '' + ) { $this->user = $user; $this->storageId = $storageId; $this->rootId = $rootId; $this->mountPoint = $mountPoint; $this->mountId = $mountId; $this->rootInternalPath = $rootInternalPath; + if (strlen($mountProvider) > 128) { + throw new \Exception("Mount provider $mountProvider name exceeds the limit of 128 characters"); + } + $this->mountProvider = $mountProvider; } /** * @return IUser */ - public function getUser() { + public function getUser(): IUser { return $this->user; } /** * @return int the numeric storage id of the mount */ - public function getStorageId() { + public function getStorageId(): int { return $this->storageId; } /** * @return int the fileid of the root of the mount */ - public function getRootId() { + public function getRootId(): int { return $this->rootId; } /** - * @return Node the root node of the mount + * @return Node|null the root node of the mount */ - public function getMountPointNode() { + public function getMountPointNode(): ?Node { // TODO injection etc Filesystem::initMountPoints($this->getUser()->getUID()); $userNode = \OC::$server->getUserFolder($this->getUser()->getUID()); @@ -116,7 +106,7 @@ class CachedMountInfo implements ICachedMountInfo { /** * @return string the mount point of the mount for the user */ - public function getMountPoint() { + public function getMountPoint(): string { return $this->mountPoint; } @@ -126,7 +116,7 @@ class CachedMountInfo implements ICachedMountInfo { * @return int|null mount id or null if not applicable * @since 9.1.0 */ - public function getMountId() { + public function getMountId(): ?int { return $this->mountId; } @@ -135,7 +125,11 @@ class CachedMountInfo implements ICachedMountInfo { * * @return string */ - public function getRootInternalPath() { + public function getRootInternalPath(): string { return $this->rootInternalPath; } + + public function getMountProvider(): string { + return $this->mountProvider; + } } diff --git a/lib/private/Files/Config/LazyStorageMountInfo.php b/lib/private/Files/Config/LazyStorageMountInfo.php index bfd632c5f6e..78055a2cdb8 100644 --- a/lib/private/Files/Config/LazyStorageMountInfo.php +++ b/lib/private/Files/Config/LazyStorageMountInfo.php @@ -25,8 +25,7 @@ use OCP\Files\Mount\IMountPoint; use OCP\IUser; class LazyStorageMountInfo extends CachedMountInfo { - /** @var IMountPoint */ - private $mount; + private IMountPoint $mount; /** * CachedMountInfo constructor. @@ -37,12 +36,15 @@ class LazyStorageMountInfo extends CachedMountInfo { public function __construct(IUser $user, IMountPoint $mount) { $this->user = $user; $this->mount = $mount; + $this->rootId = 0; + $this->storageId = 0; + $this->mountPoint = ''; } /** * @return int the numeric storage id of the mount */ - public function getStorageId() { + public function getStorageId(): int { if (!$this->storageId) { $this->storageId = $this->mount->getNumericStorageId(); } @@ -52,7 +54,7 @@ class LazyStorageMountInfo extends CachedMountInfo { /** * @return int the fileid of the root of the mount */ - public function getRootId() { + public function getRootId(): int { if (!$this->rootId) { $this->rootId = $this->mount->getStorageRootId(); } @@ -62,14 +64,14 @@ class LazyStorageMountInfo extends CachedMountInfo { /** * @return string the mount point of the mount for the user */ - public function getMountPoint() { + public function getMountPoint(): string { if (!$this->mountPoint) { $this->mountPoint = $this->mount->getMountPoint(); } return parent::getMountPoint(); } - public function getMountId() { + public function getMountId(): ?int { return $this->mount->getMountId(); } @@ -78,7 +80,11 @@ class LazyStorageMountInfo extends CachedMountInfo { * * @return string */ - public function getRootInternalPath() { + public function getRootInternalPath(): string { return $this->mount->getInternalPath($this->mount->getMountPoint()); } + + public function getMountProvider(): string { + return $this->mount->getMountProvider(); + } } diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php index ba70e29ab8d..cd8a2a2e29f 100644 --- a/lib/private/Files/Config/MountProviderCollection.php +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -184,16 +184,6 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { } /** - * Cache mounts for user - * - * @param IUser $user - * @param IMountPoint[] $mountPoints - */ - public function registerMounts(IUser $user, array $mountPoints) { - $this->mountCache->registerMounts($user, $mountPoints); - } - - /** * Get the mount cache which can be used to search for mounts without setting up the filesystem * * @return IUserMountCache @@ -222,4 +212,10 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { }, []); return $mounts; } + + public function clearProviders() { + $this->providers = []; + $this->homeProviders = []; + $this->rootProviders = []; + } } diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index 71eb918a58c..dc2640361e7 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -164,7 +164,8 @@ class UserMountCache implements IUserMountCache { if ( $newMount->getMountPoint() !== $cachedMount->getMountPoint() || $newMount->getStorageId() !== $cachedMount->getStorageId() || - $newMount->getMountId() !== $cachedMount->getMountId() + $newMount->getMountId() !== $cachedMount->getMountId() || + $newMount->getMountProvider() !== $cachedMount->getMountProvider() ) { $changed[] = $newMount; } @@ -180,7 +181,8 @@ class UserMountCache implements IUserMountCache { 'root_id' => $mount->getRootId(), 'user_id' => $mount->getUser()->getUID(), 'mount_point' => $mount->getMountPoint(), - 'mount_id' => $mount->getMountId() + 'mount_id' => $mount->getMountId(), + 'mount_provider_class' => $mount->getMountProvider(), ], ['root_id', 'user_id']); } else { // in some cases this is legitimate, like orphaned shares @@ -195,6 +197,7 @@ class UserMountCache implements IUserMountCache { ->set('storage_id', $builder->createNamedParameter($mount->getStorageId())) ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint())) ->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT)) + ->set('mount_provider_class', $builder->createNamedParameter($mount->getMountProvider())) ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); @@ -219,7 +222,15 @@ class UserMountCache implements IUserMountCache { if (!is_null($mount_id)) { $mount_id = (int)$mount_id; } - return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $mount_id, isset($row['path']) ? $row['path'] : ''); + return new CachedMountInfo( + $user, + (int)$row['storage_id'], + (int)$row['root_id'], + $row['mount_point'], + $row['mount_provider_class'] ?? '', + $mount_id, + isset($row['path']) ? $row['path'] : '', + ); } /** @@ -229,7 +240,7 @@ class UserMountCache implements IUserMountCache { public function getMountsForUser(IUser $user) { if (!isset($this->mountsForUsers[$user->getUID()])) { $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class') ->from('mounts', 'm') ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); @@ -250,7 +261,7 @@ class UserMountCache implements IUserMountCache { */ public function getMountsForStorageId($numericStorageId, $user = null) { $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class') ->from('mounts', 'm') ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT))); @@ -272,7 +283,7 @@ class UserMountCache implements IUserMountCache { */ public function getMountsForRootId($rootFileId) { $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class') ->from('mounts', 'm') ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT))); @@ -286,10 +297,10 @@ class UserMountCache implements IUserMountCache { /** * @param $fileId - * @return array + * @return array{int, string, int} * @throws \OCP\Files\NotFoundException */ - private function getCacheInfoFromFileId($fileId) { + private function getCacheInfoFromFileId($fileId): array { if (!isset($this->cacheInfoCache[$fileId])) { $builder = $this->connection->getQueryBuilder(); $query = $builder->select('storage', 'path', 'mimetype') @@ -303,7 +314,7 @@ class UserMountCache implements IUserMountCache { if (is_array($row)) { $this->cacheInfoCache[$fileId] = [ (int)$row['storage'], - $row['path'], + (string)$row['path'], (int)$row['mimetype'] ]; } else { @@ -326,7 +337,7 @@ class UserMountCache implements IUserMountCache { return []; } $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class') ->from('mounts', 'm') ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($storageId, IQueryBuilder::PARAM_INT))); @@ -343,7 +354,7 @@ class UserMountCache implements IUserMountCache { if ($fileId === (int)$row['root_id']) { return true; } - $internalMountPath = isset($row['path']) ? $row['path'] : ''; + $internalMountPath = $row['path'] ?? ''; return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/'; }); @@ -356,6 +367,7 @@ class UserMountCache implements IUserMountCache { $mount->getRootId(), $mount->getMountPoint(), $mount->getMountId(), + $mount->getMountProvider(), $mount->getRootInternalPath(), $internalPath ); diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index e439b746bf6..9db9252037f 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -38,13 +38,12 @@ namespace OC\Files; use OC\Cache\CappedMemoryCache; -use OC\Files\Config\MountProviderCollection; use OC\Files\Mount\MountPoint; -use OC\Lockdown\Filesystem\NullStorage; -use OCP\Files\Config\IMountProvider; +use OC\User\NoUserException; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Events\Node\FilesystemTornDownEvent; use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorageFactory; -use OCP\ILogger; use OCP\IUser; use OCP\IUserManager; @@ -67,6 +66,9 @@ class Filesystem { private static $listeningForProviders = false; + /** @var string[]|null */ + private static $blacklist = null; + /** * classname which used for hooks handling * used as signalclass in OC_Hooks::emit() @@ -259,11 +261,7 @@ class Filesystem { \OC_Util::setupFS(); } $mount = self::$mounts->find($path); - if ($mount) { - return $mount->getMountPoint(); - } else { - return ''; - } + return $mount->getMountPoint(); } /** @@ -319,11 +317,7 @@ class Filesystem { */ public static function resolvePath($path) { $mount = self::getMountManager()->find($path); - if ($mount) { - return [$mount->getStorage(), rtrim($mount->getInternalPath($path), '/')]; - } else { - return [null, null]; - } + return [$mount->getStorage(), rtrim($mount->getInternalPath($path), '/')]; } public static function init($user, $root) { @@ -332,6 +326,13 @@ class Filesystem { } self::getLoader(); self::$defaultInstance = new View($root); + /** @var IEventDispatcher $eventDispatcher */ + $eventDispatcher = \OC::$server->get(IEventDispatcher::class); + $eventDispatcher->addListener(FilesystemTornDownEvent::class, function () { + self::$defaultInstance = null; + self::$usersSetup = []; + self::$loaded = false; + }); if (!self::$mounts) { self::$mounts = \OC::$server->getMountManager(); @@ -358,106 +359,16 @@ class Filesystem { * @throws \OC\User\NoUserException if the user is not available */ public static function initMountPoints($user = '') { - $userManager = \OC::$server->getUserManager(); - if (is_string($user)) { - if ($user === '') { - $user = \OC_User::getUser(); - } - - $userObject = $userManager->get($user); - } elseif ($user instanceof IUser) { - $userObject = $user; - $user = $userObject->getUID(); + /** @var IUserManager $userManager */ + $userManager = \OC::$server->get(IUserManager::class); + + $userObject = ($user instanceof IUser) ? $user : $userManager->get($user); + if ($userObject) { + /** @var SetupManager $setupManager */ + $setupManager = \OC::$server->get(SetupManager::class); + $setupManager->setupForUser($userObject); } else { - $userObject = null; - } - - if ($userObject === null || $user === false || $user === '') { - throw new \OC\User\NoUserException('Attempted to initialize mount points for null user and no user in session'); - } - - if (isset(self::$usersSetup[$user])) { - return; - } - - self::$usersSetup[$user] = true; - - if (is_null($userObject)) { - \OCP\Util::writeLog('files', ' Backends provided no user object for ' . $user, ILogger::ERROR); - // reset flag, this will make it possible to rethrow the exception if called again - unset(self::$usersSetup[$user]); - throw new \OC\User\NoUserException('Backends provided no user object for ' . $user); - } - - $realUid = $userObject->getUID(); - // workaround in case of different casings - if ($user !== $realUid) { - $stack = json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 50)); - \OCP\Util::writeLog('files', 'initMountPoints() called with wrong user casing. This could be a bug. Expected: "' . $realUid . '" got "' . $user . '". Stack: ' . $stack, ILogger::WARN); - $user = $realUid; - - // again with the correct casing - if (isset(self::$usersSetup[$user])) { - return; - } - - self::$usersSetup[$user] = true; - } - - if (\OC::$server->getLockdownManager()->canAccessFilesystem()) { - /** @var \OC\Files\Config\MountProviderCollection $mountConfigManager */ - $mountConfigManager = \OC::$server->getMountProviderCollection(); - - // home mounts are handled seperate since we need to ensure this is mounted before we call the other mount providers - $homeMount = $mountConfigManager->getHomeMountForUser($userObject); - self::getMountManager()->addMount($homeMount); - - if ($homeMount->getStorageRootId() === -1) { - $homeMount->getStorage()->mkdir(''); - $homeMount->getStorage()->getScanner()->scan(''); - } - - \OC\Files\Filesystem::getStorage($user); - - // Chance to mount for other storages - if ($userObject) { - $mounts = $mountConfigManager->addMountForUser($userObject, self::getMountManager()); - $mounts[] = $homeMount; - $mountConfigManager->registerMounts($userObject, $mounts); - } - - self::listenForNewMountProviders($mountConfigManager, $userManager); - } else { - self::getMountManager()->addMount(new MountPoint( - new NullStorage([]), - '/' . $user - )); - self::getMountManager()->addMount(new MountPoint( - new NullStorage([]), - '/' . $user . '/files' - )); - } - \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user]); - } - - /** - * Get mounts from mount providers that are registered after setup - * - * @param MountProviderCollection $mountConfigManager - * @param IUserManager $userManager - */ - private static function listenForNewMountProviders(MountProviderCollection $mountConfigManager, IUserManager $userManager) { - if (!self::$listeningForProviders) { - self::$listeningForProviders = true; - $mountConfigManager->listen('\OC\Files\Config', 'registerMountProvider', function (IMountProvider $provider) use ($userManager) { - foreach (Filesystem::$usersSetup as $user => $setup) { - $userObject = $userManager->get($user); - if ($userObject) { - $mounts = $provider->getMountsForUser($userObject, Filesystem::getLoader()); - array_walk($mounts, [self::$mounts, 'addMount']); - } - } - }); + throw new NoUserException(); } } @@ -474,8 +385,7 @@ class Filesystem { * tear down the filesystem, removing all storage providers */ public static function tearDown() { - self::clearMounts(); - self::$defaultInstance = null; + \OC_Util::tearDownFS(); } /** @@ -493,16 +403,6 @@ class Filesystem { } /** - * clear all mounts and storage backends - */ - public static function clearMounts() { - if (self::$mounts) { - self::$usersSetup = []; - self::$mounts->clear(); - } - } - - /** * mount an \OC\Files\Storage\Storage in our virtual filesystem * * @param \OC\Files\Storage\Storage|string $class @@ -595,9 +495,12 @@ class Filesystem { public static function isFileBlacklisted($filename) { $filename = self::normalizePath($filename); - $blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', ['.htaccess']); + if (self::$blacklist === null) { + self::$blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', ['.htaccess']); + } + $filename = strtolower(basename($filename)); - return in_array($filename, $blacklist); + return in_array($filename, self::$blacklist); } /** diff --git a/lib/private/Files/Mount/CacheMountProvider.php b/lib/private/Files/Mount/CacheMountProvider.php index 16ecefb0dac..90dfa0b05f3 100644 --- a/lib/private/Files/Mount/CacheMountProvider.php +++ b/lib/private/Files/Mount/CacheMountProvider.php @@ -61,8 +61,8 @@ class CacheMountProvider implements IMountProvider { } return [ - new MountPoint('\OC\Files\Storage\Local', '/' . $user->getUID() . '/cache', ['datadir' => $cacheDir, $loader]), - new MountPoint('\OC\Files\Storage\Local', '/' . $user->getUID() . '/uploads', ['datadir' => $cacheDir . '/uploads', $loader]) + new MountPoint('\OC\Files\Storage\Local', '/' . $user->getUID() . '/cache', ['datadir' => $cacheDir], $loader, null, null, self::class), + new MountPoint('\OC\Files\Storage\Local', '/' . $user->getUID() . '/uploads', ['datadir' => $cacheDir . '/uploads'], $loader, null, null, self::class) ]; } else { return []; diff --git a/lib/private/Files/Mount/LocalHomeMountProvider.php b/lib/private/Files/Mount/LocalHomeMountProvider.php index c7b2296c4a8..25a67fc1574 100644 --- a/lib/private/Files/Mount/LocalHomeMountProvider.php +++ b/lib/private/Files/Mount/LocalHomeMountProvider.php @@ -38,6 +38,6 @@ class LocalHomeMountProvider implements IHomeMountProvider { */ public function getHomeMountForUser(IUser $user, IStorageFactory $loader) { $arguments = ['user' => $user]; - return new MountPoint('\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader); + return new MountPoint('\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class); } } diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php index 8c6f1acceec..66832690363 100644 --- a/lib/private/Files/Mount/Manager.php +++ b/lib/private/Files/Mount/Manager.php @@ -26,26 +26,30 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/> * */ + namespace OC\Files\Mount; use OC\Cache\CappedMemoryCache; use OC\Files\Filesystem; +use OC\Files\SetupManager; +use OC\Files\SetupManagerFactory; use OCP\Files\Mount\IMountManager; use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; class Manager implements IMountManager { /** @var MountPoint[] */ - private $mounts = []; - - /** @var CappedMemoryCache */ - private $pathCache; - - /** @var CappedMemoryCache */ - private $inPathCache; - - public function __construct() { + private array $mounts = []; + /** @var CappedMemoryCache<IMountPoint> */ + private CappedMemoryCache $pathCache; + /** @var CappedMemoryCache<IMountPoint[]> */ + private CappedMemoryCache $inPathCache; + private SetupManager $setupManager; + + public function __construct(SetupManagerFactory $setupManagerFactory) { $this->pathCache = new CappedMemoryCache(); $this->inPathCache = new CappedMemoryCache(); + $this->setupManager = $setupManagerFactory->create($this); } /** @@ -81,23 +85,14 @@ class Manager implements IMountManager { $this->inPathCache->clear(); } - private function setupForFind(string $path) { - if (strpos($path, '/appdata_' . \OC_Util::getInstanceId()) === 0) { - // for appdata, we only setup the root bits, not the user bits - \OC_Util::setupRootFS(); - } else { - \OC_Util::setupFS(); - } - } - /** * Find the mount for $path * * @param string $path - * @return MountPoint|null + * @return IMountPoint */ - public function find(string $path) { - $this->setupForFind($path); + public function find(string $path): IMountPoint { + $this->setupManager->setupForPath($path); $path = Filesystem::normalizePath($path); if (isset($this->pathCache[$path])) { @@ -110,10 +105,8 @@ class Manager implements IMountManager { if (isset($this->mounts[$mountPoint])) { $this->pathCache[$path] = $this->mounts[$mountPoint]; return $this->mounts[$mountPoint]; - } - - if ($current === '') { - return null; + } elseif ($current === '') { + break; } $current = dirname($current); @@ -121,16 +114,18 @@ class Manager implements IMountManager { $current = ''; } } + + throw new NotFoundException("No mount for path " . $path . " existing mounts: " . implode(",", array_keys($this->mounts))); } /** * Find all mounts in $path * * @param string $path - * @return MountPoint[] + * @return IMountPoint[] */ public function findIn(string $path): array { - $this->setupForFind($path); + $this->setupManager->setupForPath($path); $path = $this->formatPath($path); if (isset($this->inPathCache[$path])) { @@ -160,7 +155,7 @@ class Manager implements IMountManager { * Find mounts by storage id * * @param string $id - * @return MountPoint[] + * @return IMountPoint[] */ public function findByStorageId(string $id): array { \OC_Util::setupFS(); @@ -177,7 +172,7 @@ class Manager implements IMountManager { } /** - * @return MountPoint[] + * @return IMountPoint[] */ public function getAll(): array { return $this->mounts; @@ -187,7 +182,7 @@ class Manager implements IMountManager { * Find mounts by numeric storage id * * @param int $id - * @return MountPoint[] + * @return IMountPoint[] */ public function findByNumericId(int $id): array { $storageId = \OC\Files\Cache\Storage::getStorageId($id); @@ -205,4 +200,8 @@ class Manager implements IMountManager { } return $path; } + + public function getSetupManager(): SetupManager { + return $this->setupManager; + } } diff --git a/lib/private/Files/Mount/MountPoint.php b/lib/private/Files/Mount/MountPoint.php index 368be0a917e..d598355dbae 100644 --- a/lib/private/Files/Mount/MountPoint.php +++ b/lib/private/Files/Mount/MountPoint.php @@ -34,6 +34,7 @@ use OC\Files\Filesystem; use OC\Files\Storage\Storage; use OC\Files\Storage\StorageFactory; use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorageFactory; use OCP\ILogger; class MountPoint implements IMountPoint { @@ -76,6 +77,9 @@ class MountPoint implements IMountPoint { /** @var int|null */ protected $mountId; + /** @var string */ + protected $mountProvider; + /** * @param string|\OC\Files\Storage\Storage $storage * @param string $mountpoint @@ -83,9 +87,18 @@ class MountPoint implements IMountPoint { * @param \OCP\Files\Storage\IStorageFactory $loader * @param array $mountOptions mount specific options * @param int|null $mountId + * @param string|null $mountProvider * @throws \Exception */ - public function __construct($storage, $mountpoint, $arguments = null, $loader = null, $mountOptions = null, $mountId = null) { + public function __construct( + $storage, + string $mountpoint, + array $arguments = null, + IStorageFactory $loader = null, + array $mountOptions = null, + int $mountId = null, + string $mountProvider = null + ) { if (is_null($arguments)) { $arguments = []; } @@ -113,6 +126,12 @@ class MountPoint implements IMountPoint { $this->class = $storage; $this->arguments = $arguments; } + if ($mountProvider) { + if (strlen($mountProvider) > 128) { + throw new \Exception("Mount provider $mountProvider name exceeds the limit of 128 characters"); + } + } + $this->mountProvider = $mountProvider ?? ''; } /** @@ -286,4 +305,8 @@ class MountPoint implements IMountPoint { public function getMountType() { return ''; } + + public function getMountProvider(): string { + return $this->mountProvider; + } } diff --git a/lib/private/Files/Mount/ObjectHomeMountProvider.php b/lib/private/Files/Mount/ObjectHomeMountProvider.php index 972d3893e66..6a8a7d1f2fb 100644 --- a/lib/private/Files/Mount/ObjectHomeMountProvider.php +++ b/lib/private/Files/Mount/ObjectHomeMountProvider.php @@ -65,7 +65,7 @@ class ObjectHomeMountProvider implements IHomeMountProvider { return null; } - return new MountPoint('\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader); + return new MountPoint('\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class); } /** diff --git a/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php index 2830e37ded3..0043503f2cd 100644 --- a/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php +++ b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php @@ -69,7 +69,10 @@ class ObjectStorePreviewCacheMountProvider implements IRootMountProvider { AppdataPreviewObjectStoreStorage::class, '/appdata_' . $instanceId . '/preview/' . $parent . '/' . $child, $this->getMultiBucketObjectStore($i), - $loader + $loader, + null, + null, + self::class ); $i++; } @@ -87,7 +90,10 @@ class ObjectStorePreviewCacheMountProvider implements IRootMountProvider { $fakeRootStorageJail, '/appdata_' . $instanceId . '/preview/old-multibucket', null, - $loader + $loader, + null, + null, + self::class ); return $mountPoints; diff --git a/lib/private/Files/Mount/RootMountProvider.php b/lib/private/Files/Mount/RootMountProvider.php new file mode 100644 index 00000000000..b301fc6dd14 --- /dev/null +++ b/lib/private/Files/Mount/RootMountProvider.php @@ -0,0 +1,103 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> + * + * @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\Files\Mount; + +use OC; +use OC\Files\ObjectStore\ObjectStoreStorage; +use OC\Files\Storage\LocalRootStorage; +use OC_App; +use OCP\Files\Config\IRootMountProvider; +use OCP\Files\Storage\IStorageFactory; +use OCP\IConfig; +use Psr\Log\LoggerInterface; + +class RootMountProvider implements IRootMountProvider { + private IConfig $config; + private LoggerInterface $logger; + + public function __construct(IConfig $config, LoggerInterface $logger) { + $this->config = $config; + $this->logger = $logger; + } + + public function getRootMounts(IStorageFactory $loader): array { + $objectStore = $this->config->getSystemValue('objectstore', null); + $objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null); + + if ($objectStoreMultiBucket) { + return [$this->getMultiBucketStoreRootMount($loader, $objectStoreMultiBucket)]; + } elseif ($objectStore) { + return [$this->getObjectStoreRootMount($loader, $objectStore)]; + } else { + return [$this->getLocalRootMount($loader)]; + } + } + + private function validateObjectStoreConfig(array &$config) { + if (empty($config['class'])) { + $this->logger->error('No class given for objectstore', ['app' => 'files']); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + + // instantiate object store implementation + $name = $config['class']; + if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) { + $segments = explode('\\', $name); + OC_App::loadApp(strtolower($segments[1])); + } + } + + private function getLocalRootMount(IStorageFactory $loader): MountPoint { + $configDataDirectory = $this->config->getSystemValue("datadirectory", OC::$SERVERROOT . "/data"); + return new MountPoint(LocalRootStorage::class, '/', ['datadir' => $configDataDirectory], $loader, null, null, self::class); + } + + private function getObjectStoreRootMount(IStorageFactory $loader, array $config): MountPoint { + $this->validateObjectStoreConfig($config); + + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + // mount with plain / root object store implementation + $config['class'] = ObjectStoreStorage::class; + + return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class); + } + + private function getMultiBucketStoreRootMount(IStorageFactory $loader, array $config): MountPoint { + $this->validateObjectStoreConfig($config); + + if (!isset($config['arguments']['bucket'])) { + $config['arguments']['bucket'] = ''; + } + // put the root FS always in first bucket for multibucket configuration + $config['arguments']['bucket'] .= '0'; + + $config['arguments']['objectstore'] = new $config['class']($config['arguments']); + // mount with plain / root object store implementation + $config['class'] = ObjectStoreStorage::class; + + return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class); + } +} diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index 55421257886..bfdaeeccff7 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -38,7 +38,7 @@ class LazyFolder implements \OCP\Files\Folder { private $folderClosure; /** @var LazyFolder | null */ - private $folder = null; + protected $folder = null; /** * LazyFolder constructor. diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index 4a86207f25a..88ac4a31d34 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -29,18 +29,23 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ + namespace OC\Files\Node; use OC\Cache\CappedMemoryCache; use OC\Files\Mount\Manager; use OC\Files\Mount\MountPoint; +use OC\Files\View; use OC\Hooks\PublicEmitter; use OC\User\NoUserException; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IUserMountCache; +use OCP\Files\Events\Node\FilesystemTornDownEvent; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\ILogger; +use OCP\IUser; use OCP\IUserManager; /** @@ -63,35 +68,32 @@ use OCP\IUserManager; * @package OC\Files\Node */ class Root extends Folder implements IRootFolder { - /** @var Manager */ - private $mountManager; - /** @var PublicEmitter */ - private $emitter; - /** @var null|\OC\User\User */ - private $user; - /** @var CappedMemoryCache */ - private $userFolderCache; - /** @var IUserMountCache */ - private $userMountCache; - /** @var ILogger */ - private $logger; - /** @var IUserManager */ - private $userManager; + private Manager $mountManager; + private PublicEmitter $emitter; + private ?IUser $user; + private CappedMemoryCache $userFolderCache; + private IUserMountCache $userMountCache; + private ILogger $logger; + private IUserManager $userManager; + private IEventDispatcher $eventDispatcher; /** - * @param \OC\Files\Mount\Manager $manager - * @param \OC\Files\View $view - * @param \OC\User\User|null $user + * @param Manager $manager + * @param View $view + * @param IUser|null $user * @param IUserMountCache $userMountCache * @param ILogger $logger * @param IUserManager $userManager */ - public function __construct($manager, - $view, - $user, - IUserMountCache $userMountCache, - ILogger $logger, - IUserManager $userManager) { + public function __construct( + $manager, + $view, + $user, + IUserMountCache $userMountCache, + ILogger $logger, + IUserManager $userManager, + IEventDispatcher $eventDispatcher + ) { parent::__construct($this, $view, ''); $this->mountManager = $manager; $this->user = $user; @@ -100,6 +102,9 @@ class Root extends Folder implements IRootFolder { $this->userMountCache = $userMountCache; $this->logger = $logger; $this->userManager = $userManager; + $eventDispatcher->addListener(FilesystemTornDownEvent::class, function () { + $this->userFolderCache = new CappedMemoryCache(); + }); } /** @@ -189,9 +194,9 @@ class Root extends Folder implements IRootFolder { /** * @param string $path - * @throws \OCP\Files\NotFoundException - * @throws \OCP\Files\NotPermittedException * @return Node + * @throws \OCP\Files\NotPermittedException + * @throws \OCP\Files\NotFoundException */ public function get($path) { $path = $this->normalizePath($path); @@ -212,8 +217,8 @@ class Root extends Folder implements IRootFolder { /** * @param string $targetPath - * @throws \OCP\Files\NotPermittedException * @return \OC\Files\Node\Node + * @throws \OCP\Files\NotPermittedException */ public function rename($targetPath) { throw new NotPermittedException(); @@ -225,8 +230,8 @@ class Root extends Folder implements IRootFolder { /** * @param string $targetPath - * @throws \OCP\Files\NotPermittedException * @return \OC\Files\Node\Node + * @throws \OCP\Files\NotPermittedException */ public function copy($targetPath) { throw new NotPermittedException(); @@ -266,21 +271,21 @@ class Root extends Folder implements IRootFolder { * @return int */ public function getId() { - return null; + return 0; } /** * @return array */ public function stat() { - return null; + return []; } /** * @return int */ public function getMTime() { - return null; + return 0; } /** @@ -288,14 +293,14 @@ class Root extends Folder implements IRootFolder { * @return int */ public function getSize($includeMounts = true) { - return null; + return 0; } /** * @return string */ public function getEtag() { - return null; + return ''; } /** @@ -392,10 +397,6 @@ class Root extends Folder implements IRootFolder { return $this->userFolderCache->get($userId); } - public function clearCache() { - $this->userFolderCache = new CappedMemoryCache(); - } - public function getUserMountCache() { return $this->userMountCache; } diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index 074f3a1df91..6492145fb63 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -30,6 +30,7 @@ class S3 implements IObjectStore { use S3ObjectTrait; public function __construct($parameters) { + $parameters['primary_storage'] = true; $this->parseParams($parameters); } diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php index b72b0ebee53..d6f42c455b4 100644 --- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php +++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php @@ -38,6 +38,7 @@ use Aws\S3\Exception\S3Exception; use Aws\S3\S3Client; use GuzzleHttp\Promise; use GuzzleHttp\Promise\RejectedPromise; +use OCP\ICertificateManager; use OCP\ILogger; trait S3ConnectionTrait { @@ -120,6 +121,15 @@ trait S3ConnectionTrait { ) ); + // since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage + if (!isset($this->params['primary_storage'])) { + /** @var ICertificateManager $certManager */ + $certManager = \OC::$server->get(ICertificateManager::class); + $certPath = $certManager->getAbsoluteBundlePath(); + } else { + $certPath = \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + } + $options = [ 'version' => isset($this->params['version']) ? $this->params['version'] : 'latest', 'credentials' => $provider, @@ -129,9 +139,10 @@ trait S3ConnectionTrait { 'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()), 'csm' => false, 'use_arn_region' => false, + 'http' => ['verify' => $certPath], ]; if ($this->getProxy()) { - $options['http'] = [ 'proxy' => $this->getProxy() ]; + $options['http']['proxy'] = $this->getProxy(); } if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) { $options['signature_version'] = 'v2'; diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index 769901acc79..4e54a26e98a 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -126,7 +126,7 @@ trait S3ObjectTrait { if ($e->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) { $this->getConnection()->abortMultipartUpload($uploadInfo); } - throw $e; + throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway("Error while uploading to S3 bucket", 0, $e); } } diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php new file mode 100644 index 00000000000..9726fbef428 --- /dev/null +++ b/lib/private/Files/SetupManager.php @@ -0,0 +1,294 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> + * + * @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\Files; + +use OC\Files\Config\MountProviderCollection; +use OC\Files\Mount\MountPoint; +use OC\Files\ObjectStore\HomeObjectStoreStorage; +use OC\Files\Storage\Common; +use OC\Files\Storage\Home; +use OC\Files\Storage\Storage; +use OC\Files\Storage\Wrapper\Availability; +use OC\Files\Storage\Wrapper\Encoding; +use OC\Files\Storage\Wrapper\PermissionsMask; +use OC\Files\Storage\Wrapper\Quota; +use OC\Lockdown\Filesystem\NullStorage; +use OC_App; +use OC_Hook; +use OC_Util; +use OCP\Constants; +use OCP\Diagnostics\IEventLogger; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Config\IMountProvider; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\Events\Node\FilesystemTornDownEvent; +use OCP\Files\Mount\IMountManager; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorage; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Lockdown\ILockdownManager; + +class SetupManager { + private bool $rootSetup = false; + private IEventLogger $eventLogger; + private MountProviderCollection $mountProviderCollection; + private IMountManager $mountManager; + private IUserManager $userManager; + private array $setupUsers = []; + private IEventDispatcher $eventDispatcher; + private IUserMountCache $userMountCache; + private ILockdownManager $lockdownManager; + private IUserSession $userSession; + private bool $listeningForProviders; + + public function __construct( + IEventLogger $eventLogger, + MountProviderCollection $mountProviderCollection, + IMountManager $mountManager, + IUserManager $userManager, + IEventDispatcher $eventDispatcher, + IUserMountCache $userMountCache, + ILockdownManager $lockdownManager, + IUserSession $userSession + ) { + $this->eventLogger = $eventLogger; + $this->mountProviderCollection = $mountProviderCollection; + $this->mountManager = $mountManager; + $this->userManager = $userManager; + $this->eventDispatcher = $eventDispatcher; + $this->userMountCache = $userMountCache; + $this->lockdownManager = $lockdownManager; + $this->userSession = $userSession; + $this->listeningForProviders = false; + } + + private function setupBuiltinWrappers() { + Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) { + if ($storage->instanceOfStorage(Common::class)) { + $storage->setMountOptions($mount->getOptions()); + } + return $storage; + }); + + Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, IStorage $storage, IMountPoint $mount) { + if (!$mount->getOption('enable_sharing', true)) { + return new PermissionsMask([ + 'storage' => $storage, + 'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE, + ]); + } + return $storage; + }); + + // install storage availability wrapper, before most other wrappers + Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage) { + if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { + return new Availability(['storage' => $storage]); + } + return $storage; + }); + + Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) { + if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { + return new Encoding(['storage' => $storage]); + } + return $storage; + }); + + Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) { + // set up quota for home storages, even for other users + // which can happen when using sharing + + /** + * @var Storage $storage + */ + if ($storage->instanceOfStorage(HomeObjectStoreStorage::class) || $storage->instanceOfStorage(Home::class)) { + if (is_object($storage->getUser())) { + $quota = OC_Util::getUserQuota($storage->getUser()); + if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { + return new Quota(['storage' => $storage, 'quota' => $quota, 'root' => 'files']); + } + } + } + + return $storage; + }); + + Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) { + /* + * Do not allow any operations that modify the storage + */ + if ($mount->getOption('readonly', false)) { + return new PermissionsMask([ + 'storage' => $storage, + 'mask' => Constants::PERMISSION_ALL & ~( + Constants::PERMISSION_UPDATE | + Constants::PERMISSION_CREATE | + Constants::PERMISSION_DELETE + ), + ]); + } + return $storage; + }); + } + + /** + * Setup the full filesystem for the specified user + */ + public function setupForUser(IUser $user): void { + $this->setupRoot(); + + if (in_array($user->getUID(), $this->setupUsers, true)) { + return; + } + $this->setupUsers[] = $user->getUID(); + + $this->eventLogger->start('setup_fs', 'Setup filesystem'); + + $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); + + OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]); + + Filesystem::logWarningWhenAddingStorageWrapper($prevLogging); + + $userDir = '/' . $user->getUID() . '/files'; + + Filesystem::init($user, $userDir); + + if ($this->lockdownManager->canAccessFilesystem()) { + // home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers + $homeMount = $this->mountProviderCollection->getHomeMountForUser($user); + $this->mountManager->addMount($homeMount); + + if ($homeMount->getStorageRootId() === -1) { + $homeMount->getStorage()->mkdir(''); + $homeMount->getStorage()->getScanner()->scan(''); + } + + // Chance to mount for other storages + $mounts = $this->mountProviderCollection->addMountForUser($user, $this->mountManager); + $mounts[] = $homeMount; + $this->userMountCache->registerMounts($user, $mounts); + + $this->listenForNewMountProviders(); + } else { + $this->mountManager->addMount(new MountPoint( + new NullStorage([]), + '/' . $user->getUID() + )); + $this->mountManager->addMount(new MountPoint( + new NullStorage([]), + '/' . $user->getUID() . '/files' + )); + } + \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]); + + OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]); + + $this->eventLogger->end('setup_fs'); + } + + /** + * Set up the root filesystem + */ + public function setupRoot(): void { + //setting up the filesystem twice can only lead to trouble + if ($this->rootSetup) { + return; + } + $this->rootSetup = true; + + $this->eventLogger->start('setup_root_fs', 'Setup root filesystem'); + + // load all filesystem apps before, so no setup-hook gets lost + OC_App::loadApps(['filesystem']); + $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); + + $this->setupBuiltinWrappers(); + + Filesystem::logWarningWhenAddingStorageWrapper($prevLogging); + + $rootMounts = $this->mountProviderCollection->getRootMounts(); + foreach ($rootMounts as $rootMountProvider) { + $this->mountManager->addMount($rootMountProvider); + } + + $this->eventLogger->end('setup_root_fs'); + } + + /** + * Set up the filesystem for the specified path + */ + public function setupForPath(string $path): void { + if (substr_count($path, '/') < 2) { + if ($user = $this->userSession->getUser()) { + $this->setupForUser($user); + } else { + $this->setupRoot(); + } + return; + } elseif (strpos($path, '/appdata_' . \OC_Util::getInstanceId()) === 0 || strpos($path, '/files_external/') === 0) { + $this->setupRoot(); + return; + } else { + [, $userId] = explode('/', $path); + } + + $user = $this->userManager->get($userId); + + if (!$user) { + $this->setupRoot(); + return; + } + + $this->setupForUser($user); + } + + public function tearDown() { + $this->setupUsers = []; + $this->rootSetup = false; + $this->mountManager->clear(); + $this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent()); + } + + /** + * Get mounts from mount providers that are registered after setup + */ + private function listenForNewMountProviders() { + if (!$this->listeningForProviders) { + $this->listeningForProviders = true; + $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (IMountProvider $provider) { + foreach ($this->setupUsers as $userId) { + $user = $this->userManager->get($userId); + if ($user) { + $mounts = $provider->getMountsForUser($user, Filesystem::getLoader()); + array_walk($mounts, [$this->mountManager, 'addMount']); + } + } + }); + } + } +} diff --git a/lib/private/Files/SetupManagerFactory.php b/lib/private/Files/SetupManagerFactory.php new file mode 100644 index 00000000000..56e70d09961 --- /dev/null +++ b/lib/private/Files/SetupManagerFactory.php @@ -0,0 +1,79 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> + * + * @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\Files; + +use OCP\Diagnostics\IEventLogger; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Config\IMountProviderCollection; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\Mount\IMountManager; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Lockdown\ILockdownManager; + +class SetupManagerFactory { + private IEventLogger $eventLogger; + private IMountProviderCollection $mountProviderCollection; + private IUserManager $userManager; + private IEventDispatcher $eventDispatcher; + private IUserMountCache $userMountCache; + private ILockdownManager $lockdownManager; + private IUserSession $userSession; + private ?SetupManager $setupManager; + + public function __construct( + IEventLogger $eventLogger, + IMountProviderCollection $mountProviderCollection, + IUserManager $userManager, + IEventDispatcher $eventDispatcher, + IUserMountCache $userMountCache, + ILockdownManager $lockdownManager, + IUserSession $userSession + ) { + $this->eventLogger = $eventLogger; + $this->mountProviderCollection = $mountProviderCollection; + $this->userManager = $userManager; + $this->eventDispatcher = $eventDispatcher; + $this->userMountCache = $userMountCache; + $this->lockdownManager = $lockdownManager; + $this->userSession = $userSession; + $this->setupManager = null; + } + + public function create(IMountManager $mountManager): SetupManager { + if (!$this->setupManager) { + $this->setupManager = new SetupManager( + $this->eventLogger, + $this->mountProviderCollection, + $mountManager, + $this->userManager, + $this->eventDispatcher, + $this->userMountCache, + $this->lockdownManager, + $this->userSession, + ); + } + return $this->setupManager; + } +} diff --git a/lib/private/Files/SimpleFS/NewSimpleFile.php b/lib/private/Files/SimpleFS/NewSimpleFile.php index f805403fd87..76fc69ebbe7 100644 --- a/lib/private/Files/SimpleFS/NewSimpleFile.php +++ b/lib/private/Files/SimpleFS/NewSimpleFile.php @@ -191,6 +191,17 @@ class NewSimpleFile implements ISimpleFile { } /** + * {@inheritDoc} + */ + public function getExtension(): string { + if ($this->file) { + return $this->file->getExtension(); + } else { + return \pathinfo($this->name, PATHINFO_EXTENSION); + } + } + + /** * Open the file as stream for reading, resulting resource can be operated as stream like the result from php's own fopen * * @return resource diff --git a/lib/private/Files/SimpleFS/SimpleFile.php b/lib/private/Files/SimpleFS/SimpleFile.php index baf9b24e020..21a2fd92dcb 100644 --- a/lib/private/Files/SimpleFS/SimpleFile.php +++ b/lib/private/Files/SimpleFS/SimpleFile.php @@ -159,6 +159,13 @@ class SimpleFile implements ISimpleFile { } /** + * {@inheritDoc} + */ + public function getExtension(): string { + return $this->file->getExtension(); + } + + /** * Open the file as stream for reading, resulting resource can be operated as stream like the result from php's own fopen * * @return resource diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index 7239c58a8a1..8e7e56e6ca2 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -52,6 +52,7 @@ use OC\Files\Storage\Wrapper\Jail; use OC\Files\Storage\Wrapper\Wrapper; use OCP\Files\EmptyFileNameException; use OCP\Files\FileNameTooLongException; +use OCP\Files\ForbiddenException; use OCP\Files\GenericFileException; use OCP\Files\InvalidCharacterInPathException; use OCP\Files\InvalidDirectoryException; @@ -702,6 +703,10 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage { * @inheritdoc */ public function getMetaData($path) { + if (Filesystem::isFileBlacklisted($path)) { + throw new ForbiddenException('Invalid path: ' . $path, false); + } + $permissions = $this->getPermissions($path); if (!$permissions & \OCP\Constants::PERMISSION_READ) { //can't read, nothing we can do @@ -880,7 +885,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage { if (is_resource($dh)) { $basePath = rtrim($directory, '/'); while (($file = readdir($dh)) !== false) { - if (!Filesystem::isIgnoredDir($file) && !Filesystem::isFileBlacklisted($file)) { + if (!Filesystem::isIgnoredDir($file)) { $childPath = $basePath . '/' . trim($file, '/'); $metadata = $this->getMetaData($childPath); if ($metadata !== null) { diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index 6406beaeebc..34ab9a5fc97 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -47,7 +47,9 @@ use OC\Files\Storage\Wrapper\Jail; use OCP\Constants; use OCP\Files\ForbiddenException; use OCP\Files\GenericFileException; +use OCP\Files\IMimeTypeDetector; use OCP\Files\Storage\IStorage; +use OCP\IConfig; use OCP\ILogger; /** @@ -60,6 +62,10 @@ class Local extends \OC\Files\Storage\Common { protected $realDataDir; + private IConfig $config; + + private IMimeTypeDetector $mimeTypeDetector; + public function __construct($arguments) { if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) { throw new \InvalidArgumentException('No data directory set for local storage'); @@ -76,6 +82,8 @@ class Local extends \OC\Files\Storage\Common { $this->datadir .= '/'; } $this->dataDirLength = strlen($this->realDataDir); + $this->config = \OC::$server->get(IConfig::class); + $this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class); } public function __destruct() { @@ -155,6 +163,9 @@ class Local extends \OC\Files\Storage\Common { $statResult['size'] = $filesize; $statResult[7] = $filesize; } + if (is_array($statResult)) { + $statResult['full_path'] = $fullPath; + } return $statResult; } @@ -181,15 +192,14 @@ class Local extends \OC\Files\Storage\Common { } if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions - $fullPath = $this->getSourcePath($path); - $parent = dirname($fullPath); + $parent = dirname($stat['full_path']); if (is_writable($parent)) { $permissions += Constants::PERMISSION_DELETE; } } $data = []; - $data['mimetype'] = $isDir ? 'httpd/unix-directory' : \OC::$server->getMimeTypeDetector()->detectPath($path); + $data['mimetype'] = $isDir ? 'httpd/unix-directory' : $this->mimeTypeDetector->detectPath($path); $data['mtime'] = $stat['mtime']; if ($data['mtime'] === false) { $data['mtime'] = time(); @@ -450,7 +460,7 @@ class Local extends \OC\Files\Storage\Common { $fullPath = $this->datadir . $path; $currentPath = $path; - $allowSymlinks = \OC::$server->getConfig()->getSystemValue('localstorage.allowsymlinks', false); + $allowSymlinks = $this->config->getSystemValue('localstorage.allowsymlinks', false); if ($allowSymlinks || $currentPath === '') { return $fullPath; } diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 3cdc6166840..779e0611591 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -274,7 +274,7 @@ class View { /** * remove mount point * - * @param \OC\Files\Mount\MoveableMount $mount + * @param IMountPoint $mount * @param string $path relative to data/ * @return boolean */ @@ -719,7 +719,7 @@ class View { $postFix = (substr($path, -1) === '/') ? '/' : ''; $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); $mount = Filesystem::getMountManager()->find($absolutePath . $postFix); - if ($mount and $mount->getInternalPath($absolutePath) === '') { + if ($mount->getInternalPath($absolutePath) === '') { return $this->removeMount($mount, $absolutePath); } if ($this->is_dir($path)) { @@ -1383,10 +1383,6 @@ class View { $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); $mount = Filesystem::getMountManager()->find($path); - if (!$mount) { - \OC::$server->getLogger()->warning('Mountpoint not found for path: ' . $path); - return false; - } $storage = $mount->getStorage(); $internalPath = $mount->getInternalPath($path); if ($storage) { @@ -1488,7 +1484,7 @@ class View { $rootEntry = $subCache->get(''); if (!$rootEntry) { - $subScanner = $subStorage->getScanner(''); + $subScanner = $subStorage->getScanner(); try { $subScanner->scanFile(''); } catch (\OCP\Files\StorageNotAvailableException $e) { @@ -1739,12 +1735,13 @@ class View { $manager = Filesystem::getMountManager(); $mounts = $manager->findIn($this->fakeRoot); $mounts[] = $manager->find($this->fakeRoot); - // reverse the array so we start with the storage this view is in + $mounts = array_filter($mounts); + // reverse the array, so we start with the storage this view is in // which is the most likely to contain the file we're looking for $mounts = array_reverse($mounts); - // put non shared mounts in front of the shared mount - // this prevent unneeded recursion into shares + // put non-shared mounts in front of the shared mount + // this prevents unneeded recursion into shares usort($mounts, function (IMountPoint $a, IMountPoint $b) { return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1; }); @@ -1915,14 +1912,10 @@ class View { * @param string $absolutePath absolute path * @param bool $useParentMount true to return parent mount instead of whatever * is mounted directly on the given path, false otherwise - * @return \OC\Files\Mount\MountPoint mount point for which to apply locks + * @return IMountPoint mount point for which to apply locks */ private function getMountForLock($absolutePath, $useParentMount = false) { - $results = []; $mount = Filesystem::getMountManager()->find($absolutePath); - if (!$mount) { - return $results; - } if ($useParentMount) { // find out if something is mounted directly on the path diff --git a/lib/private/Group/MetaData.php b/lib/private/Group/MetaData.php index fb10caa86ea..a58d7e78bfc 100644 --- a/lib/private/Group/MetaData.php +++ b/lib/private/Group/MetaData.php @@ -30,6 +30,7 @@ namespace OC\Group; use OC\Group\Manager as GroupManager; +use OCP\IGroup; use OCP\IGroupManager; use OCP\IUserSession; @@ -46,25 +47,23 @@ class MetaData { protected $metaData = []; /** @var GroupManager */ protected $groupManager; - /** @var bool */ - protected $sorting = false; + /** @var int */ + protected $sorting = self::SORT_NONE; /** @var IUserSession */ protected $userSession; /** * @param string $user the uid of the current user * @param bool $isAdmin whether the current users is an admin - * @param IGroupManager $groupManager - * @param IUserSession $userSession */ public function __construct( - $user, - $isAdmin, + string $user, + bool $isAdmin, IGroupManager $groupManager, IUserSession $userSession ) { $this->user = $user; - $this->isAdmin = (bool)$isAdmin; + $this->isAdmin = $isAdmin; $this->groupManager = $groupManager; $this->userSession = $userSession; } @@ -77,9 +76,8 @@ class MetaData { * @param string $groupSearch only effective when instance was created with * isAdmin being true * @param string $userSearch the pattern users are search for - * @return array */ - public function get($groupSearch = '', $userSearch = '') { + public function get(string $groupSearch = '', string $userSearch = ''): array { $key = $groupSearch . '::' . $userSearch; if (isset($this->metaData[$key])) { return $this->metaData[$key]; @@ -122,10 +120,8 @@ class MetaData { /** * sets the sort mode, see SORT_* constants for supported modes - * - * @param int $sortMode */ - public function setSorting($sortMode) { + public function setSorting(int $sortMode): void { switch ($sortMode) { case self::SORT_USERCOUNT: case self::SORT_GROUPNAME: @@ -144,7 +140,7 @@ class MetaData { * @param int $sortIndex the sort key index, by reference * @param array $data the group's meta data as returned by generateGroupMetaData() */ - private function addEntry(&$entries, &$sortKeys, &$sortIndex, $data) { + private function addEntry(array &$entries, array &$sortKeys, int &$sortIndex, array $data): void { $entries[] = $data; if ($this->sorting === self::SORT_USERCOUNT) { $sortKeys[$sortIndex] = $data['usercount']; @@ -157,11 +153,9 @@ class MetaData { /** * creates an array containing the group meta data - * @param \OCP\IGroup $group - * @param string $userSearch * @return array with the keys 'id', 'name', 'usercount' and 'disabled' */ - private function generateGroupMetaData(\OCP\IGroup $group, $userSearch) { + private function generateGroupMetaData(IGroup $group, string $userSearch): array { return [ 'id' => $group->getGID(), 'name' => $group->getDisplayName(), @@ -176,9 +170,8 @@ class MetaData { * sorts the result array, if applicable * @param array $entries the result array, by reference * @param array $sortKeys the array containing the sort keys - * @param return null */ - private function sort(&$entries, $sortKeys) { + private function sort(array &$entries, array $sortKeys): void { if ($this->sorting === self::SORT_USERCOUNT) { array_multisort($sortKeys, SORT_DESC, $entries); } elseif ($this->sorting === self::SORT_GROUPNAME) { @@ -188,10 +181,9 @@ class MetaData { /** * returns the available groups - * @param string $search a search string - * @return \OCP\IGroup[] + * @return IGroup[] */ - public function getGroups($search = '') { + public function getGroups(string $search = ''): array { if ($this->isAdmin) { return $this->groupManager->search($search); } else { diff --git a/lib/private/Log.php b/lib/private/Log.php index edbfdea7b9d..0415967f0f0 100644 --- a/lib/private/Log.php +++ b/lib/private/Log.php @@ -232,7 +232,7 @@ class Log implements ILogger, IDataLogger { } } - private function getLogLevel($context) { + public function getLogLevel($context) { $logCondition = $this->config->getValue('log.condition', []); /** @@ -308,7 +308,13 @@ class Log implements ILogger, IDataLogger { $app = $context['app'] ?? 'no app in context'; $level = $context['level'] ?? ILogger::ERROR; - $serializer = new ExceptionSerializer($this->config); + // if an error is raised before the autoloader is properly setup, we can't serialize exceptions + try { + $serializer = new ExceptionSerializer($this->config); + } catch (\Throwable $e) { + $this->error("Failed to load ExceptionSerializer serializer while trying to log " . $exception->getMessage()); + return; + } $data = $serializer->serializeException($exception); $data['CustomMessage'] = $this->interpolateMessage($context, $context['message'] ?? '--'); diff --git a/lib/private/Preview/Bitmap.php b/lib/private/Preview/Bitmap.php index 57451da5725..3a4108664dd 100644 --- a/lib/private/Preview/Bitmap.php +++ b/lib/private/Preview/Bitmap.php @@ -29,7 +29,7 @@ namespace OC\Preview; use Imagick; use OCP\Files\File; use OCP\IImage; -use OCP\ILogger; +use Psr\Log\LoggerInterface; /** * Creates a PNG preview using ImageMagick via the PECL extension @@ -43,16 +43,25 @@ abstract class Bitmap extends ProviderV2 { */ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { $tmpPath = $this->getLocalFile($file); + if ($tmpPath === false) { + \OC::$server->get(LoggerInterface::class)->error( + 'Failed to get thumbnail for: ' . $file->getPath(), + ['app' => 'core'] + ); + return null; + } // Creates \Imagick object from bitmap or vector file try { $bp = $this->getResizedPreview($tmpPath, $maxX, $maxY); } catch (\Exception $e) { - \OC::$server->getLogger()->logException($e, [ - 'message' => 'File: ' . $file->getPath() . ' Imagick says:', - 'level' => ILogger::ERROR, - 'app' => 'core', - ]); + \OC::$server->get(LoggerInterface::class)->error( + 'File: ' . $file->getPath() . ' Imagick says:', + [ + 'exception' => $e, + 'app' => 'core', + ] + ); return null; } diff --git a/lib/private/Preview/Bundled.php b/lib/private/Preview/Bundled.php index f026d3259f5..063c69ba5dd 100644 --- a/lib/private/Preview/Bundled.php +++ b/lib/private/Preview/Bundled.php @@ -30,9 +30,11 @@ use OCP\IImage; * Extracts a preview from files that embed them in an ZIP archive */ abstract class Bundled extends ProviderV2 { - protected function extractThumbnail(File $file, $path): ?IImage { + protected function extractThumbnail(File $file, string $path): ?IImage { $sourceTmp = \OC::$server->getTempManager()->getTemporaryFile(); $targetTmp = \OC::$server->getTempManager()->getTemporaryFile(); + $this->tmpFiles[] = $sourceTmp; + $this->tmpFiles[] = $targetTmp; try { $content = $file->fopen('r'); @@ -46,7 +48,7 @@ abstract class Bundled extends ProviderV2 { $image->fixOrientation(); return $image; - } catch (\Exception $e) { + } catch (\Throwable $e) { return null; } } diff --git a/lib/private/Preview/HEIC.php b/lib/private/Preview/HEIC.php index 6601de238a9..7ce6b93ba3b 100644 --- a/lib/private/Preview/HEIC.php +++ b/lib/private/Preview/HEIC.php @@ -32,7 +32,7 @@ namespace OC\Preview; use OCP\Files\File; use OCP\Files\FileInfo; use OCP\IImage; -use OCP\ILogger; +use Psr\Log\LoggerInterface; /** * Creates a JPG preview using ImageMagick via the PECL extension @@ -63,17 +63,26 @@ class HEIC extends ProviderV2 { } $tmpPath = $this->getLocalFile($file); + if ($tmpPath === false) { + \OC::$server->get(LoggerInterface::class)->error( + 'Failed to get thumbnail for: ' . $file->getPath(), + ['app' => 'core'] + ); + return null; + } // Creates \Imagick object from the heic file try { $bp = $this->getResizedPreview($tmpPath, $maxX, $maxY); $bp->setFormat('jpg'); } catch (\Exception $e) { - \OC::$server->getLogger()->logException($e, [ - 'message' => 'File: ' . $file->getPath() . ' Imagick says:', - 'level' => ILogger::ERROR, - 'app' => 'core', - ]); + \OC::$server->get(LoggerInterface::class)->error( + 'File: ' . $file->getPath() . ' Imagick says:', + [ + 'exception' => $e, + 'app' => 'core', + ] + ); return null; } @@ -109,7 +118,7 @@ class HEIC extends ProviderV2 { $bp->setImageFormat('jpg'); $bp = $this->resize($bp, $maxX, $maxY); - + return $bp; } diff --git a/lib/private/Preview/Krita.php b/lib/private/Preview/Krita.php index ec7be4b82cf..eb25db9928c 100644 --- a/lib/private/Preview/Krita.php +++ b/lib/private/Preview/Krita.php @@ -39,11 +39,11 @@ class Krita extends Bundled { */ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { $image = $this->extractThumbnail($file, 'mergedimage.png'); - if ($image->valid()) { + if (($image !== null) && $image->valid()) { return $image; } $image = $this->extractThumbnail($file, 'preview.png'); - if ($image->valid()) { + if (($image !== null) && $image->valid()) { return $image; } return null; diff --git a/lib/private/Preview/Movie.php b/lib/private/Preview/Movie.php index a6e424caa2a..781cbad1954 100644 --- a/lib/private/Preview/Movie.php +++ b/lib/private/Preview/Movie.php @@ -99,11 +99,14 @@ class Movie extends ProviderV2 { foreach ($sizeAttempts as $size) { $absPath = $this->getLocalFile($file, $size); - $result = $this->generateThumbNail($maxX, $maxY, $absPath, 5); - if ($result === null) { - $result = $this->generateThumbNail($maxX, $maxY, $absPath, 1); + $result = null; + if (is_string($absPath)) { + $result = $this->generateThumbNail($maxX, $maxY, $absPath, 5); if ($result === null) { - $result = $this->generateThumbNail($maxX, $maxY, $absPath, 0); + $result = $this->generateThumbNail($maxX, $maxY, $absPath, 1); + if ($result === null) { + $result = $this->generateThumbNail($maxX, $maxY, $absPath, 0); + } } } @@ -117,25 +120,18 @@ class Movie extends ProviderV2 { return $result; } - /** - * @param int $maxX - * @param int $maxY - * @param string $absPath - * @param int $second - * @return null|\OCP\IImage - */ - private function generateThumbNail($maxX, $maxY, $absPath, $second): ?IImage { + private function generateThumbNail(int $maxX, int $maxY, string $absPath, int $second): ?IImage { $tmpPath = \OC::$server->getTempManager()->getTemporaryFile(); $binaryType = substr(strrchr($this->binary, '/'), 1); if ($binaryType === 'avconv') { - $cmd = $this->binary . ' -y -ss ' . escapeshellarg($second) . + $cmd = $this->binary . ' -y -ss ' . escapeshellarg((string)$second) . ' -i ' . escapeshellarg($absPath) . ' -an -f mjpeg -vframes 1 -vsync 1 ' . escapeshellarg($tmpPath) . ' 2>&1'; } elseif ($binaryType === 'ffmpeg') { - $cmd = $this->binary . ' -y -ss ' . escapeshellarg($second) . + $cmd = $this->binary . ' -y -ss ' . escapeshellarg((string)$second) . ' -i ' . escapeshellarg($absPath) . ' -f mjpeg -vframes 1' . ' ' . escapeshellarg($tmpPath) . diff --git a/lib/private/Preview/OpenDocument.php b/lib/private/Preview/OpenDocument.php index 8deb9fd0a1e..5f27e325d31 100644 --- a/lib/private/Preview/OpenDocument.php +++ b/lib/private/Preview/OpenDocument.php @@ -42,7 +42,7 @@ class OpenDocument extends Bundled { */ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { $image = $this->extractThumbnail($file, 'Thumbnails/thumbnail.png'); - if ($image->valid()) { + if (($image !== null) && $image->valid()) { return $image; } return null; diff --git a/lib/private/Preview/ProviderV2.php b/lib/private/Preview/ProviderV2.php index 4323f149702..0cb7eb59e21 100644 --- a/lib/private/Preview/ProviderV2.php +++ b/lib/private/Preview/ProviderV2.php @@ -35,7 +35,7 @@ abstract class ProviderV2 implements IProviderV2 { protected $options; /** @var array */ - private $tmpFiles = []; + protected $tmpFiles = []; /** * Constructor @@ -72,7 +72,7 @@ abstract class ProviderV2 implements IProviderV2 { */ abstract public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage; - protected function useTempFile(File $file) { + protected function useTempFile(File $file): bool { return $file->isEncrypted() || !$file->getStorage()->isLocal(); } @@ -81,9 +81,9 @@ abstract class ProviderV2 implements IProviderV2 { * * @param File $file * @param int $maxSize maximum size for temporary files - * @return string + * @return string|false */ - protected function getLocalFile(File $file, int $maxSize = null): string { + protected function getLocalFile(File $file, int $maxSize = null) { if ($this->useTempFile($file)) { $absPath = \OC::$server->getTempManager()->getTemporaryFile(); @@ -97,14 +97,19 @@ abstract class ProviderV2 implements IProviderV2 { $this->tmpFiles[] = $absPath; return $absPath; } else { - return $file->getStorage()->getLocalFile($file->getInternalPath()); + $path = $file->getStorage()->getLocalFile($file->getInternalPath()); + if (is_string($path)) { + return $path; + } else { + return false; + } } } /** * Clean any generated temporary files */ - protected function cleanTmpFiles() { + protected function cleanTmpFiles(): void { foreach ($this->tmpFiles as $tmpFile) { unlink($tmpFile); } diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php index c4317b294f3..edb51458c66 100644 --- a/lib/private/Profile/ProfileManager.php +++ b/lib/private/Profile/ProfileManager.php @@ -40,6 +40,7 @@ use OCP\Accounts\IAccountManager; use OCP\Accounts\PropertyDoesNotExistException; use OCP\App\IAppManager; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\IConfig; use OCP\IUser; use OCP\L10N\IFactory; use OCP\Profile\ILinkAction; @@ -54,6 +55,9 @@ class ProfileManager { /** @var IAppManager */ private $appManager; + /** @var IConfig */ + private $config; + /** @var ProfileConfigMapper */ private $configMapper; @@ -106,6 +110,7 @@ class ProfileManager { public function __construct( IAccountManager $accountManager, IAppManager $appManager, + IConfig $config, ProfileConfigMapper $configMapper, ContainerInterface $container, KnownUserService $knownUserService, @@ -115,6 +120,7 @@ class ProfileManager { ) { $this->accountManager = $accountManager; $this->appManager = $appManager; + $this->config = $config; $this->configMapper = $configMapper; $this->container = $container; $this->knownUserService = $knownUserService; @@ -124,6 +130,24 @@ class ProfileManager { } /** + * If no user is passed as an argument return whether profile is enabled globally in `config.php` + */ + public function isProfileEnabled(?IUser $user = null): ?bool { + $profileEnabledGlobally = $this->config->getSystemValueBool('profile.enabled', true); + + if (empty($user) || !$profileEnabledGlobally) { + return $profileEnabledGlobally; + } + + $account = $this->accountManager->getAccount($user); + return filter_var( + $account->getProperty(IAccountManager::PROPERTY_PROFILE_ENABLED)->getValue(), + FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE, + ); + } + + /** * Register an action for the user */ private function registerAction(ILinkAction $action, IUser $targetUser, ?IUser $visitingUser): void { diff --git a/lib/private/Profile/TProfileHelper.php b/lib/private/Profile/TProfileHelper.php index 0d4b5c6286e..5b57e1c9d5c 100644 --- a/lib/private/Profile/TProfileHelper.php +++ b/lib/private/Profile/TProfileHelper.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * @copyright 2021 Christopher Ng <chrng8@gmail.com> + * @copyright 2022 Christopher Ng <chrng8@gmail.com> * * @author Christopher Ng <chrng8@gmail.com> * @@ -26,19 +26,12 @@ declare(strict_types=1); namespace OC\Profile; -use OCP\Accounts\IAccount; -use OCP\Accounts\IAccountManager; +use OCP\IConfig; trait TProfileHelper { - - /** - * Returns whether the profile is enabled for the account - * - * @since 23.0.0 - */ - protected function isProfileEnabled(IAccount $account): ?bool { + protected function isProfileEnabledByDefault(IConfig $config): ?bool { return filter_var( - $account->getProperty(IAccountManager::PROPERTY_PROFILE_ENABLED)->getValue(), + $config->getAppValue('settings', 'profile_enabled_by_default', '1'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE, ); diff --git a/lib/private/RedisFactory.php b/lib/private/RedisFactory.php index 88d22cf9d13..5c38ed0807d 100644 --- a/lib/private/RedisFactory.php +++ b/lib/private/RedisFactory.php @@ -26,6 +26,8 @@ */ namespace OC; +use OCP\Diagnostics\IEventLogger; + class RedisFactory { public const REDIS_MINIMAL_VERSION = '3.1.3'; public const REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION = '5.3.0'; @@ -33,16 +35,18 @@ class RedisFactory { /** @var \Redis|\RedisCluster */ private $instance; - /** @var SystemConfig */ - private $config; + private SystemConfig $config; + + private IEventLogger $eventLogger; /** * RedisFactory constructor. * * @param SystemConfig $config */ - public function __construct(SystemConfig $config) { + public function __construct(SystemConfig $config, IEventLogger $eventLogger) { $this->config = $config; + $this->eventLogger = $eventLogger; } private function create() { @@ -113,6 +117,7 @@ class RedisFactory { $port = null; } + $this->eventLogger->start('connect:redis', 'Connect to redis and send AUTH, SELECT'); // Support for older phpredis versions not supporting connectionParameters if ($connectionParameters !== null) { // Non-clustered redis requires connection parameters to be wrapped inside `stream` @@ -141,6 +146,7 @@ class RedisFactory { if (isset($config['dbindex'])) { $this->instance->select($config['dbindex']); } + $this->eventLogger->end('connect:redis'); } } diff --git a/lib/private/Security/CSP/ContentSecurityPolicy.php b/lib/private/Security/CSP/ContentSecurityPolicy.php index 78517f639a7..8a72934d4c9 100644 --- a/lib/private/Security/CSP/ContentSecurityPolicy.php +++ b/lib/private/Security/CSP/ContentSecurityPolicy.php @@ -244,4 +244,11 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy public function setReportTo(array $reportTo) { $this->reportTo = $reportTo; } + + /** + * @param boolean $strictDynamicAllowed + */ + public function setStrictDynamicAllowed(bool $strictDynamicAllowed) { + $this->strictDynamicAllowed = $strictDynamicAllowed; + } } diff --git a/lib/private/Security/CertificateManager.php b/lib/private/Security/CertificateManager.php index 0c6791163c2..6f3b01e23b9 100644 --- a/lib/private/Security/CertificateManager.php +++ b/lib/private/Security/CertificateManager.php @@ -240,15 +240,19 @@ class CertificateManager implements ICertificateManager { * @return string */ public function getAbsoluteBundlePath(): string { - if (!$this->hasCertificates()) { - return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; - } + try { + if (!$this->hasCertificates()) { + return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + } - if ($this->needsRebundling()) { - $this->createCertificateBundle(); - } + if ($this->needsRebundling()) { + $this->createCertificateBundle(); + } - return $this->view->getLocalFile($this->getCertificateBundle()); + return $this->view->getLocalFile($this->getCertificateBundle()); + } catch (\Exception $e) { + return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + } } /** diff --git a/lib/private/Server.php b/lib/private/Server.php index 13bbf972abb..00afaf1d6a9 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -91,9 +91,11 @@ use OC\Files\Mount\CacheMountProvider; use OC\Files\Mount\LocalHomeMountProvider; use OC\Files\Mount\ObjectHomeMountProvider; use OC\Files\Mount\ObjectStorePreviewCacheMountProvider; +use OC\Files\Mount\RootMountProvider; use OC\Files\Node\HookConnector; use OC\Files\Node\LazyRoot; use OC\Files\Node\Root; +use OC\Files\SetupManager; use OC\Files\Storage\StorageFactory; use OC\Files\Template\TemplateManager; use OC\Files\Type\Loader; @@ -142,6 +144,7 @@ use OC\Share20\ProviderFactory; use OC\Share20\ShareHelper; use OC\SystemTag\ManagerFactory as SystemTagManagerFactory; use OC\Tagging\TagMapper; +use OC\Talk\Broker; use OC\Template\JSCombiner; use OCA\Theming\ImageManager; use OCA\Theming\ThemingDefaults; @@ -215,6 +218,7 @@ use OCP\L10N\IFactory; use OCP\LDAP\ILDAPProvider; use OCP\LDAP\ILDAPProviderFactory; use OCP\Lock\ILockingProvider; +use OCP\Lockdown\ILockdownManager; use OCP\Log\ILogFactory; use OCP\Mail\IMailer; use OCP\Remote\Api\IApiFactory; @@ -231,6 +235,7 @@ use OCP\Security\VerificationToken\IVerificationToken; use OCP\Share\IShareHelper; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; +use OCP\Talk\IBroker; use OCP\User\Events\BeforePasswordUpdatedEvent; use OCP\User\Events\BeforeUserCreatedEvent; use OCP\User\Events\BeforeUserDeletedEvent; @@ -422,7 +427,8 @@ class Server extends ServerContainer implements IServerContainer { null, $c->get(IUserMountCache::class), $this->get(ILogger::class), - $this->get(IUserManager::class) + $this->get(IUserManager::class), + $this->get(IEventDispatcher::class), ); $previewConnector = new \OC\Preview\WatcherConnector( @@ -718,7 +724,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('RedisFactory', function (Server $c) { $systemConfig = $c->get(SystemConfig::class); - return new RedisFactory($systemConfig); + return new RedisFactory($systemConfig, $c->getEventLogger()); }); $this->registerService(\OCP\Activity\IManager::class, function (Server $c) { @@ -871,12 +877,7 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerDeprecatedAlias('HttpClientService', IClientService::class); $this->registerService(IEventLogger::class, function (ContainerInterface $c) { - $eventLogger = new EventLogger(); - if ($c->get(SystemConfig::class)->getValue('debug', false)) { - // In debug mode, module is being activated by default - $eventLogger->activate(); - } - return $eventLogger; + return new EventLogger($c->get(SystemConfig::class), $c->get(LoggerInterface::class), $c->get(Log::class)); }); /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('EventLogger', IEventLogger::class); @@ -952,6 +953,7 @@ class Server extends ServerContainer implements IServerContainer { $manager->registerProvider(new CacheMountProvider($config)); $manager->registerHomeProvider(new LocalHomeMountProvider()); $manager->registerHomeProvider(new ObjectHomeMountProvider($config)); + $manager->registerRootProvider(new RootMountProvider($config, $c->get(LoggerInterface::class))); $manager->registerRootProvider(new ObjectStorePreviewCacheMountProvider($logger, $config)); return $manager; @@ -1094,6 +1096,11 @@ class Server extends ServerContainer implements IServerContainer { /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('LockingProvider', ILockingProvider::class); + $this->registerAlias(ILockdownManager::class, 'LockdownManager'); + $this->registerService(SetupManager::class, function ($c) { + // create the setupmanager through the mount manager to resolve the cyclic dependency + return $c->get(\OC\Files\Mount\Manager::class)->getSetupManager(); + }); $this->registerAlias(IMountManager::class, \OC\Files\Mount\Manager::class); /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('MountManager', IMountManager::class); @@ -1395,6 +1402,8 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(\OCP\UserStatus\IManager::class, \OC\UserStatus\Manager::class); + $this->registerAlias(IBroker::class, Broker::class); + $this->connectDispatcher(); } diff --git a/lib/private/Support/Subscription/Registry.php b/lib/private/Support/Subscription/Registry.php index 1298337acb2..ba3642d021c 100644 --- a/lib/private/Support/Subscription/Registry.php +++ b/lib/private/Support/Subscription/Registry.php @@ -34,6 +34,7 @@ use OCP\IGroupManager; use OCP\IServerContainer; use OCP\IUserManager; use OCP\Notification\IManager; +use OCP\User\Backend\ICountUsersBackend; use OCP\Support\Subscription\Exception\AlreadyRegisteredException; use OCP\Support\Subscription\IRegistry; use OCP\Support\Subscription\ISubscription; @@ -189,6 +190,7 @@ class Registry implements IRegistry { $backends = $this->userManager->getBackends(); foreach ($backends as $backend) { if ($backend->implementsActions(Backend::COUNT_USERS)) { + /** @var ICountUsersBackend $backend */ $backendUsers = $backend->countUsers(); if ($backendUsers !== false) { $userCount += $backendUsers; diff --git a/lib/private/SystemConfig.php b/lib/private/SystemConfig.php index c435b9180b9..0bc6154fbc4 100644 --- a/lib/private/SystemConfig.php +++ b/lib/private/SystemConfig.php @@ -43,6 +43,10 @@ class SystemConfig { 'dbhost' => true, 'dbpassword' => true, 'dbuser' => true, + 'activity_dbname' => true, + 'activity_dbhost' => true, + 'activity_dbpassword' => true, + 'activity_dbuser' => true, 'mail_from_address' => true, 'mail_domain' => true, 'mail_smtphost' => true, @@ -53,6 +57,13 @@ class SystemConfig { 'updater.secret' => true, 'trusted_proxies' => true, 'proxyuserpwd' => true, + 'sentry.dsn' => true, + 'sentry.public-dsn' => true, + 'zammad.download.secret' => true, + 'zammad.portal.secret' => true, + 'zammad.secret' => true, + 'github.client_id' => true, + 'github.client_secret' => true, 'log.condition' => [ 'shared_secret' => true, ], diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php index 1421e28033a..bf84e1ae958 100644 --- a/lib/private/Template/JSResourceLocator.php +++ b/lib/private/Template/JSResourceLocator.php @@ -74,7 +74,7 @@ class JSResourceLocator extends ResourceLocator { || $this->cacheAndAppendCombineJsonIfExist($this->serverroot, $script.'.json') || $this->appendIfExist($this->serverroot, $theme_dir.'core/'.$script.'.js') || $this->appendIfExist($this->serverroot, 'core/'.$script.'.js') - || $this->appendIfExist($this->serverroot, "dist/core-$scriptName.js") + || (strpos($scriptName, '/') === -1 && $this->appendIfExist($this->serverroot, "dist/core-$scriptName.js")) || $this->cacheAndAppendCombineJsonIfExist($this->serverroot, 'core/'.$script.'.json') ) { return; diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index 81094d4d8af..a9464c27085 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -193,7 +193,13 @@ class Database extends ABackend implements $hasher = \OC::$server->getHasher(); $hashedPassword = $hasher->hash($password); - return $this->updatePassword($uid, $hashedPassword); + $return = $this->updatePassword($uid, $hashedPassword); + + if ($return) { + $this->cache[$uid]['password'] = $hashedPassword; + } + + return $return; } return false; @@ -329,28 +335,16 @@ class Database extends ABackend implements * returns the user id or false */ public function checkPassword(string $loginName, string $password) { - $this->fixDI(); + $found = $this->loadUser($loginName); - $qb = $this->dbConn->getQueryBuilder(); - $qb->select('uid', 'password') - ->from($this->table) - ->where( - $qb->expr()->eq( - 'uid_lower', $qb->createNamedParameter(mb_strtolower($loginName)) - ) - ); - $result = $qb->execute(); - $row = $result->fetch(); - $result->closeCursor(); - - if ($row) { - $storedHash = $row['password']; + if ($found && is_array($this->cache[$loginName])) { + $storedHash = $this->cache[$loginName]['password']; $newHash = ''; if (\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) { if (!empty($newHash)) { $this->updatePassword($loginName, $newHash); } - return (string)$row['uid']; + return (string)$this->cache[$loginName]['uid']; } } @@ -375,7 +369,7 @@ class Database extends ABackend implements } $qb = $this->dbConn->getQueryBuilder(); - $qb->select('uid', 'displayname') + $qb->select('uid', 'displayname', 'password') ->from($this->table) ->where( $qb->expr()->eq( @@ -391,6 +385,7 @@ class Database extends ABackend implements $this->cache[$uid] = [ 'uid' => (string)$row['uid'], 'displayname' => (string)$row['displayname'], + 'password' => (string)$row['password'], ]; } else { $this->cache[$uid] = false; diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 2973240a4a1..cd26337cd20 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -548,11 +548,11 @@ class Session implements IUserSession, Emitter { \OC::$server->getCsrfTokenManager()->refreshToken(); } - //we need to pass the user name, which may differ from login name - $user = $this->getUser()->getUID(); - OC_Util::setupFS($user); - if ($firstTimeLogin) { + //we need to pass the user name, which may differ from login name + $user = $this->getUser()->getUID(); + OC_Util::setupFS($user); + // TODO: lock necessary? //trigger creation of user home and /files folder $userFolder = \OC::$server->getUserFolder($user); diff --git a/lib/private/User/User.php b/lib/private/User/User.php index 5fa1272f95c..0a51622428b 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -73,7 +73,7 @@ class User implements IUser { /** @var IEventDispatcher */ private $dispatcher; - /** @var bool */ + /** @var bool|null */ private $enabled; /** @var Emitter|Manager */ @@ -82,7 +82,7 @@ class User implements IUser { /** @var string */ private $home; - /** @var int */ + /** @var int|null */ private $lastLogin; /** @var \OCP\IConfig */ @@ -104,9 +104,6 @@ class User implements IUser { } $this->config = $config; $this->urlGenerator = $urlGenerator; - $enabled = $this->config->getUserValue($uid, 'core', 'enabled', 'true'); - $this->enabled = ($enabled === 'true'); - $this->lastLogin = $this->config->getUserValue($uid, 'login', 'lastLogin', 0); if (is_null($this->urlGenerator)) { $this->urlGenerator = \OC::$server->getURLGenerator(); } @@ -231,14 +228,17 @@ class User implements IUser { * @return int */ public function getLastLogin() { - return $this->lastLogin; + if ($this->lastLogin === null) { + $this->lastLogin = (int) $this->config->getUserValue($this->uid, 'login', 'lastLogin', 0); + } + return (int) $this->lastLogin; } /** * updates the timestamp of the most recent login of this user */ public function updateLastLoginTimestamp() { - $firstTimeLogin = ($this->lastLogin === 0); + $firstTimeLogin = ($this->getLastLogin() === 0); $this->lastLogin = time(); $this->config->setUserValue( $this->uid, 'login', 'lastLogin', $this->lastLogin); @@ -406,7 +406,11 @@ class User implements IUser { * @return bool */ public function isEnabled() { - return $this->enabled; + if ($this->enabled === null) { + $enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true'); + $this->enabled = $enabled === 'true'; + } + return (bool) $this->enabled; } /** diff --git a/lib/private/UserStatus/ISettableProvider.php b/lib/private/UserStatus/ISettableProvider.php index fc0d502845e..88a107d1f86 100644 --- a/lib/private/UserStatus/ISettableProvider.php +++ b/lib/private/UserStatus/ISettableProvider.php @@ -52,4 +52,15 @@ interface ISettableProvider extends IProvider { * @param string $status The expected current status. */ public function revertUserStatus(string $userId, string $messageId, string $status): void; + + /** + * Revert an automatically set user status. For example after leaving a call, + * change back to the previously set status. If the user has already updated + * their status, this method does nothing. + * + * @param string[] $userIds The users for which we want to update the status. + * @param string $messageId The expected current messageId. + * @param string $status The expected current status. + */ + public function revertMultipleUserStatus(array $userIds, string $messageId, string $status): void; } diff --git a/lib/private/UserStatus/Manager.php b/lib/private/UserStatus/Manager.php index bca80bc5b03..c93795bea5b 100644 --- a/lib/private/UserStatus/Manager.php +++ b/lib/private/UserStatus/Manager.php @@ -121,4 +121,12 @@ class Manager implements IManager { } $this->provider->revertUserStatus($userId, $messageId, $status); } + + public function revertMultipleUserStatus(array $userIds, string $messageId, string $status): void { + $this->setupProvider(); + if (!$this->provider || !($this->provider instanceof ISettableProvider)) { + return; + } + $this->provider->revertMultipleUserStatus($userIds, $messageId, $status); + } } diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index 1ce3662000e..ca01e91216b 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -173,6 +173,7 @@ class OC_App { $hasAppPhpFile = is_file($appPath . '/appinfo/app.php'); + \OC::$server->getEventLogger()->start('bootstrap:load_app_' . $app, 'Load app: ' . $app); if ($isBootable && $hasAppPhpFile) { \OC::$server->getLogger()->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [ 'app' => $app, @@ -181,7 +182,6 @@ class OC_App { \OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [ 'app' => $app, ]); - \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app); try { self::requireAppFile($app); } catch (Throwable $ex) { @@ -201,8 +201,9 @@ class OC_App { ]); } } - \OC::$server->getEventLogger()->end('load_app_' . $app); } + \OC::$server->getEventLogger()->end('bootstrap:load_app_' . $app); + $coordinator->bootApp($app); $info = self::getAppInfo($app); diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php index 9110678537f..ceed79bc9d5 100644 --- a/lib/private/legacy/OC_Util.php +++ b/lib/private/legacy/OC_Util.php @@ -66,11 +66,10 @@ use bantu\IniGetWrapper\IniGetWrapper; use OC\AppFramework\Http\Request; -use OC\Files\Storage\LocalRootStorage; +use OC\Files\SetupManager; use OCP\Files\Template\ITemplateManager; use OCP\IConfig; use OCP\IGroupManager; -use OCP\ILogger; use OCP\IURLGenerator; use OCP\IUser; use OCP\Share\IManager; @@ -80,9 +79,6 @@ class OC_Util { public static $scripts = []; public static $styles = []; public static $headers = []; - private static $rootMounted = false; - private static $rootFsSetup = false; - private static $fsSetup = false; /** @var array Local cache of version.php */ private static $versionCache = null; @@ -91,222 +87,6 @@ class OC_Util { return \OC::$server->getAppManager(); } - private static function initLocalStorageRootFS() { - // mount local file backend as root - $configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data"); - //first set up the local "root" storage - \OC\Files\Filesystem::initMountManager(); - if (!self::$rootMounted) { - \OC\Files\Filesystem::mount(LocalRootStorage::class, ['datadir' => $configDataDirectory], '/'); - self::$rootMounted = true; - } - } - - /** - * mounting an object storage as the root fs will in essence remove the - * necessity of a data folder being present. - * TODO make home storage aware of this and use the object storage instead of local disk access - * - * @param array $config containing 'class' and optional 'arguments' - * @suppress PhanDeprecatedFunction - */ - private static function initObjectStoreRootFS($config) { - // check misconfiguration - if (empty($config['class'])) { - \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR); - } - if (!isset($config['arguments'])) { - $config['arguments'] = []; - } - - // instantiate object store implementation - $name = $config['class']; - if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) { - $segments = explode('\\', $name); - OC_App::loadApp(strtolower($segments[1])); - } - $config['arguments']['objectstore'] = new $config['class']($config['arguments']); - // mount with plain / root object store implementation - $config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage'; - - // mount object storage as root - \OC\Files\Filesystem::initMountManager(); - if (!self::$rootMounted) { - \OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/'); - self::$rootMounted = true; - } - } - - /** - * mounting an object storage as the root fs will in essence remove the - * necessity of a data folder being present. - * - * @param array $config containing 'class' and optional 'arguments' - * @suppress PhanDeprecatedFunction - */ - private static function initObjectStoreMultibucketRootFS($config) { - // check misconfiguration - if (empty($config['class'])) { - \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR); - } - if (!isset($config['arguments'])) { - $config['arguments'] = []; - } - - // instantiate object store implementation - $name = $config['class']; - if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) { - $segments = explode('\\', $name); - OC_App::loadApp(strtolower($segments[1])); - } - - if (!isset($config['arguments']['bucket'])) { - $config['arguments']['bucket'] = ''; - } - // put the root FS always in first bucket for multibucket configuration - $config['arguments']['bucket'] .= '0'; - - $config['arguments']['objectstore'] = new $config['class']($config['arguments']); - // mount with plain / root object store implementation - $config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage'; - - // mount object storage as root - \OC\Files\Filesystem::initMountManager(); - if (!self::$rootMounted) { - \OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/'); - self::$rootMounted = true; - } - } - - /** - * Can be set up - * - * @param string $user - * @return boolean - * @description configure the initial filesystem based on the configuration - * @suppress PhanDeprecatedFunction - * @suppress PhanAccessMethodInternal - */ - public static function setupRootFS(string $user = '') { - //setting up the filesystem twice can only lead to trouble - if (self::$rootFsSetup) { - return false; - } - - \OC::$server->getEventLogger()->start('setup_root_fs', 'Setup root filesystem'); - - // load all filesystem apps before, so no setup-hook gets lost - OC_App::loadApps(['filesystem']); - - self::$rootFsSetup = true; - - \OC\Files\Filesystem::initMountManager(); - - $prevLogging = \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false); - \OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { - if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) { - /** @var \OC\Files\Storage\Common $storage */ - $storage->setMountOptions($mount->getOptions()); - } - return $storage; - }); - - \OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) { - if (!$mount->getOption('enable_sharing', true)) { - return new \OC\Files\Storage\Wrapper\PermissionsMask([ - 'storage' => $storage, - 'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE - ]); - } - return $storage; - }); - - // install storage availability wrapper, before most other wrappers - \OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, \OCP\Files\Storage\IStorage $storage) { - if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { - return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]); - } - return $storage; - }); - - \OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { - if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { - return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]); - } - return $storage; - }); - - \OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) { - // set up quota for home storages, even for other users - // which can happen when using sharing - - /** - * @var \OC\Files\Storage\Storage $storage - */ - if ($storage->instanceOfStorage('\OC\Files\Storage\Home') - || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage') - ) { - /** @var \OC\Files\Storage\Home $storage */ - if (is_object($storage->getUser())) { - $quota = OC_Util::getUserQuota($storage->getUser()); - if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { - return new \OC\Files\Storage\Wrapper\Quota(['storage' => $storage, 'quota' => $quota, 'root' => 'files']); - } - } - } - - return $storage; - }); - - \OC\Files\Filesystem::addStorageWrapper('readonly', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) { - /* - * Do not allow any operations that modify the storage - */ - if ($mount->getOption('readonly', false)) { - return new \OC\Files\Storage\Wrapper\PermissionsMask([ - 'storage' => $storage, - 'mask' => \OCP\Constants::PERMISSION_ALL & ~( - \OCP\Constants::PERMISSION_UPDATE | - \OCP\Constants::PERMISSION_CREATE | - \OCP\Constants::PERMISSION_DELETE - ), - ]); - } - return $storage; - }); - - OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user]); - - \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper($prevLogging); - - //check if we are using an object storage - $objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null); - $objectStoreMultibucket = \OC::$server->getSystemConfig()->getValue('objectstore_multibucket', null); - - // use the same order as in ObjectHomeMountProvider - if (isset($objectStoreMultibucket)) { - self::initObjectStoreMultibucketRootFS($objectStoreMultibucket); - } elseif (isset($objectStore)) { - self::initObjectStoreRootFS($objectStore); - } else { - self::initLocalStorageRootFS(); - } - - /** @var \OCP\Files\Config\IMountProviderCollection $mountProviderCollection */ - $mountProviderCollection = \OC::$server->query(\OCP\Files\Config\IMountProviderCollection::class); - $rootMountProviders = $mountProviderCollection->getRootMounts(); - - /** @var \OC\Files\Mount\Manager $mountManager */ - $mountManager = \OC\Files\Filesystem::getMountManager(); - foreach ($rootMountProviders as $rootMountProvider) { - $mountManager->addMount($rootMountProvider); - } - - \OC::$server->getEventLogger()->end('setup_root_fs'); - - return true; - } - /** * Setup the file system * @@ -317,14 +97,6 @@ class OC_Util { * @suppress PhanAccessMethodInternal */ public static function setupFS(?string $user = '') { - self::setupRootFS($user ?? ''); - - if (self::$fsSetup) { - return false; - } - - \OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem'); - // If we are not forced to load a specific user we load the one that is logged in if ($user === '') { $userObject = \OC::$server->get(\OCP\IUserSession::class)->getUser(); @@ -332,18 +104,14 @@ class OC_Util { $userObject = \OC::$server->get(\OCP\IUserManager::class)->get($user); } - //if we aren't logged in, or the user doesn't exist, there is no use to set up the filesystem - if ($userObject) { - self::$fsSetup = true; - - $userDir = '/' . $userObject->getUID() . '/files'; - - //jail the user into his "home" directory - \OC\Files\Filesystem::init($userObject, $userDir); + /** @var SetupManager $setupManager */ + $setupManager = \OC::$server->get(SetupManager::class); - OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $userObject->getUID(), 'user_dir' => $userDir]); + if ($userObject) { + $setupManager->setupForUser($userObject); + } else { + $setupManager->setupRoot(); } - \OC::$server->getEventLogger()->end('setup_fs'); return true; } @@ -497,11 +265,9 @@ class OC_Util { * @suppress PhanUndeclaredMethod */ public static function tearDownFS() { - \OC\Files\Filesystem::tearDown(); - \OC::$server->getRootFolder()->clearCache(); - self::$fsSetup = false; - self::$rootFsSetup = false; - self::$rootMounted = false; + /** @var SetupManager $setupManager */ + $setupManager = \OC::$server->get(SetupManager::class); + $setupManager->tearDown(); } /** |