mirror of
https://github.com/nextcloud/server.git
synced 2026-02-20 00:12:30 -05:00
fix: add INodeByPath to Directory
Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>
This commit is contained in:
parent
aaf07ab73e
commit
2a4ee2df9f
2 changed files with 225 additions and 1 deletions
|
|
@ -8,6 +8,7 @@
|
|||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use OC\Files\Mount\MoveableMount;
|
||||
use OC\Files\Utils\PathHelper;
|
||||
use OC\Files\View;
|
||||
use OCA\DAV\AppInfo\Application;
|
||||
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
|
||||
|
|
@ -38,8 +39,14 @@ use Sabre\DAV\Exception\NotFound;
|
|||
use Sabre\DAV\Exception\ServiceUnavailable;
|
||||
use Sabre\DAV\IFile;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\INodeByPath;
|
||||
|
||||
class Directory extends Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget, \Sabre\DAV\ICopyTarget {
|
||||
class Directory extends Node implements
|
||||
\Sabre\DAV\ICollection,
|
||||
\Sabre\DAV\IQuota,
|
||||
\Sabre\DAV\IMoveTarget,
|
||||
\Sabre\DAV\ICopyTarget,
|
||||
INodeByPath {
|
||||
/**
|
||||
* Cached directory content
|
||||
* @var FileInfo[]
|
||||
|
|
@ -490,4 +497,79 @@ class Directory extends Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuot
|
|||
public function getNode(): Folder {
|
||||
return $this->node;
|
||||
}
|
||||
|
||||
public function getNodeForPath($path): INode {
|
||||
$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->instanceOfStorage(PublicShareWrapper::class)) {
|
||||
$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();
|
||||
}
|
||||
|
||||
$destinationPath = PathHelper::normalizePath($this->getPath() . '/' . $path);
|
||||
$destinationDir = dirname($destinationPath);
|
||||
|
||||
try {
|
||||
$info = $this->getNode()->get($path);
|
||||
} catch (NotFoundException $e) {
|
||||
throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
|
||||
. ' could not be located');
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
|
||||
} catch (NotPermittedException $ex) {
|
||||
throw new InvalidPath($ex->getMessage(), false, $ex);
|
||||
}
|
||||
|
||||
// if not in a public share with no read permissions, throw Forbidden
|
||||
if (!$allowDirectory && !$info->isReadable()) {
|
||||
if (Server::get(IAppManager::class)->isEnabledForAnyone('files_accesscontrol')) {
|
||||
throw new Forbidden('No read permissions. This might be caused by files_accesscontrol, check your configured rules');
|
||||
}
|
||||
|
||||
throw new Forbidden('No read permissions');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
$this->tree?->cacheNode($node);
|
||||
|
||||
// recurse upwards until the root and check for read permissions to keep
|
||||
// ACL checks working in files_accesscontrol
|
||||
if (!$allowDirectory && $destinationDir !== '') {
|
||||
$scanPath = $destinationPath;
|
||||
while (($scanPath = dirname($scanPath)) !== '/') {
|
||||
// fileView can get the parent info in a cheaper way compared
|
||||
// to the node API
|
||||
/** @psalm-suppress InternalMethod */
|
||||
$info = $this->fileView->getFileInfo($scanPath, false);
|
||||
$directory = new Directory($this->fileView, $info, $this->tree, $this->shareManager);
|
||||
$readable = $directory->getNode()->isReadable();
|
||||
if (!$readable) {
|
||||
throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
|
||||
. ' could not be located');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre;
|
|||
|
||||
use OC\Files\FileInfo;
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\Node\Folder;
|
||||
use OC\Files\Node\Node;
|
||||
use OC\Files\Storage\Wrapper\Quota;
|
||||
use OC\Files\View;
|
||||
|
|
@ -24,6 +25,7 @@ use OCP\Files\Mount\IMountPoint;
|
|||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Test\Traits\UserTrait;
|
||||
|
||||
class TestViewDirectory extends View {
|
||||
|
|
@ -271,6 +273,146 @@ class DirectoryTest extends \Test\TestCase {
|
|||
$dir->getChild('.');
|
||||
}
|
||||
|
||||
public function testGetNodeForPath(): void {
|
||||
$directoryNode = $this->createMock(Folder::class);
|
||||
$pathNode = $this->createMock(Folder::class);
|
||||
$pathParentNode = $this->createMock(Folder::class);
|
||||
$storage = $this->createMock(IStorage::class);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getStorage')
|
||||
->willReturn($storage);
|
||||
$storage->expects($this->once())
|
||||
->method('instanceOfStorage')
|
||||
->willReturn(false);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/');
|
||||
$directoryNode->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn($pathNode);
|
||||
|
||||
$pathNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/my/deep/folder/');
|
||||
$pathNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$pathNode->expects($this->once())
|
||||
->method('getMimetype')
|
||||
->willReturn(FileInfo::MIMETYPE_FOLDER);
|
||||
|
||||
$this->view->method('getRelativePath')
|
||||
->willReturnCallback(function ($path) {
|
||||
return str_replace('/admin/files/', '', $path);
|
||||
});
|
||||
|
||||
$this->view->expects($this->exactly(2))
|
||||
->method('getFileInfo')
|
||||
->willReturn($pathParentNode);
|
||||
|
||||
$pathParentNode->expects($this->exactly(2))
|
||||
->method('getPath')
|
||||
->willReturnOnConsecutiveCalls('/my/deep', '/my');
|
||||
$pathParentNode->expects($this->exactly(2))
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
|
||||
$dir = new Directory($this->view, $directoryNode);
|
||||
$dir->getNodeForPath('/my/deep/folder/');
|
||||
}
|
||||
|
||||
public function testGetNodeForPathFailsWithNoReadPermissionsForParent(): void {
|
||||
$directoryNode = $this->createMock(Folder::class);
|
||||
$pathNode = $this->createMock(Folder::class);
|
||||
$pathParentNode = $this->createMock(Folder::class);
|
||||
$storage = $this->createMock(IStorage::class);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getStorage')
|
||||
->willReturn($storage);
|
||||
$storage->expects($this->once())
|
||||
->method('instanceOfStorage')
|
||||
->willReturn(false);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/');
|
||||
$directoryNode->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn($pathNode);
|
||||
|
||||
$pathNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/my/deep/folder/');
|
||||
$pathNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$pathNode->expects($this->once())
|
||||
->method('getMimetype')
|
||||
->willReturn(FileInfo::MIMETYPE_FOLDER);
|
||||
|
||||
$this->view->method('getRelativePath')
|
||||
->willReturnCallback(function ($path) {
|
||||
return str_replace('/admin/files/', '', $path);
|
||||
});
|
||||
|
||||
$this->view->expects($this->exactly(2))
|
||||
->method('getFileInfo')
|
||||
->willReturn($pathParentNode);
|
||||
|
||||
$pathParentNode->expects($this->exactly(2))
|
||||
->method('getPath')
|
||||
->willReturnOnConsecutiveCalls('/my/deep', '/my');
|
||||
$pathParentNode->expects($this->exactly(2))
|
||||
->method('isReadable')
|
||||
->willReturnOnConsecutiveCalls(true, false);
|
||||
|
||||
$this->expectException(NotFound::class);
|
||||
|
||||
$dir = new Directory($this->view, $directoryNode);
|
||||
$dir->getNodeForPath('/my/deep/folder/');
|
||||
}
|
||||
|
||||
public function testGetNodeForPathFailsWithNoReadPermissionsForPath(): void {
|
||||
$directoryNode = $this->createMock(Folder::class);
|
||||
$pathNode = $this->createMock(Folder::class);
|
||||
$storage = $this->createMock(IStorage::class);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getStorage')
|
||||
->willReturn($storage);
|
||||
$storage->expects($this->once())
|
||||
->method('instanceOfStorage')
|
||||
->willReturn(false);
|
||||
|
||||
$directoryNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(true);
|
||||
$directoryNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->willReturn('/admin/files/');
|
||||
$directoryNode->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn($pathNode);
|
||||
|
||||
$pathNode->expects($this->once())
|
||||
->method('isReadable')
|
||||
->willReturn(false);
|
||||
|
||||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
|
||||
|
||||
$dir = new Directory($this->view, $directoryNode);
|
||||
$dir->getNodeForPath('/my/deep/folder/');
|
||||
}
|
||||
|
||||
public function testGetQuotaInfoUnlimited(): void {
|
||||
$this->createUser('user', 'password');
|
||||
self::loginAsUser('user');
|
||||
|
|
|
|||
Loading…
Reference in a new issue