diff options
Diffstat (limited to 'apps/dav/lib/Connector')
19 files changed, 224 insertions, 93 deletions
diff --git a/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php b/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php index b39dc7197b0..0e2b1c58748 100644 --- a/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php +++ b/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php b/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php index 88fff5e6a5a..9cff113140a 100644 --- a/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php +++ b/apps/dav/lib/Connector/Sabre/AppleQuirksPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index d977721bdfa..a174920946a 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -55,8 +55,8 @@ class Auth extends AbstractBasic { * @see https://github.com/owncloud/core/issues/13245 */ public function isDavAuthenticated(string $username): bool { - return !is_null($this->session->get(self::DAV_AUTHENTICATED)) && - $this->session->get(self::DAV_AUTHENTICATED) === $username; + return !is_null($this->session->get(self::DAV_AUTHENTICATED)) + && $this->session->get(self::DAV_AUTHENTICATED) === $username; } /** @@ -71,8 +71,8 @@ class Auth extends AbstractBasic { * @throws PasswordLoginForbidden */ protected function validateUserPass($username, $password) { - if ($this->userSession->isLoggedIn() && - $this->isDavAuthenticated($this->userSession->getUser()->getUID()) + if ($this->userSession->isLoggedIn() + && $this->isDavAuthenticated($this->userSession->getUser()->getUID()) ) { $this->session->close(); return true; @@ -118,7 +118,7 @@ class Auth extends AbstractBasic { * Checks whether a CSRF check is required on the request */ private function requiresCSRFCheck(): bool { - + $methodsWithoutCsrf = ['GET', 'HEAD', 'OPTIONS']; if (in_array($this->request->getMethod(), $methodsWithoutCsrf)) { return false; @@ -144,8 +144,8 @@ class Auth extends AbstractBasic { } // If logged-in AND DAV authenticated no check is required - if ($this->userSession->isLoggedIn() && - $this->isDavAuthenticated($this->userSession->getUser()->getUID())) { + if ($this->userSession->isLoggedIn() + && $this->isDavAuthenticated($this->userSession->getUser()->getUID())) { return false; } @@ -159,8 +159,8 @@ class Auth extends AbstractBasic { private function auth(RequestInterface $request, ResponseInterface $response): array { $forcedLogout = false; - if (!$this->request->passesCSRFCheck() && - $this->requiresCSRFCheck()) { + if (!$this->request->passesCSRFCheck() + && $this->requiresCSRFCheck()) { // In case of a fail with POST we need to recheck the credentials if ($this->request->getMethod() === 'POST') { $forcedLogout = true; @@ -178,10 +178,10 @@ class Auth extends AbstractBasic { } if ( //Fix for broken webdav clients - ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) || + ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) //Well behaved clients that only send the cookie are allowed - ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && empty($request->getHeader('Authorization'))) || - \OC_User::handleApacheAuth() + || ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && empty($request->getHeader('Authorization'))) + || \OC_User::handleApacheAuth() ) { $user = $this->userSession->getUser()->getUID(); $this->currentUser = $user; diff --git a/apps/dav/lib/Connector/Sabre/BearerAuth.php b/apps/dav/lib/Connector/Sabre/BearerAuth.php index e189d8fa128..23453ae8efb 100644 --- a/apps/dav/lib/Connector/Sabre/BearerAuth.php +++ b/apps/dav/lib/Connector/Sabre/BearerAuth.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/Connector/Sabre/CachingTree.php b/apps/dav/lib/Connector/Sabre/CachingTree.php index 86e102677c1..5d72b530f58 100644 --- a/apps/dav/lib/Connector/Sabre/CachingTree.php +++ b/apps/dav/lib/Connector/Sabre/CachingTree.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/Connector/Sabre/ChecksumUpdatePlugin.php b/apps/dav/lib/Connector/Sabre/ChecksumUpdatePlugin.php index 64a61a43a9b..18009080585 100644 --- a/apps/dav/lib/Connector/Sabre/ChecksumUpdatePlugin.php +++ b/apps/dav/lib/Connector/Sabre/ChecksumUpdatePlugin.php @@ -27,20 +27,6 @@ class ChecksumUpdatePlugin extends ServerPlugin { } /** @return string[] */ - public function getHTTPMethods($path): array { - $tree = $this->server->tree; - - if ($tree->nodeExists($path)) { - $node = $tree->getNodeForPath($path); - if ($node instanceof File) { - return ['PATCH']; - } - } - - return []; - } - - /** @return string[] */ public function getFeatures(): array { return ['nextcloud-checksum-update']; } diff --git a/apps/dav/lib/Connector/Sabre/DavAclPlugin.php b/apps/dav/lib/Connector/Sabre/DavAclPlugin.php index 7846896182f..100d719ef01 100644 --- a/apps/dav/lib/Connector/Sabre/DavAclPlugin.php +++ b/apps/dav/lib/Connector/Sabre/DavAclPlugin.php @@ -62,7 +62,7 @@ class DavAclPlugin extends \Sabre\DAVACL\Plugin { ) ); } - + } return $access; diff --git a/apps/dav/lib/Connector/Sabre/Directory.php b/apps/dav/lib/Connector/Sabre/Directory.php index 7f8fe3a84de..fe09c3f423f 100644 --- a/apps/dav/lib/Connector/Sabre/Directory.php +++ b/apps/dav/lib/Connector/Sabre/Directory.php @@ -13,7 +13,9 @@ use OCA\DAV\AppInfo\Application; use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OCA\DAV\Connector\Sabre\Exception\Forbidden; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use OCA\DAV\Storage\PublicShareWrapper; use OCP\App\IAppManager; +use OCP\Constants; use OCP\Files\FileInfo; use OCP\Files\Folder; use OCP\Files\ForbiddenException; @@ -172,7 +174,20 @@ class Directory extends Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuot * @throws \Sabre\DAV\Exception\ServiceUnavailable */ public function getChild($name, $info = null, ?IRequest $request = null, ?IL10N $l10n = null) { - if (!$this->info->isReadable()) { + $storage = $this->info->getStorage(); + $allowDirectory = false; + + // Checking if we're in a file drop + // If we are, then only PUT and MKCOL are allowed (see plugin) + // so we are safe to return the directory without a risk of + // leaking files and folders structure. + if ($storage instanceof PublicShareWrapper) { + $share = $storage->getShare(); + $allowDirectory = ($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ; + } + + // For file drop we need to be allowed to read the directory with the nickname + if (!$allowDirectory && !$this->info->isReadable()) { // avoid detecting files through this way throw new NotFound(); } @@ -198,6 +213,11 @@ class Directory extends Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuot if ($info->getMimeType() === FileInfo::MIMETYPE_FOLDER) { $node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager); } else { + // In case reading a directory was allowed but it turns out the node was a not a directory, reject it now. + if (!$this->info->isReadable()) { + throw new NotFound(); + } + $node = new File($this->fileView, $info, $this->shareManager, $request, $l10n); } if ($this->tree) { diff --git a/apps/dav/lib/Connector/Sabre/DummyGetResponsePlugin.php b/apps/dav/lib/Connector/Sabre/DummyGetResponsePlugin.php index 4a7e30caa10..f6baceb748b 100644 --- a/apps/dav/lib/Connector/Sabre/DummyGetResponsePlugin.php +++ b/apps/dav/lib/Connector/Sabre/DummyGetResponsePlugin.php @@ -43,8 +43,8 @@ class DummyGetResponsePlugin extends \Sabre\DAV\ServerPlugin { * @return false */ public function httpGet(RequestInterface $request, ResponseInterface $response) { - $string = 'This is the WebDAV interface. It can only be accessed by ' . - 'WebDAV clients such as the Nextcloud desktop sync client.'; + $string = 'This is the WebDAV interface. It can only be accessed by ' + . 'WebDAV clients such as the Nextcloud desktop sync client.'; $stream = fopen('php://memory', 'r+'); fwrite($stream, $string); rewind($stream); diff --git a/apps/dav/lib/Connector/Sabre/Exception/BadGateway.php b/apps/dav/lib/Connector/Sabre/Exception/BadGateway.php index 41ace002660..1e1e4aaed04 100644 --- a/apps/dav/lib/Connector/Sabre/Exception/BadGateway.php +++ b/apps/dav/lib/Connector/Sabre/Exception/BadGateway.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-only diff --git a/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php b/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php index 61ecfaf845c..b0c5a079ce1 100644 --- a/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php @@ -117,8 +117,8 @@ class FakeLockerPlugin extends ServerPlugin { $lockInfo->timeout = 1800; $body = $this->server->xml->write('{DAV:}prop', [ - '{DAV:}lockdiscovery' => - new LockDiscovery([$lockInfo]) + '{DAV:}lockdiscovery' + => new LockDiscovery([$lockInfo]) ]); $response->setStatus(Http::STATUS_OK); diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index 045b9d7e784..218d38e1c4b 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -19,6 +19,7 @@ use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException; use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType; use OCP\App\IAppManager; use OCP\Encryption\Exceptions\GenericEncryptionException; +use OCP\Files; use OCP\Files\EntityTooLargeException; use OCP\Files\FileInfo; use OCP\Files\ForbiddenException; @@ -215,7 +216,9 @@ class File extends Node implements IFile { try { /** @var IWriteStreamStorage $partStorage */ $count = $partStorage->writeStream($internalPartPath, $wrappedData); - } catch (GenericFileException) { + } catch (GenericFileException $e) { + $logger = Server::get(LoggerInterface::class); + $logger->error('Error while writing stream to storage: ' . $e->getMessage(), ['exception' => $e, 'app' => 'webdav']); $result = $isEOF; if (is_resource($wrappedData)) { $result = feof($wrappedData); @@ -229,7 +232,7 @@ class File extends Node implements IFile { // because we have no clue about the cause we can only throw back a 500/Internal Server Error throw new Exception($this->l10n->t('Could not write file contents')); } - [$count, $result] = \OC_Helper::streamCopy($data, $target); + [$count, $result] = Files::streamCopy($data, $target, true); fclose($target); } diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index 34f48384e12..843383a0452 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -9,8 +9,10 @@ namespace OCA\DAV\Connector\Sabre; use OC\AppFramework\Http\Request; use OC\FilesMetadata\Model\FilesMetadata; +use OC\User\NoUserException; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCA\Files_Sharing\External\Mount as SharingExternalMount; +use OCP\Accounts\IAccountManager; use OCP\Constants; use OCP\Files\ForbiddenException; use OCP\Files\IFilenameValidator; @@ -91,6 +93,7 @@ class FilesPlugin extends ServerPlugin { private IPreview $previewManager, private IUserSession $userSession, private IFilenameValidator $validator, + private IAccountManager $accountManager, private bool $isPublic = false, private bool $downloadAttachment = true, ) { @@ -254,8 +257,8 @@ class FilesPlugin extends ServerPlugin { // adds a 'Content-Disposition: attachment' header in case no disposition // header has been set before - if ($this->downloadAttachment && - $response->getHeader('Content-Disposition') === null) { + if ($this->downloadAttachment + && $response->getHeader('Content-Disposition') === null) { $filename = $node->getName(); if ($this->request->isUserAgent( [ @@ -361,9 +364,32 @@ class FilesPlugin extends ServerPlugin { $owner = $node->getOwner(); if (!$owner) { return null; - } else { + } + + // Get current user to see if we're in a public share or not + $user = $this->userSession->getUser(); + + // If the user is logged in, we can return the display name + if ($user !== null) { return $owner->getDisplayName(); } + + // Check if the user published their display name + try { + $ownerAccount = $this->accountManager->getAccount($owner); + } catch (NoUserException) { + // do not lock process if owner is not local + return null; + } + + $ownerNameProperty = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME); + + // Since we are not logged in, we need to have at least the published scope + if ($ownerNameProperty->getScope() === IAccountManager::SCOPE_PUBLISHED) { + return $owner->getDisplayName(); + } + + return null; }); $propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) { @@ -515,8 +541,8 @@ class FilesPlugin extends ServerPlugin { $ocmPermissions[] = 'read'; } - if (($ncPermissions & Constants::PERMISSION_CREATE) || - ($ncPermissions & Constants::PERMISSION_UPDATE)) { + if (($ncPermissions & Constants::PERMISSION_CREATE) + || ($ncPermissions & Constants::PERMISSION_UPDATE)) { $ocmPermissions[] = 'write'; } @@ -701,15 +727,15 @@ class FilesPlugin extends ServerPlugin { */ public function sendFileIdHeader($filePath, ?\Sabre\DAV\INode $node = null) { // we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder - if (!$this->server->tree->nodeExists($filePath)) { - return; - } - $node = $this->server->tree->getNodeForPath($filePath); - if ($node instanceof Node) { - $fileId = $node->getFileId(); - if (!is_null($fileId)) { - $this->server->httpResponse->setHeader('OC-FileId', $fileId); + try { + $node = $this->server->tree->getNodeForPath($filePath); + if ($node instanceof Node) { + $fileId = $node->getFileId(); + if (!is_null($fileId)) { + $this->server->httpResponse->setHeader('OC-FileId', $fileId); + } } + } catch (NotFound) { } } } diff --git a/apps/dav/lib/Connector/Sabre/MtimeSanitizer.php b/apps/dav/lib/Connector/Sabre/MtimeSanitizer.php index efed6ce09f8..e18ef58149a 100644 --- a/apps/dav/lib/Connector/Sabre/MtimeSanitizer.php +++ b/apps/dav/lib/Connector/Sabre/MtimeSanitizer.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-only diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php index 515ef807a25..b61cabedf5f 100644 --- a/apps/dav/lib/Connector/Sabre/Principal.php +++ b/apps/dav/lib/Connector/Sabre/Principal.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -41,9 +42,6 @@ class Principal implements BackendInterface { /** @var bool */ private $hasCircles; - /** @var ProxyMapper */ - private $proxyMapper; - /** @var KnownUserService */ private $knownUserService; @@ -54,7 +52,7 @@ class Principal implements BackendInterface { private IShareManager $shareManager, private IUserSession $userSession, private IAppManager $appManager, - ProxyMapper $proxyMapper, + private ProxyMapper $proxyMapper, KnownUserService $knownUserService, private IConfig $config, private IFactory $languageFactory, @@ -62,7 +60,6 @@ class Principal implements BackendInterface { ) { $this->principalPrefix = trim($principalPrefix, '/'); $this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/'); - $this->proxyMapper = $proxyMapper; $this->knownUserService = $knownUserService; } @@ -155,6 +152,11 @@ class Principal implements BackendInterface { 'uri' => 'principals/system/' . $name, '{DAV:}displayname' => $this->languageFactory->get('dav')->t('Accounts'), ]; + } elseif ($prefix === 'principals/shares') { + return [ + 'uri' => 'principals/shares/' . $name, + '{DAV:}displayname' => $name, + ]; } return null; } diff --git a/apps/dav/lib/Connector/Sabre/PublicAuth.php b/apps/dav/lib/Connector/Sabre/PublicAuth.php index ea59d9efc8f..2ca1c25e2f6 100644 --- a/apps/dav/lib/Connector/Sabre/PublicAuth.php +++ b/apps/dav/lib/Connector/Sabre/PublicAuth.php @@ -14,7 +14,9 @@ namespace OCA\DAV\Connector\Sabre; use OCP\Defaults; use OCP\IRequest; use OCP\ISession; +use OCP\IURLGenerator; use OCP\Security\Bruteforce\IThrottler; +use OCP\Security\Bruteforce\MaxDelayReached; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use OCP\Share\IShare; @@ -22,6 +24,7 @@ use Psr\Log\LoggerInterface; use Sabre\DAV\Auth\Backend\AbstractBasic; use Sabre\DAV\Exception\NotAuthenticated; use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\PreconditionFailed; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\HTTP; use Sabre\HTTP\RequestInterface; @@ -44,6 +47,7 @@ class PublicAuth extends AbstractBasic { private ISession $session, private IThrottler $throttler, private LoggerInterface $logger, + private IURLGenerator $urlGenerator, ) { // setup realm $defaults = new Defaults(); @@ -51,17 +55,18 @@ class PublicAuth extends AbstractBasic { } /** - * @param RequestInterface $request - * @param ResponseInterface $response - * - * @return array * @throws NotAuthenticated + * @throws MaxDelayReached * @throws ServiceUnavailable */ public function check(RequestInterface $request, ResponseInterface $response): array { try { $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), self::BRUTEFORCE_ACTION); + if (count($_COOKIE) > 0 && !$this->request->passesStrictCookieCheck() && $this->getShare()->getPassword() !== null) { + throw new PreconditionFailed('Strict cookie check failed'); + } + $auth = new HTTP\Auth\Basic( $this->realm, $request, @@ -75,7 +80,17 @@ class PublicAuth extends AbstractBasic { } return $this->checkToken(); - } catch (NotAuthenticated $e) { + } catch (NotAuthenticated|MaxDelayReached $e) { + $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress()); + throw $e; + } catch (PreconditionFailed $e) { + $response->setHeader( + 'Location', + $this->urlGenerator->linkToRoute( + 'files_sharing.share.showShare', + [ 'token' => $this->getToken() ], + ), + ); throw $e; } catch (\Exception $e) { $class = get_class($e); @@ -87,14 +102,13 @@ class PublicAuth extends AbstractBasic { /** * Extract token from request url - * @return string * @throws NotFound */ private function getToken(): string { $path = $this->request->getPathInfo() ?: ''; // ['', 'dav', 'files', 'token'] $splittedPath = explode('/', $path); - + if (count($splittedPath) < 4 || $splittedPath[3] === '') { throw new NotFound(); } @@ -104,7 +118,7 @@ class PublicAuth extends AbstractBasic { /** * Check token validity - * @return array + * * @throws NotFound * @throws NotAuthenticated */ @@ -152,15 +166,13 @@ class PublicAuth extends AbstractBasic { protected function validateUserPass($username, $password) { $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), self::BRUTEFORCE_ACTION); - $token = $this->getToken(); try { - $share = $this->shareManager->getShareByToken($token); + $share = $this->getShare(); } catch (ShareNotFound $e) { $this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress()); return false; } - $this->share = $share; \OC_User::setIncognitoMode(true); // check if the share is password protected @@ -176,7 +188,7 @@ class PublicAuth extends AbstractBasic { } return true; } - + if ($this->session->exists(PublicAuth::DAV_AUTHENTICATED) && $this->session->get(PublicAuth::DAV_AUTHENTICATED) === $share->getId()) { return true; @@ -203,7 +215,13 @@ class PublicAuth extends AbstractBasic { } public function getShare(): IShare { - assert($this->share !== null); + $token = $this->getToken(); + + if ($this->share === null) { + $share = $this->shareManager->getShareByToken($token); + $this->share = $share; + } + return $this->share; } } diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index f035ff8d625..214412e1744 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -8,17 +8,23 @@ namespace OCA\DAV\Connector\Sabre; use OC\Files\View; +use OC\KnownUser\KnownUserService; use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\CalDAV\DefaultCalendarValidator; +use OCA\DAV\CalDAV\Proxy\ProxyMapper; use OCA\DAV\DAV\CustomPropertiesBackend; use OCA\DAV\DAV\ViewOnlyPlugin; use OCA\DAV\Files\BrowserErrorPagePlugin; +use OCA\DAV\Files\Sharing\RootCollection; +use OCA\DAV\Upload\CleanupService; use OCA\Theming\ThemingDefaults; +use OCP\Accounts\IAccountManager; use OCP\App\IAppManager; use OCP\Comments\ICommentsManager; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Folder; use OCP\Files\IFilenameValidator; +use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountManager; use OCP\IConfig; use OCP\IDBConnection; @@ -27,12 +33,14 @@ use OCP\IL10N; use OCP\IPreview; use OCP\IRequest; use OCP\ITagManager; +use OCP\IUserManager; use OCP\IUserSession; use OCP\SabrePluginEvent; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use Psr\Log\LoggerInterface; use Sabre\DAV\Auth\Plugin; +use Sabre\DAV\SimpleCollection; class ServerFactory { @@ -53,13 +61,22 @@ class ServerFactory { /** * @param callable $viewCallBack callback that should return the view for the dav endpoint */ - public function createServer(string $baseUri, + public function createServer( + bool $isPublicShare, + string $baseUri, string $requestUri, Plugin $authPlugin, - callable $viewCallBack): Server { + callable $viewCallBack, + ): Server { // Fire up server - $objectTree = new ObjectTree(); - $server = new Server($objectTree); + if ($isPublicShare) { + $rootCollection = new SimpleCollection('root'); + $tree = new CachingTree($rootCollection); + } else { + $rootCollection = null; + $tree = new ObjectTree(); + } + $server = new Server($tree); // Set URL explicitly due to reverse-proxy situations $server->httpRequest->setUrl($requestUri); $server->setBaseUri($baseUri); @@ -80,7 +97,7 @@ class ServerFactory { $server->addPlugin(new RequestIdHeaderPlugin($this->request)); $server->addPlugin(new ZipFolderPlugin( - $objectTree, + $tree, $this->logger, $this->eventDispatcher, )); @@ -100,7 +117,7 @@ class ServerFactory { } // wait with registering these until auth is handled and the filesystem is setup - $server->on('beforeMethod:*', function () use ($server, $objectTree, $viewCallBack): void { + $server->on('beforeMethod:*', function () use ($server, $tree, $viewCallBack, $isPublicShare, $rootCollection): void { // ensure the skeleton is copied $userFolder = \OC::$server->getUserFolder(); @@ -114,20 +131,55 @@ class ServerFactory { // Create Nextcloud Dir if ($rootInfo->getType() === 'dir') { - $root = new Directory($view, $rootInfo, $objectTree); + $root = new Directory($view, $rootInfo, $tree); } else { $root = new File($view, $rootInfo); } - $objectTree->init($root, $view, $this->mountManager); + + if ($isPublicShare) { + $userPrincipalBackend = new Principal( + \OCP\Server::get(IUserManager::class), + \OCP\Server::get(IGroupManager::class), + \OCP\Server::get(IAccountManager::class), + \OCP\Server::get(\OCP\Share\IManager::class), + \OCP\Server::get(IUserSession::class), + \OCP\Server::get(IAppManager::class), + \OCP\Server::get(ProxyMapper::class), + \OCP\Server::get(KnownUserService::class), + \OCP\Server::get(IConfig::class), + \OC::$server->getL10NFactory(), + ); + + // Mount the share collection at /public.php/dav/shares/<share token> + $rootCollection->addChild(new RootCollection( + $root, + $userPrincipalBackend, + 'principals/shares', + )); + + // Mount the upload collection at /public.php/dav/uploads/<share token> + $rootCollection->addChild(new \OCA\DAV\Upload\RootCollection( + $userPrincipalBackend, + 'principals/shares', + \OCP\Server::get(CleanupService::class), + \OCP\Server::get(IRootFolder::class), + \OCP\Server::get(IUserSession::class), + \OCP\Server::get(\OCP\Share\IManager::class), + )); + } else { + /** @var ObjectTree $tree */ + $tree->init($root, $view, $this->mountManager); + } $server->addPlugin( new FilesPlugin( - $objectTree, + $tree, $this->config, $this->request, $this->previewManager, $this->userSession, \OCP\Server::get(IFilenameValidator::class), + \OCP\Server::get(IAccountManager::class), false, !$this->config->getSystemValue('debug', false) ) @@ -141,16 +193,16 @@ class ServerFactory { )); if ($this->userSession->isLoggedIn()) { - $server->addPlugin(new TagsPlugin($objectTree, $this->tagManager, $this->eventDispatcher, $this->userSession)); + $server->addPlugin(new TagsPlugin($tree, $this->tagManager, $this->eventDispatcher, $this->userSession)); $server->addPlugin(new SharesPlugin( - $objectTree, + $tree, $this->userSession, $userFolder, \OCP\Server::get(\OCP\Share\IManager::class) )); $server->addPlugin(new CommentPropertiesPlugin(\OCP\Server::get(ICommentsManager::class), $this->userSession)); $server->addPlugin(new FilesReportPlugin( - $objectTree, + $tree, $view, \OCP\Server::get(ISystemTagManager::class), \OCP\Server::get(ISystemTagObjectMapper::class), @@ -165,7 +217,7 @@ class ServerFactory { new \Sabre\DAV\PropertyStorage\Plugin( new CustomPropertiesBackend( $server, - $objectTree, + $tree, $this->databaseConnection, $this->userSession->getUser(), \OCP\Server::get(DefaultCalendarValidator::class), diff --git a/apps/dav/lib/Connector/Sabre/SharesPlugin.php b/apps/dav/lib/Connector/Sabre/SharesPlugin.php index 088cf33d85f..f49e85333f3 100644 --- a/apps/dav/lib/Connector/Sabre/SharesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/SharesPlugin.php @@ -176,8 +176,8 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin { if ($sabreNode instanceof Directory && $propFind->getDepth() !== 0 && ( - !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) || - !is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME)) + !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) + || !is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME)) ) ) { $folderNode = $sabreNode->getNode(); diff --git a/apps/dav/lib/Connector/Sabre/TagsPlugin.php b/apps/dav/lib/Connector/Sabre/TagsPlugin.php index eb06fa5cef6..25c1633df36 100644 --- a/apps/dav/lib/Connector/Sabre/TagsPlugin.php +++ b/apps/dav/lib/Connector/Sabre/TagsPlugin.php @@ -94,6 +94,7 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin { $this->server = $server; $this->server->on('propFind', [$this, 'handleGetProperties']); $this->server->on('propPatch', [$this, 'handleUpdateProperties']); + $this->server->on('preloadProperties', [$this, 'handlePreloadProperties']); } /** @@ -150,6 +151,24 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin { } /** + * Prefetches tags for a list of file IDs and caches the results + * + * @param array $fileIds List of file IDs to prefetch tags for + * @return void + */ + private function prefetchTagsForFileIds(array $fileIds) { + $tags = $this->getTagger()->getTagsForObjects($fileIds); + if ($tags === false) { + // the tags API returns false on error... + $tags = []; + } + + foreach ($fileIds as $fileId) { + $this->cachedTags[$fileId] = $tags[$fileId] ?? []; + } + } + + /** * Updates the tags of the given file id * * @param int $fileId @@ -199,22 +218,11 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin { )) { // note: pre-fetching only supported for depth <= 1 $folderContent = $node->getChildren(); - $fileIds[] = (int)$node->getId(); + $fileIds = [(int)$node->getId()]; foreach ($folderContent as $info) { $fileIds[] = (int)$info->getId(); } - $tags = $this->getTagger()->getTagsForObjects($fileIds); - if ($tags === false) { - // the tags API returns false on error... - $tags = []; - } - - $this->cachedTags = $this->cachedTags + $tags; - $emptyFileIds = array_diff($fileIds, array_keys($tags)); - // also cache the ones that were not found - foreach ($emptyFileIds as $fileId) { - $this->cachedTags[$fileId] = []; - } + $this->prefetchTagsForFileIds($fileIds); } $isFav = null; @@ -270,4 +278,14 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin { return 200; }); } + + public function handlePreloadProperties(array $nodes, array $requestProperties): void { + if ( + !in_array(self::FAVORITE_PROPERTYNAME, $requestProperties, true) + && !in_array(self::TAGS_PROPERTYNAME, $requestProperties, true) + ) { + return; + } + $this->prefetchTagsForFileIds(array_map(fn ($node) => $node->getId(), $nodes)); + } } |