richdocuments/lib/Storage/SecureViewWrapper.php
Arthur Schiwon 3e90cc881c
refactor: untangle Node|ICacheEntry parameter in shouldWatermark
Results in split methods as we do not have nice overloading in PHP. But
simplifies parameters when calling them, while implementation is being
hidden yet simplified as it does not have to deal with different objects
but a few primitive types.

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2025-10-07 20:05:46 +02:00

119 lines
3.8 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Richdocuments\Storage;
use OC\Files\Storage\Wrapper\Wrapper;
use OCA\Richdocuments\Middleware\WOPIMiddleware;
use OCA\Richdocuments\PermissionManager;
use OCP\Files\ForbiddenException;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\ISharedStorage;
use OCP\Files\Storage\IStorage;
use OCP\IUserSession;
use OCP\Server;
class SecureViewWrapper extends Wrapper {
private PermissionManager $permissionManager;
private WOPIMiddleware $wopiMiddleware;
private IRootFolder $rootFolder;
private IUserSession $userSession;
private string $mountPoint;
public function __construct(array $parameters) {
parent::__construct($parameters);
$this->permissionManager = Server::get(PermissionManager::class);
$this->wopiMiddleware = Server::get(WOPIMiddleware::class);
$this->rootFolder = Server::get(IRootFolder::class);
$this->userSession = Server::get(IUserSession::class);
$this->mountPoint = $parameters['mountPoint'];
}
public function fopen($path, $mode) {
$this->checkFileAccess($path);
return $this->storage->fopen($path, $mode);
}
public function file_get_contents(string $path): false|string {
$this->checkFileAccess($path);
return $this->storage->file_get_contents($path);
}
public function copy(string $source, string $target): bool {
$this->checkSourceAndTarget($source, $target);
return parent::copy($source, $target);
}
public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
$this->checkSourceAndTarget($sourceInternalPath, $targetInternalPath, $sourceStorage);
return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
}
public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
$this->checkSourceAndTarget($sourceInternalPath, $targetInternalPath, $sourceStorage);
return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
}
public function rename(string $source, string $target): bool {
$this->checkSourceAndTarget($source, $target);
return parent::rename($source, $target);
}
/**
* @throws ForbiddenException
*/
private function checkFileAccess(string $path): void {
if ($this->shouldSecure($path) && !$this->wopiMiddleware->isWOPIRequest()) {
throw new ForbiddenException('Download blocked due the secure view policy', false);
}
}
private function shouldSecure(string $path, ?IStorage $sourceStorage = null): bool {
if ($sourceStorage !== $this && $sourceStorage !== null) {
$fp = $sourceStorage->fopen($path, 'r');
fclose($fp);
}
$storage = $sourceStorage ?? $this;
$cacheEntry = $storage->getCache()->get($path);
if (!$cacheEntry) {
$parent = dirname($path);
if ($parent === '.') {
$parent = '';
}
$cacheEntry = $storage->getCache()->get($parent);
if (!$cacheEntry) {
throw new NotFoundException(sprintf('Could not find cache entry for path and parent of %s within storage %s ', $path, $storage->getId()));
}
}
$isSharedStorage = $storage->instanceOfStorage(ISharedStorage::class);
$share = $isSharedStorage ? $storage->getShare() : null;
$userId = $this->userSession->getUser()?->getUID();
return $this->permissionManager->shouldWatermarkByCacheEntry($cacheEntry, $userId, $share, $storage->getOwner($path) ?: null);
}
private function checkSourceAndTarget(string $source, string $target, ?IStorage $sourceStorage = null): void {
if ($this->shouldSecure($source, $sourceStorage) && !$this->shouldSecure($target)) {
throw new ForbiddenException('Download blocked due the secure view policy. The source requires secure view that the target cannot offer.', false);
}
}
}