Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable24] Add share attributes + prevent download permission #33416

Merged
merged 15 commits into from
Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions apps/dav/lib/Connector/Sabre/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use OC\Files\Mount\MoveableMount;
use OC\Files\Node\File;
use OC\Files\Node\Folder;
use OC\Files\Storage\Wrapper\Wrapper;
use OC\Files\View;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
use OCP\Files\FileInfo;
Expand Down Expand Up @@ -334,9 +335,14 @@ public function getShareAttributes(): array {
$storage = null;
}

if ($storage && $storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
if ($storage && $storage->instanceOfStorage(\OCA\Files_Sharing\SharedStorage::class)) {
PVince81 marked this conversation as resolved.
Show resolved Hide resolved
/** @var \OCA\Files_Sharing\SharedStorage $storage */
$attributes = $storage->getShare()->getAttributes()->toArray();
$attributes = $storage->getShare()->getAttributes();
if ($attributes === null) {
return [];
} else {
return $attributes->toArray();
}
}

return $attributes;
Expand Down
8 changes: 7 additions & 1 deletion apps/dav/lib/Connector/Sabre/ServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
use OCP\ITagManager;
use OCP\IUserSession;
use OCP\SabrePluginEvent;
use Psr\Log\LoggerInterface;
use Sabre\DAV\Auth\Plugin;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

Expand All @@ -53,6 +54,8 @@ class ServerFactory {
private $config;
/** @var ILogger */
private $logger;
/** @var LoggerInterface */
private $psrLogger;
/** @var IDBConnection */
private $databaseConnection;
/** @var IUserSession */
Expand All @@ -73,6 +76,7 @@ class ServerFactory {
/**
* @param IConfig $config
* @param ILogger $logger
* @param LoggerInterface $psrLogger
* @param IDBConnection $databaseConnection
* @param IUserSession $userSession
* @param IMountManager $mountManager
Expand All @@ -83,6 +87,7 @@ class ServerFactory {
public function __construct(
IConfig $config,
ILogger $logger,
LoggerInterface $psrLogger,
IDBConnection $databaseConnection,
IUserSession $userSession,
IMountManager $mountManager,
Expand All @@ -94,6 +99,7 @@ public function __construct(
) {
$this->config = $config;
$this->logger = $logger;
$this->psrLogger = $psrLogger;
$this->databaseConnection = $databaseConnection;
$this->userSession = $userSession;
$this->mountManager = $mountManager;
Expand Down Expand Up @@ -185,7 +191,7 @@ public function createServer($baseUri,

// Allow view-only plugin for webdav requests
$server->addPlugin(new ViewOnlyPlugin(
$this->logger
$this->psrLogger
));

if ($this->userSession->isLoggedIn()) {
Expand Down
7 changes: 4 additions & 3 deletions apps/dav/lib/Controller/DirectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\GenericEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\BeforeDirectFileDownloadEvent;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\IRequest;
Expand Down Expand Up @@ -106,10 +107,10 @@ public function getUrl(int $fileId, int $expirationTime = 60 * 60 * 8): DataResp
throw new OCSBadRequestException('Direct download only works for files');
}

$event = new GenericEvent(null, ['path' => $userFolder->getRelativePath($file->getPath())]);
$this->eventDispatcher->dispatch('file.beforeGetDirect', $event);
$event = new BeforeDirectFileDownloadEvent($userFolder->getRelativePath($file->getPath()));

Check notice

Code scanning / Psalm

PossiblyNullArgument

Argument 1 of OCP\Files\Events\BeforeDirectFileDownloadEvent::__construct cannot be null, possibly null value provided
$this->eventDispatcher->dispatchTyped($event);

if ($event->getArgument('run') === false) {
if ($event->isSuccessful() === false) {
throw new OCSForbiddenException('Permission denied to download file');
}

Expand Down
38 changes: 16 additions & 22 deletions apps/dav/lib/DAV/ViewOnlyPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,11 @@
*/
class ViewOnlyPlugin extends ServerPlugin {

/** @var Server $server */
private $server;
private ?Server $server = null;
private LoggerInterface $logger;

/** @var LoggerInterface $logger */
private $logger;

/**
* @param LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
$this->server = null;
}

/**
Expand All @@ -58,11 +51,8 @@ public function __construct(LoggerInterface $logger) {
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param Server $server
* @return void
*/
public function initialize(Server $server) {
public function initialize(Server $server): void {
$this->server = $server;
//priority 90 to make sure the plugin is called before
//Sabre\DAV\CorePlugin::httpGet
Expand All @@ -73,17 +63,14 @@ public function initialize(Server $server) {
* Disallow download via DAV Api in case file being received share
* and having special permission
*
* @param RequestInterface $request request object
* @return boolean
* @throws Forbidden
* @throws NotFoundException
*/
public function checkViewOnly(
RequestInterface $request
) {
public function checkViewOnly(RequestInterface $request): bool {
$path = $request->getPath();

try {
assert($this->server !== null);
$davNode = $this->server->tree->getNodeForPath($path);
if (!($davNode instanceof DavFile)) {
return true;
Expand All @@ -92,21 +79,28 @@ public function checkViewOnly(
$node = $davNode->getNode();

$storage = $node->getStorage();
// using string as we have no guarantee that "files_sharing" app is loaded
if (!$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {

if (!$storage->instanceOfStorage(\OCA\Files_Sharing\SharedStorage::class)) {
CarlSchwan marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
// Extract extra permissions
/** @var \OCA\Files_Sharing\SharedStorage $storage */
$share = $storage->getShare();

$attributes = $share->getAttributes();
if ($attributes === null) {
return true;
}

// Check if read-only and on whether permission can download is both set and disabled.
$canDownload = $share->getAttributes()->getAttribute('permissions', 'download');
$canDownload = $attributes->getAttribute('permissions', 'download');
if ($canDownload !== null && !$canDownload) {
throw new Forbidden('Access to this resource has been denied because it is in view-only mode.');
}
} catch (NotFound $e) {
$this->logger->warning($e->getMessage());
$this->logger->warning($e->getMessage(), [
'exception' => $e,
]);
}

return true;
Expand Down
2 changes: 1 addition & 1 deletion apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public function __construct(IRequest $request, string $baseUri) {

// Allow view-only plugin for webdav requests
$this->server->addPlugin(new ViewOnlyPlugin(
$logger
\OC::$server->get(LoggerInterface::class)
));

if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
Expand Down
26 changes: 17 additions & 9 deletions apps/dav/tests/unit/Controller/DirectControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUrlGenerator;
use OCP\Security\ISecureRandom;
use Test\TestCase;

Expand All @@ -56,11 +57,13 @@ class DirectControllerTest extends TestCase {
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
private $timeFactory;

/** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
/** @var IUrlGenerator|\PHPUnit\Framework\MockObject\MockObject */
private $urlGenerator;

/** @var DirectController */
private $controller;
/** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
private $eventDispatcher;

private DirectController $controller;

protected function setUp(): void {
parent::setUp();
Expand All @@ -69,7 +72,8 @@ protected function setUp(): void {
$this->directMapper = $this->createMock(DirectMapper::class);
$this->random = $this->createMock(ISecureRandom::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->urlGenerator = $this->createMock(IUrlGenerator::class);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);

$this->controller = new DirectController(
'dav',
Expand All @@ -79,11 +83,12 @@ protected function setUp(): void {
$this->directMapper,
$this->random,
$this->timeFactory,
$this->urlGenerator
$this->urlGenerator,
$this->eventDispatcher
);
}

public function testGetUrlNonExistingFileId() {
public function testGetUrlNonExistingFileId(): void {
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with('awesomeUser')
Expand All @@ -97,7 +102,7 @@ public function testGetUrlNonExistingFileId() {
$this->controller->getUrl(101);
}

public function testGetUrlForFolder() {
public function testGetUrlForFolder(): void {
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with('awesomeUser')
Expand All @@ -113,7 +118,7 @@ public function testGetUrlForFolder() {
$this->controller->getUrl(101);
}

public function testGetUrlValid() {
public function testGetUrlValid(): void {
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with('awesomeUser')
Expand All @@ -128,6 +133,9 @@ public function testGetUrlValid() {
->with(101)
->willReturn([$file]);

$userFolder->method('getRelativePath')
->willReturn('/path');

$this->random->method('generate')
->with(
60,
Expand Down
11 changes: 5 additions & 6 deletions apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@

class ViewOnlyPluginTest extends TestCase {

/** @var ViewOnlyPlugin */
private $plugin;
private ViewOnlyPlugin $plugin;
/** @var Tree | \PHPUnit\Framework\MockObject\MockObject */
private $tree;
/** @var RequestInterface | \PHPUnit\Framework\MockObject\MockObject */
Expand All @@ -56,14 +55,14 @@ public function setUp(): void {
$this->plugin->initialize($server);
}

public function testCanGetNonDav() {
public function testCanGetNonDav(): void {
$this->request->expects($this->once())->method('getPath')->willReturn('files/test/target');
$this->tree->method('getNodeForPath')->willReturn(null);

$this->assertTrue($this->plugin->checkViewOnly($this->request));
}

public function testCanGetNonShared() {
public function testCanGetNonShared(): void {
$this->request->expects($this->once())->method('getPath')->willReturn('files/test/target');
$davNode = $this->createMock(DavFile::class);
$this->tree->method('getNodeForPath')->willReturn($davNode);
Expand All @@ -78,7 +77,7 @@ public function testCanGetNonShared() {
$this->assertTrue($this->plugin->checkViewOnly($this->request));
}

public function providesDataForCanGet() {
public function providesDataForCanGet(): array {
return [
// has attribute permissions-download enabled - can get file
[ $this->createMock(File::class), true, true],
Expand All @@ -92,7 +91,7 @@ public function providesDataForCanGet() {
/**
* @dataProvider providesDataForCanGet
*/
public function testCanGet($nodeInfo, $attrEnabled, $expectCanDownloadFile) {
public function testCanGet(File $nodeInfo, ?bool $attrEnabled, bool $expectCanDownloadFile): void {
$this->request->expects($this->once())->method('getPath')->willReturn('files/test/target');

$davNode = $this->createMock(DavFile::class);
Expand Down
Loading