summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/dav/lib/Connector/Sabre/ServerFactory.php2
-rw-r--r--apps/dav/lib/DAV/ViewOnlyPlugin.php20
-rw-r--r--apps/dav/lib/Server.php2
-rw-r--r--apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php28
-rw-r--r--apps/dav/tests/unit/ServerTest.php1
-rw-r--r--apps/files_versions/lib/Versions/LegacyVersionsBackend.php38
-rw-r--r--apps/files_versions/src/components/Version.vue39
7 files changed, 117 insertions, 13 deletions
diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php
index 4c57f3412e3..755d13f8371 100644
--- a/apps/dav/lib/Connector/Sabre/ServerFactory.php
+++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php
@@ -161,7 +161,7 @@ class ServerFactory {
// Allow view-only plugin for webdav requests
$server->addPlugin(new ViewOnlyPlugin(
- $this->logger
+ $userFolder,
));
if ($this->userSession->isLoggedIn()) {
diff --git a/apps/dav/lib/DAV/ViewOnlyPlugin.php b/apps/dav/lib/DAV/ViewOnlyPlugin.php
index 51e3622142d..0ae472460be 100644
--- a/apps/dav/lib/DAV/ViewOnlyPlugin.php
+++ b/apps/dav/lib/DAV/ViewOnlyPlugin.php
@@ -24,8 +24,8 @@ namespace OCA\DAV\DAV;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\File as DavFile;
use OCA\Files_Versions\Sabre\VersionFile;
+use OCP\Files\Folder;
use OCP\Files\NotFoundException;
-use Psr\Log\LoggerInterface;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
@@ -36,10 +36,12 @@ use Sabre\DAV\Exception\NotFound;
*/
class ViewOnlyPlugin extends ServerPlugin {
private ?Server $server = null;
- private LoggerInterface $logger;
+ private ?Folder $userFolder;
- public function __construct(LoggerInterface $logger) {
- $this->logger = $logger;
+ public function __construct(
+ ?Folder $userFolder,
+ ) {
+ $this->userFolder = $userFolder;
}
/**
@@ -76,6 +78,16 @@ class ViewOnlyPlugin extends ServerPlugin {
$node = $davNode->getNode();
} else if ($davNode instanceof VersionFile) {
$node = $davNode->getVersion()->getSourceFile();
+ $currentUserId = $this->userFolder?->getOwner()?->getUID();
+ // The version source file is relative to the owner storage.
+ // But we need the node from the current user perspective.
+ if ($node->getOwner()->getUID() !== $currentUserId) {
+ $nodes = $this->userFolder->getById($node->getId());
+ $node = array_pop($nodes);
+ if (!$node) {
+ throw new NotFoundException("Version file not accessible by current user");
+ }
+ }
} else {
return true;
}
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index 37b5eb3b70b..809b804796d 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -236,7 +236,7 @@ class Server {
// Allow view-only plugin for webdav requests
$this->server->addPlugin(new ViewOnlyPlugin(
- $logger
+ \OC::$server->getUserFolder(),
));
if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
diff --git a/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php b/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php
index e0f6f1302a5..a00a04d147f 100644
--- a/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php
+++ b/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php
@@ -26,10 +26,11 @@ use OCA\DAV\Connector\Sabre\File as DavFile;
use OCA\Files_Versions\Versions\IVersion;
use OCA\Files_Versions\Sabre\VersionFile;
use OCP\Files\File;
+use OCP\Files\Folder;
use OCP\Files\Storage\IStorage;
+use OCP\IUser;
use OCP\Share\IAttributes;
use OCP\Share\IShare;
-use Psr\Log\LoggerInterface;
use Sabre\DAV\Server;
use Sabre\DAV\Tree;
use Test\TestCase;
@@ -43,10 +44,13 @@ class ViewOnlyPluginTest extends TestCase {
private $tree;
/** @var RequestInterface | \PHPUnit\Framework\MockObject\MockObject */
private $request;
+ /** @var Folder | \PHPUnit\Framework\MockObject\MockObject */
+ private $userFolder;
public function setUp(): void {
+ $this->userFolder = $this->createMock(Folder::class);
$this->plugin = new ViewOnlyPlugin(
- $this->createMock(LoggerInterface::class)
+ $this->userFolder,
);
$this->request = $this->createMock(RequestInterface::class);
$this->tree = $this->createMock(Tree::class);
@@ -111,6 +115,26 @@ class ViewOnlyPluginTest extends TestCase {
$davNode->expects($this->once())
->method('getVersion')
->willReturn($version);
+
+ $currentUser = $this->createMock(IUser::class);
+ $currentUser->expects($this->once())
+ ->method('getUID')
+ ->willReturn('alice');
+ $nodeInfo->expects($this->once())
+ ->method('getOwner')
+ ->willReturn($currentUser);
+
+ $nodeInfo = $this->createMock(File::class);
+ $owner = $this->createMock(IUser::class);
+ $owner->expects($this->once())
+ ->method('getUID')
+ ->willReturn('bob');
+ $this->userFolder->expects($this->once())
+ ->method('getById')
+ ->willReturn([$nodeInfo]);
+ $this->userFolder->expects($this->once())
+ ->method('getOwner')
+ ->willReturn($owner);
} else {
$davPath = 'files/path/to/file.odt';
$davNode = $this->createMock(DavFile::class);
diff --git a/apps/dav/tests/unit/ServerTest.php b/apps/dav/tests/unit/ServerTest.php
index 62e2accd697..26309d5fcd4 100644
--- a/apps/dav/tests/unit/ServerTest.php
+++ b/apps/dav/tests/unit/ServerTest.php
@@ -45,6 +45,7 @@ class ServerTest extends \Test\TestCase {
/** @var IRequest | \PHPUnit\Framework\MockObject\MockObject $r */
$r = $this->createMock(IRequest::class);
$r->expects($this->any())->method('getRequestUri')->willReturn($uri);
+ $this->loginAsUser('admin');
$s = new Server($r, '/');
$this->assertNotNull($s->server);
foreach ($plugins as $plugin) {
diff --git a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php
index 0820266d627..8b2d2caf40e 100644
--- a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php
+++ b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php
@@ -27,6 +27,7 @@ declare(strict_types=1);
namespace OCA\Files_Versions\Versions;
use OC\Files\View;
+use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\Files_Sharing\ISharedStorage;
use OCA\Files_Sharing\SharedStorage;
use OCA\Files_Versions\Db\VersionEntity;
@@ -42,23 +43,27 @@ use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;
use OCP\IUser;
use OCP\IUserManager;
+use OCP\IUserSession;
class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend {
private IRootFolder $rootFolder;
private IUserManager $userManager;
private VersionsMapper $versionsMapper;
private IMimeTypeLoader $mimeTypeLoader;
+ private IUserSession $userSession;
public function __construct(
IRootFolder $rootFolder,
IUserManager $userManager,
VersionsMapper $versionsMapper,
- IMimeTypeLoader $mimeTypeLoader
+ IMimeTypeLoader $mimeTypeLoader,
+ IUserSession $userSession,
) {
$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
$this->versionsMapper = $versionsMapper;
$this->mimeTypeLoader = $mimeTypeLoader;
+ $this->userSession = $userSession;
}
public function useBackendForStorage(IStorage $storage): bool {
@@ -174,6 +179,10 @@ class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend,
}
public function rollback(IVersion $version) {
+ if (!$this->currentUserHasPermissions($version, \OCP\Constants::PERMISSION_UPDATE)) {
+ throw new Forbidden('You cannot restore this version because you do not have update permissions on the source file.');
+ }
+
return Storage::rollback($version->getVersionPath(), $version->getRevisionId(), $version->getUser());
}
@@ -220,6 +229,10 @@ class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend,
}
public function setVersionLabel(IVersion $version, string $label): void {
+ if (!$this->currentUserHasPermissions($version, \OCP\Constants::PERMISSION_UPDATE)) {
+ throw new Forbidden('You cannot label this version because you do not have update permissions on the source file.');
+ }
+
$versionEntity = $this->versionsMapper->findVersionForFileId(
$version->getSourceFile()->getId(),
$version->getTimestamp(),
@@ -232,6 +245,10 @@ class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend,
}
public function deleteVersion(IVersion $version): void {
+ if (!$this->currentUserHasPermissions($version, \OCP\Constants::PERMISSION_DELETE)) {
+ throw new Forbidden('You cannot delete this version because you do not have delete permissions on the source file.');
+ }
+
Storage::deleteRevision($version->getVersionPath(), $version->getRevisionId());
$versionEntity = $this->versionsMapper->findVersionForFileId(
$version->getSourceFile()->getId(),
@@ -271,4 +288,23 @@ class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend,
public function deleteVersionsEntity(File $file): void {
$this->versionsMapper->deleteAllVersionsForFileId($file->getId());
}
+
+ private function currentUserHasPermissions(IVersion $version, int $permissions): bool {
+ $sourceFile = $version->getSourceFile();
+ $currentUserId = $this->userSession->getUser()?->getUID();
+
+ if ($currentUserId === null) {
+ throw new NotFoundException("No user logged in");
+ }
+
+ if ($sourceFile->getOwner()?->getUID() !== $currentUserId) {
+ $nodes = $this->rootFolder->getUserFolder($currentUserId)->getById($sourceFile->getId());
+ $sourceFile = array_pop($nodes);
+ if (!$sourceFile) {
+ throw new NotFoundException("Version file not accessible by current user");
+ }
+ }
+
+ return ($sourceFile->getPermissions() & $permissions) === $permissions;
+ }
}
diff --git a/apps/files_versions/src/components/Version.vue b/apps/files_versions/src/components/Version.vue
index 36f429b26c1..a5f5c05b6d5 100644
--- a/apps/files_versions/src/components/Version.vue
+++ b/apps/files_versions/src/components/Version.vue
@@ -47,7 +47,7 @@
</div>
</template>
<template #actions>
- <NcActionButton v-if="enableLabeling"
+ <NcActionButton v-if="enableLabeling && hasUpdatePermissions"
:close-after-click="true"
@click="openVersionLabelModal">
<template #icon>
@@ -63,7 +63,7 @@
</template>
{{ t('files_versions', 'Compare to current version') }}
</NcActionButton>
- <NcActionButton v-if="!isCurrent"
+ <NcActionButton v-if="!isCurrent && hasUpdatePermissions"
:close-after-click="true"
@click="restoreVersion">
<template #icon>
@@ -71,7 +71,8 @@
</template>
{{ t('files_versions', 'Restore version') }}
</NcActionButton>
- <NcActionLink :href="downloadURL"
+ <NcActionLink v-if="isDownloadable"
+ :href="downloadURL"
:close-after-click="true"
:download="downloadURL">
<template #icon>
@@ -79,7 +80,7 @@
</template>
{{ t('files_versions', 'Download version') }}
</NcActionLink>
- <NcActionButton v-if="!isCurrent && enableDeletion"
+ <NcActionButton v-if="!isCurrent && enableDeletion && hasDeletePermissions"
:close-after-click="true"
@click="deleteVersion">
<template #icon>
@@ -136,6 +137,9 @@ import { translate } from '@nextcloud/l10n'
import { joinPaths } from '@nextcloud/paths'
import { getRootUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
+import { Permission } from '@nextcloud/files'
+
+import { hasPermissions } from '../../../files_sharing/src/lib/SharePermissionsToolBox.js'
export default {
name: 'Version',
@@ -260,6 +264,33 @@ export default {
enableDeletion() {
return this.capabilities.files.version_deletion === true
},
+
+ /** @return {boolean} */
+ hasDeletePermissions() {
+ return hasPermissions(this.fileInfo.permissions, Permission.DELETE)
+ },
+
+ /** @return {boolean} */
+ hasUpdatePermissions() {
+ return hasPermissions(this.fileInfo.permissions, Permission.UPDATE)
+ },
+
+ /** @return {boolean} */
+ isDownloadable() {
+ if ((this.fileInfo.permissions & Permission.READ) === 0) {
+ return false
+ }
+
+ // If the mount type is a share, ensure it got download permissions.
+ if (this.fileInfo.mountType === 'shared') {
+ const downloadAttribute = this.fileInfo.shareAttributes.find((attribute) => attribute.scope === 'permissions' && attribute.key === 'download')
+ if (downloadAttribute !== undefined && downloadAttribute.enabled === false) {
+ return false
+ }
+ }
+
+ return true
+ },
},
methods: {
openVersionLabelModal() {