mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
refactor(dav): Replace baseuri manipulation with RootCollection for public shares
Signed-off-by: provokateurin <kate@provokateurin.de>
This commit is contained in:
parent
e90e3a70fa
commit
7f0953d520
12 changed files with 129 additions and 39 deletions
|
|
@ -68,7 +68,7 @@ $requestUri = Server::get(IRequest::class)->getRequestUri();
|
|||
$linkCheckPlugin = new PublicLinkCheckPlugin();
|
||||
$filesDropPlugin = new FilesDropPlugin();
|
||||
|
||||
$server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
|
||||
$server = $serverFactory->createServer(false, $baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
|
||||
$isAjax = in_array('XMLHttpRequest', explode(',', $_SERVER['HTTP_X_REQUESTED_WITH'] ?? ''));
|
||||
/** @var FederatedShareProvider $shareProvider */
|
||||
$federatedShareProvider = Server::get(FederatedShareProvider::class);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ $authPlugin->addBackend($bearerAuthPlugin);
|
|||
|
||||
$requestUri = Server::get(IRequest::class)->getRequestUri();
|
||||
|
||||
$server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, function () {
|
||||
$server = $serverFactory->createServer(false, $baseuri, $requestUri, $authPlugin, function () {
|
||||
// use the view for the logged in user
|
||||
return Filesystem::getView();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -75,12 +75,8 @@ $serverFactory = new ServerFactory(
|
|||
$linkCheckPlugin = new PublicLinkCheckPlugin();
|
||||
$filesDropPlugin = new FilesDropPlugin();
|
||||
|
||||
// Define root url with /public.php/dav/files/TOKEN
|
||||
/** @var string $baseuri defined in public.php */
|
||||
preg_match('/(^files\/[a-z0-9-_]+)/i', substr($requestUri, strlen($baseuri)), $match);
|
||||
$baseuri = $baseuri . $match[0];
|
||||
|
||||
$server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
|
||||
$server = $serverFactory->createServer(true, $baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) {
|
||||
// GET must be allowed for e.g. showing images and allowing Zip downloads
|
||||
if ($server->httpRequest->getMethod() !== 'GET') {
|
||||
// If this is *not* a GET request we only allow access to public DAV from AJAX or when Server2Server is allowed
|
||||
|
|
|
|||
|
|
@ -282,6 +282,7 @@ return array(
|
|||
'OCA\\DAV\\Files\\RootCollection' => $baseDir . '/../lib/Files/RootCollection.php',
|
||||
'OCA\\DAV\\Files\\Sharing\\FilesDropPlugin' => $baseDir . '/../lib/Files/Sharing/FilesDropPlugin.php',
|
||||
'OCA\\DAV\\Files\\Sharing\\PublicLinkCheckPlugin' => $baseDir . '/../lib/Files/Sharing/PublicLinkCheckPlugin.php',
|
||||
'OCA\\DAV\\Files\\Sharing\\RootCollection' => $baseDir . '/../lib/Files/Sharing/RootCollection.php',
|
||||
'OCA\\DAV\\Listener\\ActivityUpdaterListener' => $baseDir . '/../lib/Listener/ActivityUpdaterListener.php',
|
||||
'OCA\\DAV\\Listener\\AddMissingIndicesListener' => $baseDir . '/../lib/Listener/AddMissingIndicesListener.php',
|
||||
'OCA\\DAV\\Listener\\AddressbookListener' => $baseDir . '/../lib/Listener/AddressbookListener.php',
|
||||
|
|
|
|||
|
|
@ -297,6 +297,7 @@ class ComposerStaticInitDAV
|
|||
'OCA\\DAV\\Files\\RootCollection' => __DIR__ . '/..' . '/../lib/Files/RootCollection.php',
|
||||
'OCA\\DAV\\Files\\Sharing\\FilesDropPlugin' => __DIR__ . '/..' . '/../lib/Files/Sharing/FilesDropPlugin.php',
|
||||
'OCA\\DAV\\Files\\Sharing\\PublicLinkCheckPlugin' => __DIR__ . '/..' . '/../lib/Files/Sharing/PublicLinkCheckPlugin.php',
|
||||
'OCA\\DAV\\Files\\Sharing\\RootCollection' => __DIR__ . '/..' . '/../lib/Files/Sharing/RootCollection.php',
|
||||
'OCA\\DAV\\Listener\\ActivityUpdaterListener' => __DIR__ . '/..' . '/../lib/Listener/ActivityUpdaterListener.php',
|
||||
'OCA\\DAV\\Listener\\AddMissingIndicesListener' => __DIR__ . '/..' . '/../lib/Listener/AddMissingIndicesListener.php',
|
||||
'OCA\\DAV\\Listener\\AddressbookListener' => __DIR__ . '/..' . '/../lib/Listener/AddressbookListener.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,19 @@ 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;
|
||||
if ($storage instanceof PublicShareWrapper) {
|
||||
$share = $storage->getShare();
|
||||
$allowDirectory =
|
||||
// Only allow directories for file drops
|
||||
($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ &&
|
||||
// And only allow it for directories which are a direct child of the share root
|
||||
$this->info->getId() === $share->getNodeId();
|
||||
}
|
||||
|
||||
// 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 +212,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) {
|
||||
|
|
|
|||
|
|
@ -720,15 +720,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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@
|
|||
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;
|
||||
|
|
@ -28,12 +30,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 {
|
||||
|
||||
|
|
@ -54,13 +58,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);
|
||||
|
|
@ -81,7 +94,7 @@ class ServerFactory {
|
|||
$server->addPlugin(new RequestIdHeaderPlugin($this->request));
|
||||
|
||||
$server->addPlugin(new ZipFolderPlugin(
|
||||
$objectTree,
|
||||
$tree,
|
||||
$this->logger,
|
||||
$this->eventDispatcher,
|
||||
));
|
||||
|
|
@ -101,7 +114,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();
|
||||
|
||||
|
|
@ -115,15 +128,39 @@ 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 \OCA\DAV\Files\Sharing\RootCollection(
|
||||
$root,
|
||||
$userPrincipalBackend,
|
||||
'principals/shares',
|
||||
));
|
||||
} else {
|
||||
/** @var ObjectTree $tree */
|
||||
$tree->init($root, $view, $this->mountManager);
|
||||
}
|
||||
|
||||
$server->addPlugin(
|
||||
new FilesPlugin(
|
||||
$objectTree,
|
||||
$tree,
|
||||
$this->config,
|
||||
$this->request,
|
||||
$this->previewManager,
|
||||
|
|
@ -143,16 +180,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),
|
||||
|
|
@ -167,7 +204,7 @@ class ServerFactory {
|
|||
new \Sabre\DAV\PropertyStorage\Plugin(
|
||||
new CustomPropertiesBackend(
|
||||
$server,
|
||||
$objectTree,
|
||||
$tree,
|
||||
$this->databaseConnection,
|
||||
$this->userSession->getUser(),
|
||||
\OCP\Server::get(DefaultCalendarValidator::class),
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class FilesDropPlugin extends ServerPlugin {
|
|||
if ($isFileRequest && ($nickName == null || trim($nickName) === '')) {
|
||||
throw new MethodNotAllowed('Nickname is required for file requests');
|
||||
}
|
||||
|
||||
|
||||
// If this is a file request we need to create a folder for the user
|
||||
if ($isFileRequest) {
|
||||
// Check if the folder already exists
|
||||
|
|
@ -83,9 +83,9 @@ class FilesDropPlugin extends ServerPlugin {
|
|||
// Put all files in the subfolder
|
||||
$path = $nickName . '/' . $path;
|
||||
}
|
||||
|
||||
|
||||
$newName = \OC_Helper::buildNotExistingFileNameForView('/', $path, $this->view);
|
||||
$url = $request->getBaseUrl() . $newName;
|
||||
$url = $request->getBaseUrl() . '/files/' . $this->share->getToken() . $newName;
|
||||
$request->setUrl($url);
|
||||
}
|
||||
|
||||
|
|
|
|||
32
apps/dav/lib/Files/Sharing/RootCollection.php
Normal file
32
apps/dav/lib/Files/Sharing/RootCollection.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\Files\Sharing;
|
||||
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAVACL\AbstractPrincipalCollection;
|
||||
use Sabre\DAVACL\PrincipalBackend\BackendInterface;
|
||||
|
||||
class RootCollection extends AbstractPrincipalCollection {
|
||||
public function __construct(
|
||||
private INode $root,
|
||||
BackendInterface $principalBackend,
|
||||
string $principalPrefix = 'principals',
|
||||
) {
|
||||
parent::__construct($principalBackend, $principalPrefix);
|
||||
}
|
||||
|
||||
public function getChildForPrincipal(array $principalInfo): INode {
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return 'files';
|
||||
}
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ abstract class RequestTestCase extends TestCase {
|
|||
$authBackend = new Auth($user, $password);
|
||||
$authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend);
|
||||
|
||||
$server = $this->serverFactory->createServer('/', 'dummy', $authPlugin, function () use ($view) {
|
||||
$server = $this->serverFactory->createServer(false, '/', 'dummy', $authPlugin, function () use ($view) {
|
||||
return $view;
|
||||
});
|
||||
$server->addPlugin($exceptionPlugin);
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ class FilesDropPluginTest extends TestCase {
|
|||
$this->share->expects($this->any())
|
||||
->method('getAttributes')
|
||||
->willReturn($attributes);
|
||||
|
||||
$this->share
|
||||
->method('getToken')
|
||||
->willReturn('token');
|
||||
}
|
||||
|
||||
public function testInitialize(): void {
|
||||
|
|
@ -86,7 +90,7 @@ class FilesDropPluginTest extends TestCase {
|
|||
->willReturn('PUT');
|
||||
|
||||
$this->request->method('getPath')
|
||||
->willReturn('file.txt');
|
||||
->willReturn('/files/token/file.txt');
|
||||
|
||||
$this->request->method('getBaseUrl')
|
||||
->willReturn('https://example.com');
|
||||
|
|
@ -97,7 +101,7 @@ class FilesDropPluginTest extends TestCase {
|
|||
|
||||
$this->request->expects($this->once())
|
||||
->method('setUrl')
|
||||
->with('https://example.com/file.txt');
|
||||
->with('https://example.com/files/token/file.txt');
|
||||
|
||||
$this->plugin->beforeMethod($this->request, $this->response);
|
||||
}
|
||||
|
|
@ -111,7 +115,7 @@ class FilesDropPluginTest extends TestCase {
|
|||
->willReturn('PUT');
|
||||
|
||||
$this->request->method('getPath')
|
||||
->willReturn('file.txt');
|
||||
->willReturn('/files/token/file.txt');
|
||||
|
||||
$this->request->method('getBaseUrl')
|
||||
->willReturn('https://example.com');
|
||||
|
|
@ -127,7 +131,7 @@ class FilesDropPluginTest extends TestCase {
|
|||
|
||||
$this->request->expects($this->once())
|
||||
->method('setUrl')
|
||||
->with($this->equalTo('https://example.com/file (2).txt'));
|
||||
->with($this->equalTo('https://example.com/files/token/file (2).txt'));
|
||||
|
||||
$this->plugin->beforeMethod($this->request, $this->response);
|
||||
}
|
||||
|
|
@ -154,7 +158,7 @@ class FilesDropPluginTest extends TestCase {
|
|||
->willReturn('PUT');
|
||||
|
||||
$this->request->method('getPath')
|
||||
->willReturn('folder/file.txt');
|
||||
->willReturn('/files/token/folder/file.txt');
|
||||
|
||||
$this->request->method('getBaseUrl')
|
||||
->willReturn('https://example.com');
|
||||
|
|
@ -170,7 +174,7 @@ class FilesDropPluginTest extends TestCase {
|
|||
|
||||
$this->request->expects($this->once())
|
||||
->method('setUrl')
|
||||
->with($this->equalTo('https://example.com/file (2).txt'));
|
||||
->with($this->equalTo('https://example.com/files/token/file (2).txt'));
|
||||
|
||||
$this->plugin->beforeMethod($this->request, $this->response);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue