mirror of
https://github.com/nextcloud/server.git
synced 2026-03-07 07:50:57 -05:00
Merge pull request #32482 from nextcloud/enh/noid/share-attributes
Add share attributes + prevent download permission
This commit is contained in:
commit
f74e89bde5
56 changed files with 1925 additions and 138 deletions
|
|
@ -191,6 +191,7 @@ return array(
|
|||
'OCA\\DAV\\DAV\\Sharing\\Xml\\Invite' => $baseDir . '/../lib/DAV/Sharing/Xml/Invite.php',
|
||||
'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest' => $baseDir . '/../lib/DAV/Sharing/Xml/ShareRequest.php',
|
||||
'OCA\\DAV\\DAV\\SystemPrincipalBackend' => $baseDir . '/../lib/DAV/SystemPrincipalBackend.php',
|
||||
'OCA\\DAV\\DAV\\ViewOnlyPlugin' => $baseDir . '/../lib/DAV/ViewOnlyPlugin.php',
|
||||
'OCA\\DAV\\Db\\Direct' => $baseDir . '/../lib/Db/Direct.php',
|
||||
'OCA\\DAV\\Db\\DirectMapper' => $baseDir . '/../lib/Db/DirectMapper.php',
|
||||
'OCA\\DAV\\Direct\\DirectFile' => $baseDir . '/../lib/Direct/DirectFile.php',
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ class ComposerStaticInitDAV
|
|||
'OCA\\DAV\\DAV\\Sharing\\Xml\\Invite' => __DIR__ . '/..' . '/../lib/DAV/Sharing/Xml/Invite.php',
|
||||
'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest' => __DIR__ . '/..' . '/../lib/DAV/Sharing/Xml/ShareRequest.php',
|
||||
'OCA\\DAV\\DAV\\SystemPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/SystemPrincipalBackend.php',
|
||||
'OCA\\DAV\\DAV\\ViewOnlyPlugin' => __DIR__ . '/..' . '/../lib/DAV/ViewOnlyPlugin.php',
|
||||
'OCA\\DAV\\Db\\Direct' => __DIR__ . '/..' . '/../lib/Db/Direct.php',
|
||||
'OCA\\DAV\\Db\\DirectMapper' => __DIR__ . '/..' . '/../lib/Db/DirectMapper.php',
|
||||
'OCA\\DAV\\Direct\\DirectFile' => __DIR__ . '/..' . '/../lib/Direct/DirectFile.php',
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class FilesPlugin extends ServerPlugin {
|
|||
public const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
|
||||
public const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
|
||||
public const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
|
||||
public const SHARE_ATTRIBUTES_PROPERTYNAME = '{http://nextcloud.org/ns}share-attributes';
|
||||
public const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
|
||||
public const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
|
||||
public const GETETAG_PROPERTYNAME = '{DAV:}getetag';
|
||||
|
|
@ -134,6 +135,7 @@ class FilesPlugin extends ServerPlugin {
|
|||
$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::SHARE_ATTRIBUTES_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
|
||||
$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
|
||||
|
|
@ -321,6 +323,10 @@ class FilesPlugin extends ServerPlugin {
|
|||
return json_encode($ocmPermissions);
|
||||
});
|
||||
|
||||
$propFind->handle(self::SHARE_ATTRIBUTES_PROPERTYNAME, function () use ($node, $httpRequest) {
|
||||
return json_encode($node->getShareAttributes());
|
||||
});
|
||||
|
||||
$propFind->handle(self::GETETAG_PROPERTYNAME, function () use ($node): string {
|
||||
return $node->getETag();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ namespace OCA\DAV\Connector\Sabre;
|
|||
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;
|
||||
|
|
@ -322,6 +323,31 @@ abstract class Node implements \Sabre\DAV\INode {
|
|||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getShareAttributes(): array {
|
||||
$attributes = [];
|
||||
|
||||
try {
|
||||
$storage = $this->info->getStorage();
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
$storage = null;
|
||||
}
|
||||
|
||||
if ($storage && $storage->instanceOfStorage(\OCA\Files_Sharing\SharedStorage::class)) {
|
||||
/** @var \OCA\Files_Sharing\SharedStorage $storage */
|
||||
$attributes = $storage->getShare()->getAttributes();
|
||||
if ($attributes === null) {
|
||||
return [];
|
||||
} else {
|
||||
return $attributes->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @return string
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ namespace OCA\DAV\Connector\Sabre;
|
|||
|
||||
use OCP\Files\Folder;
|
||||
use OCA\DAV\AppInfo\PluginManager;
|
||||
use OCA\DAV\DAV\ViewOnlyPlugin;
|
||||
use OCA\DAV\Files\BrowserErrorPagePlugin;
|
||||
use OCP\Files\Mount\IMountManager;
|
||||
use OCP\IConfig;
|
||||
|
|
@ -158,6 +159,11 @@ class ServerFactory {
|
|||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\QuotaPlugin($view, true));
|
||||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ChecksumUpdatePlugin());
|
||||
|
||||
// Allow view-only plugin for webdav requests
|
||||
$server->addPlugin(new ViewOnlyPlugin(
|
||||
$this->logger
|
||||
));
|
||||
|
||||
if ($this->userSession->isLoggedIn()) {
|
||||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager));
|
||||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\SharesPlugin(
|
||||
|
|
|
|||
|
|
@ -31,8 +31,12 @@ use OCA\DAV\Db\DirectMapper;
|
|||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCS\OCSBadRequestException;
|
||||
use OCP\AppFramework\OCS\OCSNotFoundException;
|
||||
use OCP\AppFramework\OCS\OCSForbiddenException;
|
||||
use OCP\AppFramework\OCSController;
|
||||
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;
|
||||
|
|
@ -59,6 +63,8 @@ class DirectController extends OCSController {
|
|||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
/** @var IEventDispatcher */
|
||||
private $eventDispatcher;
|
||||
|
||||
public function __construct(string $appName,
|
||||
IRequest $request,
|
||||
|
|
@ -67,7 +73,8 @@ class DirectController extends OCSController {
|
|||
DirectMapper $mapper,
|
||||
ISecureRandom $random,
|
||||
ITimeFactory $timeFactory,
|
||||
IURLGenerator $urlGenerator) {
|
||||
IURLGenerator $urlGenerator,
|
||||
IEventDispatcher $eventDispatcher) {
|
||||
parent::__construct($appName, $request);
|
||||
|
||||
$this->rootFolder = $rootFolder;
|
||||
|
|
@ -76,6 +83,7 @@ class DirectController extends OCSController {
|
|||
$this->random = $random;
|
||||
$this->timeFactory = $timeFactory;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -99,6 +107,13 @@ class DirectController extends OCSController {
|
|||
throw new OCSBadRequestException('Direct download only works for files');
|
||||
}
|
||||
|
||||
$event = new BeforeDirectFileDownloadEvent($userFolder->getRelativePath($file->getPath()));
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
|
||||
if ($event->isSuccessful() === false) {
|
||||
throw new OCSForbiddenException('Permission denied to download file');
|
||||
}
|
||||
|
||||
//TODO: at some point we should use the directdownlaod function of storages
|
||||
$direct = new Direct();
|
||||
$direct->setUserId($this->userId);
|
||||
|
|
|
|||
108
apps/dav/lib/DAV/ViewOnlyPlugin.php
Normal file
108
apps/dav/lib/DAV/ViewOnlyPlugin.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Piotr Mrowczynski piotr@owncloud.com
|
||||
*
|
||||
* @copyright Copyright (c) 2019, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\DAV;
|
||||
|
||||
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
|
||||
use OCA\DAV\Connector\Sabre\File as DavFile;
|
||||
use OCA\DAV\Meta\MetaFile;
|
||||
use OCP\Files\FileInfo;
|
||||
use OCP\Files\NotFoundException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\ServerPlugin;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
|
||||
/**
|
||||
* Sabre plugin for restricting file share receiver download:
|
||||
*/
|
||||
class ViewOnlyPlugin extends ServerPlugin {
|
||||
|
||||
private ?Server $server = null;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(LoggerInterface $logger) {
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* This initializes the plugin.
|
||||
*
|
||||
* This function is called by Sabre\DAV\Server, after
|
||||
* addPlugin is called.
|
||||
*
|
||||
* This method should set up the required event subscriptions.
|
||||
*/
|
||||
public function initialize(Server $server): void {
|
||||
$this->server = $server;
|
||||
//priority 90 to make sure the plugin is called before
|
||||
//Sabre\DAV\CorePlugin::httpGet
|
||||
$this->server->on('method:GET', [$this, 'checkViewOnly'], 90);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disallow download via DAV Api in case file being received share
|
||||
* and having special permission
|
||||
*
|
||||
* @throws Forbidden
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
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;
|
||||
}
|
||||
// Restrict view-only to nodes which are shared
|
||||
$node = $davNode->getNode();
|
||||
|
||||
$storage = $node->getStorage();
|
||||
|
||||
if (!$storage->instanceOfStorage(\OCA\Files_Sharing\SharedStorage::class)) {
|
||||
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 = $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(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +62,7 @@ use OCA\DAV\Connector\Sabre\SharesPlugin;
|
|||
use OCA\DAV\Connector\Sabre\TagsPlugin;
|
||||
use OCA\DAV\DAV\CustomPropertiesBackend;
|
||||
use OCA\DAV\DAV\PublicAuth;
|
||||
use OCA\DAV\DAV\ViewOnlyPlugin;
|
||||
use OCA\DAV\Events\SabrePluginAuthInitEvent;
|
||||
use OCA\DAV\Files\BrowserErrorPagePlugin;
|
||||
use OCA\DAV\Files\LazySearchBackend;
|
||||
|
|
@ -229,6 +230,11 @@ class Server {
|
|||
$this->server->addPlugin(new FakeLockerPlugin());
|
||||
}
|
||||
|
||||
// Allow view-only plugin for webdav requests
|
||||
$this->server->addPlugin(new ViewOnlyPlugin(
|
||||
$logger
|
||||
));
|
||||
|
||||
if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
|
||||
$this->server->addPlugin(new BrowserErrorPagePlugin());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,11 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre;
|
|||
|
||||
use OC\Files\FileInfo;
|
||||
use OC\Files\View;
|
||||
use OC\Share20\ShareAttributes;
|
||||
use OCA\Files_Sharing\SharedStorage;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\Storage;
|
||||
use OCP\Share\IAttributes;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
|
|
@ -169,6 +172,65 @@ class NodeTest extends \Test\TestCase {
|
|||
$this->assertEquals($expected, $node->getSharePermissions($user));
|
||||
}
|
||||
|
||||
public function testShareAttributes() {
|
||||
$storage = $this->getMockBuilder(SharedStorage::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['getShare'])
|
||||
->getMock();
|
||||
|
||||
$shareManager = $this->getMockBuilder(IManager::class)->disableOriginalConstructor()->getMock();
|
||||
$share = $this->getMockBuilder(IShare::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$storage->expects($this->once())
|
||||
->method('getShare')
|
||||
->willReturn($share);
|
||||
|
||||
$attributes = new ShareAttributes();
|
||||
$attributes->setAttribute('permissions', 'download', false);
|
||||
|
||||
$share->expects($this->once())->method('getAttributes')->willReturn($attributes);
|
||||
|
||||
$info = $this->getMockBuilder(FileInfo::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['getStorage', 'getType'])
|
||||
->getMock();
|
||||
|
||||
$info->method('getStorage')->willReturn($storage);
|
||||
$info->method('getType')->willReturn(FileInfo::TYPE_FOLDER);
|
||||
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$node = new \OCA\DAV\Connector\Sabre\File($view, $info);
|
||||
$this->invokePrivate($node, 'shareManager', [$shareManager]);
|
||||
$this->assertEquals($attributes->toArray(), $node->getShareAttributes());
|
||||
}
|
||||
|
||||
public function testShareAttributesNonShare() {
|
||||
$storage = $this->getMockBuilder(Storage::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$shareManager = $this->getMockBuilder(IManager::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$info = $this->getMockBuilder(FileInfo::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['getStorage', 'getType'])
|
||||
->getMock();
|
||||
|
||||
$info->method('getStorage')->willReturn($storage);
|
||||
$info->method('getType')->willReturn(FileInfo::TYPE_FOLDER);
|
||||
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$node = new \OCA\DAV\Connector\Sabre\File($view, $info);
|
||||
$this->invokePrivate($node, 'shareManager', [$shareManager]);
|
||||
$this->assertEquals([], $node->getShareAttributes());
|
||||
}
|
||||
|
||||
public function sanitizeMtimeProvider() {
|
||||
return [
|
||||
[123456789, 123456789],
|
||||
|
|
|
|||
|
|
@ -34,11 +34,12 @@ use OCP\AppFramework\Http\DataResponse;
|
|||
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;
|
||||
|
||||
|
|
@ -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();
|
||||
|
|
@ -69,7 +72,8 @@ class DirectControllerTest extends TestCase {
|
|||
$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',
|
||||
|
|
@ -79,11 +83,12 @@ class DirectControllerTest extends TestCase {
|
|||
$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')
|
||||
|
|
@ -97,7 +102,7 @@ class DirectControllerTest extends TestCase {
|
|||
$this->controller->getUrl(101);
|
||||
}
|
||||
|
||||
public function testGetUrlForFolder() {
|
||||
public function testGetUrlForFolder(): void {
|
||||
$userFolder = $this->createMock(Folder::class);
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
->with('awesomeUser')
|
||||
|
|
@ -113,7 +118,7 @@ class DirectControllerTest extends TestCase {
|
|||
$this->controller->getUrl(101);
|
||||
}
|
||||
|
||||
public function testGetUrlValid() {
|
||||
public function testGetUrlValid(): void {
|
||||
$userFolder = $this->createMock(Folder::class);
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
->with('awesomeUser')
|
||||
|
|
@ -128,6 +133,9 @@ class DirectControllerTest extends TestCase {
|
|||
->with(101)
|
||||
->willReturn([$file]);
|
||||
|
||||
$userFolder->method('getRelativePath')
|
||||
->willReturn('/path');
|
||||
|
||||
$this->random->method('generate')
|
||||
->with(
|
||||
60,
|
||||
|
|
|
|||
117
apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php
Normal file
117
apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Piotr Mrowczynski piotr@owncloud.com
|
||||
*
|
||||
* @copyright Copyright (c) 2019, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\Tests\unit\DAV;
|
||||
|
||||
use OCA\DAV\DAV\ViewOnlyPlugin;
|
||||
use OCA\Files_Sharing\SharedStorage;
|
||||
use OCA\DAV\Connector\Sabre\File as DavFile;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\Share\IAttributes;
|
||||
use OCP\Share\IShare;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\Tree;
|
||||
use Test\TestCase;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
|
||||
|
||||
class ViewOnlyPluginTest extends TestCase {
|
||||
|
||||
private ViewOnlyPlugin $plugin;
|
||||
/** @var Tree | \PHPUnit\Framework\MockObject\MockObject */
|
||||
private $tree;
|
||||
/** @var RequestInterface | \PHPUnit\Framework\MockObject\MockObject */
|
||||
private $request;
|
||||
|
||||
public function setUp(): void {
|
||||
$this->plugin = new ViewOnlyPlugin(
|
||||
$this->createMock(LoggerInterface::class)
|
||||
);
|
||||
$this->request = $this->createMock(RequestInterface::class);
|
||||
$this->tree = $this->createMock(Tree::class);
|
||||
|
||||
$server = $this->createMock(Server::class);
|
||||
$server->tree = $this->tree;
|
||||
|
||||
$this->plugin->initialize($server);
|
||||
}
|
||||
|
||||
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(): void {
|
||||
$this->request->expects($this->once())->method('getPath')->willReturn('files/test/target');
|
||||
$davNode = $this->createMock(DavFile::class);
|
||||
$this->tree->method('getNodeForPath')->willReturn($davNode);
|
||||
|
||||
$file = $this->createMock(File::class);
|
||||
$davNode->method('getNode')->willReturn($file);
|
||||
|
||||
$storage = $this->createMock(IStorage::class);
|
||||
$file->method('getStorage')->willReturn($storage);
|
||||
$storage->method('instanceOfStorage')->with(SharedStorage::class)->willReturn(false);
|
||||
|
||||
$this->assertTrue($this->plugin->checkViewOnly($this->request));
|
||||
}
|
||||
|
||||
public function providesDataForCanGet(): array {
|
||||
return [
|
||||
// has attribute permissions-download enabled - can get file
|
||||
[ $this->createMock(File::class), true, true],
|
||||
// has no attribute permissions-download - can get file
|
||||
[ $this->createMock(File::class), null, true],
|
||||
// has attribute permissions-download disabled- cannot get the file
|
||||
[ $this->createMock(File::class), false, false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providesDataForCanGet
|
||||
*/
|
||||
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);
|
||||
$this->tree->method('getNodeForPath')->willReturn($davNode);
|
||||
|
||||
$davNode->method('getNode')->willReturn($nodeInfo);
|
||||
|
||||
$storage = $this->createMock(SharedStorage::class);
|
||||
$share = $this->createMock(IShare::class);
|
||||
$nodeInfo->method('getStorage')->willReturn($storage);
|
||||
$storage->method('instanceOfStorage')->with(SharedStorage::class)->willReturn(true);
|
||||
$storage->method('getShare')->willReturn($share);
|
||||
|
||||
$extAttr = $this->createMock(IAttributes::class);
|
||||
$share->method('getAttributes')->willReturn($extAttr);
|
||||
$extAttr->method('getAttribute')->with('permissions', 'download')->willReturn($attrEnabled);
|
||||
|
||||
if (!$expectCanDownloadFile) {
|
||||
$this->expectException(Forbidden::class);
|
||||
}
|
||||
$this->plugin->checkViewOnly($this->request);
|
||||
}
|
||||
}
|
||||
|
|
@ -83,6 +83,15 @@
|
|||
return OC.joinPaths(this.get('path'), this.get('name'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the mimetype of the file
|
||||
*
|
||||
* @return {string} mimetype
|
||||
*/
|
||||
getMimeType: function() {
|
||||
return this.get('mimetype');
|
||||
},
|
||||
|
||||
/**
|
||||
* Reloads missing properties from server and set them in the model.
|
||||
* @param properties array of properties to be reloaded
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ export default async function(url) {
|
|||
<nc:mount-type />
|
||||
<nc:is-encrypted />
|
||||
<ocs:share-permissions />
|
||||
<nc:share-attributes />
|
||||
<oc:tags />
|
||||
<oc:favorite />
|
||||
<oc:comments-unread />
|
||||
|
|
|
|||
|
|
@ -80,4 +80,5 @@ return array(
|
|||
'OCA\\Files_Sharing\\SharedMount' => $baseDir . '/../lib/SharedMount.php',
|
||||
'OCA\\Files_Sharing\\SharedStorage' => $baseDir . '/../lib/SharedStorage.php',
|
||||
'OCA\\Files_Sharing\\Updater' => $baseDir . '/../lib/Updater.php',
|
||||
'OCA\\Files_Sharing\\ViewOnly' => $baseDir . '/../lib/ViewOnly.php',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ class ComposerStaticInitFiles_Sharing
|
|||
'OCA\\Files_Sharing\\SharedMount' => __DIR__ . '/..' . '/../lib/SharedMount.php',
|
||||
'OCA\\Files_Sharing\\SharedStorage' => __DIR__ . '/..' . '/../lib/SharedStorage.php',
|
||||
'OCA\\Files_Sharing\\Updater' => __DIR__ . '/..' . '/../lib/Updater.php',
|
||||
'OCA\\Files_Sharing\\ViewOnly' => __DIR__ . '/..' . '/../lib/ViewOnly.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
|
|
|
|||
|
|
@ -50,16 +50,22 @@ use OCA\Files_Sharing\Notification\Listener;
|
|||
use OCA\Files_Sharing\Notification\Notifier;
|
||||
use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
||||
use OCA\Files\Event\LoadSidebar;
|
||||
use OCP\Files\Event\BeforeDirectGetEvent;
|
||||
use OCA\Files_Sharing\ShareBackend\File;
|
||||
use OCA\Files_Sharing\ShareBackend\Folder;
|
||||
use OCA\Files_Sharing\ViewOnly;
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as ResourcesLoadAdditionalScriptsEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\EventDispatcher\GenericEvent;
|
||||
use OCP\Federation\ICloudIdManager;
|
||||
use OCP\Files\Config\IMountProviderCollection;
|
||||
use OCP\Files\Events\BeforeDirectFileDownloadEvent;
|
||||
use OCP\Files\Events\BeforeZipCreatedEvent;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Group\Events\UserAddedEvent;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroup;
|
||||
|
|
@ -71,7 +77,7 @@ use OCP\User\Events\UserChangedEvent;
|
|||
use OCP\Util;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent as OldGenericEvent;
|
||||
|
||||
class Application extends App implements IBootstrap {
|
||||
public const APP_ID = 'files_sharing';
|
||||
|
|
@ -107,6 +113,7 @@ class Application extends App implements IBootstrap {
|
|||
public function boot(IBootContext $context): void {
|
||||
$context->injectFn([$this, 'registerMountProviders']);
|
||||
$context->injectFn([$this, 'registerEventsScripts']);
|
||||
$context->injectFn([$this, 'registerDownloadEvents']);
|
||||
$context->injectFn([$this, 'setupSharingMenus']);
|
||||
|
||||
Helper::registerHooks();
|
||||
|
|
@ -121,12 +128,12 @@ class Application extends App implements IBootstrap {
|
|||
}
|
||||
|
||||
|
||||
public function registerMountProviders(IMountProviderCollection $mountProviderCollection, MountProvider $mountProvider, ExternalMountProvider $externalMountProvider) {
|
||||
public function registerMountProviders(IMountProviderCollection $mountProviderCollection, MountProvider $mountProvider, ExternalMountProvider $externalMountProvider): void {
|
||||
$mountProviderCollection->registerProvider($mountProvider);
|
||||
$mountProviderCollection->registerProvider($externalMountProvider);
|
||||
}
|
||||
|
||||
public function registerEventsScripts(IEventDispatcher $dispatcher, EventDispatcherInterface $oldDispatcher) {
|
||||
public function registerEventsScripts(IEventDispatcher $dispatcher, EventDispatcherInterface $oldDispatcher): void {
|
||||
// sidebar and files scripts
|
||||
$dispatcher->addServiceListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
|
||||
$dispatcher->addServiceListener(BeforeTemplateRenderedEvent::class, LegacyBeforeTemplateRenderedListener::class);
|
||||
|
|
@ -139,19 +146,73 @@ class Application extends App implements IBootstrap {
|
|||
});
|
||||
|
||||
// notifications api to accept incoming user shares
|
||||
$oldDispatcher->addListener('OCP\Share::postShare', function (GenericEvent $event) {
|
||||
$oldDispatcher->addListener('OCP\Share::postShare', function (OldGenericEvent $event) {
|
||||
/** @var Listener $listener */
|
||||
$listener = $this->getContainer()->query(Listener::class);
|
||||
$listener->shareNotification($event);
|
||||
});
|
||||
$oldDispatcher->addListener(IGroup::class . '::postAddUser', function (GenericEvent $event) {
|
||||
$oldDispatcher->addListener(IGroup::class . '::postAddUser', function (OldGenericEvent $event) {
|
||||
/** @var Listener $listener */
|
||||
$listener = $this->getContainer()->query(Listener::class);
|
||||
$listener->userAddedToGroup($event);
|
||||
});
|
||||
}
|
||||
|
||||
public function setupSharingMenus(IManager $shareManager, IFactory $l10nFactory, IUserSession $userSession) {
|
||||
public function registerDownloadEvents(
|
||||
IEventDispatcher $dispatcher,
|
||||
IUserSession $userSession,
|
||||
IRootFolder $rootFolder
|
||||
): void {
|
||||
|
||||
$dispatcher->addListener(
|
||||
BeforeDirectFileDownloadEvent::class,
|
||||
function (BeforeDirectFileDownloadEvent $event) use ($userSession, $rootFolder): void {
|
||||
$pathsToCheck = [$event->getPath()];
|
||||
// Check only for user/group shares. Don't restrict e.g. share links
|
||||
$user = $userSession->getUser();
|
||||
if ($user) {
|
||||
$viewOnlyHandler = new ViewOnly(
|
||||
$rootFolder->getUserFolder($user->getUID())
|
||||
);
|
||||
if (!$viewOnlyHandler->check($pathsToCheck)) {
|
||||
$event->setSuccessful(false);
|
||||
$event->setErrorMessage('Access to this resource or one of its sub-items has been denied.');
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$dispatcher->addListener(
|
||||
BeforeZipCreatedEvent::class,
|
||||
function (BeforeZipCreatedEvent $event) use ($userSession, $rootFolder): void {
|
||||
$dir = $event->getDirectory();
|
||||
$files = $event->getFiles();
|
||||
|
||||
$pathsToCheck = [];
|
||||
foreach ($files as $file) {
|
||||
$pathsToCheck[] = $dir . '/' . $file;
|
||||
}
|
||||
|
||||
// Check only for user/group shares. Don't restrict e.g. share links
|
||||
$user = $userSession->getUser();
|
||||
if ($user) {
|
||||
$viewOnlyHandler = new ViewOnly(
|
||||
$rootFolder->getUserFolder($user->getUID())
|
||||
);
|
||||
if (!$viewOnlyHandler->check($pathsToCheck)) {
|
||||
$event->setErrorMessage('Access to this resource or one of its sub-items has been denied.');
|
||||
$event->setSuccessful(false);
|
||||
} else {
|
||||
$event->setSuccessful(true);
|
||||
}
|
||||
} else {
|
||||
$event->setSuccessful(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function setupSharingMenus(IManager $shareManager, IFactory $l10nFactory, IUserSession $userSession): void {
|
||||
if (!$shareManager->shareApiEnabled() || !class_exists('\OCA\Files\App')) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ class PublicPreviewController extends PublicShareController {
|
|||
* @param $token
|
||||
* @return DataResponse|FileDisplayResponse
|
||||
*/
|
||||
public function directLink($token) {
|
||||
public function directLink(string $token) {
|
||||
// No token no image
|
||||
if ($token === '') {
|
||||
return new DataResponse([], Http::STATUS_BAD_REQUEST);
|
||||
|
|
|
|||
|
|
@ -45,8 +45,10 @@ declare(strict_types=1);
|
|||
namespace OCA\Files_Sharing\Controller;
|
||||
|
||||
use OC\Files\FileInfo;
|
||||
use OC\Files\Storage\Wrapper\Wrapper;
|
||||
use OCA\Files_Sharing\Exceptions\SharingRightsException;
|
||||
use OCA\Files_Sharing\External\Storage;
|
||||
use OCA\Files_Sharing\SharedStorage;
|
||||
use OCA\Files\Helper;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
|
|
@ -324,6 +326,11 @@ class ShareAPIController extends OCSController {
|
|||
$result['mail_send'] = $share->getMailSend() ? 1 : 0;
|
||||
$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
|
||||
|
||||
$result['attributes'] = null;
|
||||
if ($attributes = $share->getAttributes()) {
|
||||
$result['attributes'] = \json_encode($attributes->toArray());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
|
@ -436,6 +443,7 @@ class ShareAPIController extends OCSController {
|
|||
* @param string $sendPasswordByTalk
|
||||
* @param string $expireDate
|
||||
* @param string $label
|
||||
* @param string $attributes
|
||||
*
|
||||
* @return DataResponse
|
||||
* @throws NotFoundException
|
||||
|
|
@ -456,7 +464,8 @@ class ShareAPIController extends OCSController {
|
|||
string $sendPasswordByTalk = null,
|
||||
string $expireDate = '',
|
||||
string $note = '',
|
||||
string $label = ''
|
||||
string $label = '',
|
||||
string $attributes = null
|
||||
): DataResponse {
|
||||
$share = $this->shareManager->newShare();
|
||||
|
||||
|
|
@ -516,6 +525,8 @@ class ShareAPIController extends OCSController {
|
|||
$permissions &= ~($permissions & ~$node->getPermissions());
|
||||
}
|
||||
|
||||
$this->checkInheritedAttributes($share);
|
||||
|
||||
if ($shareType === IShare::TYPE_USER) {
|
||||
// Valid user is required to share
|
||||
if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
|
||||
|
|
@ -674,6 +685,10 @@ class ShareAPIController extends OCSController {
|
|||
$share->setNote($note);
|
||||
}
|
||||
|
||||
if ($attributes !== null) {
|
||||
$share = $this->setShareAttributes($share, $attributes);
|
||||
}
|
||||
|
||||
try {
|
||||
$share = $this->shareManager->createShare($share);
|
||||
} catch (GenericShareException $e) {
|
||||
|
|
@ -1035,6 +1050,7 @@ class ShareAPIController extends OCSController {
|
|||
* @param string $note
|
||||
* @param string $label
|
||||
* @param string $hideDownload
|
||||
* @param string $attributes
|
||||
* @return DataResponse
|
||||
* @throws LockedException
|
||||
* @throws NotFoundException
|
||||
|
|
@ -1051,7 +1067,8 @@ class ShareAPIController extends OCSController {
|
|||
string $expireDate = null,
|
||||
string $note = null,
|
||||
string $label = null,
|
||||
string $hideDownload = null
|
||||
string $hideDownload = null,
|
||||
string $attributes = null
|
||||
): DataResponse {
|
||||
try {
|
||||
$share = $this->getShareById($id);
|
||||
|
|
@ -1077,7 +1094,8 @@ class ShareAPIController extends OCSController {
|
|||
$expireDate === null &&
|
||||
$note === null &&
|
||||
$label === null &&
|
||||
$hideDownload === null
|
||||
$hideDownload === null &&
|
||||
$attributes === null
|
||||
) {
|
||||
throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
|
||||
}
|
||||
|
|
@ -1086,6 +1104,25 @@ class ShareAPIController extends OCSController {
|
|||
$share->setNote($note);
|
||||
}
|
||||
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
|
||||
|
||||
// get the node with the point of view of the current user
|
||||
$nodes = $userFolder->getById($share->getNode()->getId());
|
||||
if (count($nodes) > 0) {
|
||||
$node = $nodes[0];
|
||||
$storage = $node->getStorage();
|
||||
if ($storage && $storage->instanceOfStorage(SharedStorage::class)) {
|
||||
/** @var \OCA\Files_Sharing\SharedStorage $storage */
|
||||
$inheritedAttributes = $storage->getShare()->getAttributes();
|
||||
if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) {
|
||||
if ($hideDownload === 'false') {
|
||||
throw new OCSBadRequestException($this->l->t('Cannot increase permissions'));
|
||||
}
|
||||
$share->setHideDownload(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* expirationdate, password and publicUpload only make sense for link shares
|
||||
*/
|
||||
|
|
@ -1216,6 +1253,10 @@ class ShareAPIController extends OCSController {
|
|||
}
|
||||
}
|
||||
|
||||
if ($attributes !== null) {
|
||||
$share = $this->setShareAttributes($share, $attributes);
|
||||
}
|
||||
|
||||
try {
|
||||
$share = $this->shareManager->updateShare($share);
|
||||
} catch (GenericShareException $e) {
|
||||
|
|
@ -1832,4 +1873,51 @@ class ShareAPIController extends OCSController {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IShare $share
|
||||
* @param string|null $attributesString
|
||||
* @return IShare modified share
|
||||
*/
|
||||
private function setShareAttributes(IShare $share, ?string $attributesString) {
|
||||
$newShareAttributes = null;
|
||||
if ($attributesString !== null) {
|
||||
$newShareAttributes = $this->shareManager->newShare()->newAttributes();
|
||||
$formattedShareAttributes = \json_decode($attributesString, true);
|
||||
if (is_array($formattedShareAttributes)) {
|
||||
foreach ($formattedShareAttributes as $formattedAttr) {
|
||||
$newShareAttributes->setAttribute(
|
||||
$formattedAttr['scope'],
|
||||
$formattedAttr['key'],
|
||||
is_string($formattedAttr['enabled']) ? (bool) \json_decode($formattedAttr['enabled']) : $formattedAttr['enabled']
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new OCSBadRequestException('Invalid share attributes provided: \"' . $attributesString . '\"');
|
||||
}
|
||||
}
|
||||
$share->setAttributes($newShareAttributes);
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
||||
private function checkInheritedAttributes(IShare $share): void {
|
||||
if ($share->getNode()->getStorage()->instanceOfStorage(SharedStorage::class)) {
|
||||
$storage = $share->getNode()->getStorage();
|
||||
if ($storage instanceof Wrapper) {
|
||||
$storage = $storage->getInstanceOfStorage(SharedStorage::class);
|
||||
if ($storage === null) {
|
||||
throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
|
||||
}
|
||||
/** @var \OCA\Files_Sharing\SharedStorage $storage */
|
||||
$inheritedAttributes = $storage->getShare()->getAttributes();
|
||||
if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) {
|
||||
$share->setHideDownload(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ use OCP\ICacheFactory;
|
|||
use OCP\IConfig;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUser;
|
||||
use OCP\Share\IAttributes;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
|
|
@ -229,14 +230,32 @@ class MountProvider implements IMountProvider {
|
|||
->setTarget($shares[0]->getTarget());
|
||||
|
||||
// use most permissive permissions
|
||||
$permissions = 0;
|
||||
// this covers the case where there are multiple shares for the same
|
||||
// file e.g. from different groups and different permissions
|
||||
$superPermissions = 0;
|
||||
$superAttributes = $this->shareManager->newShare()->newAttributes();
|
||||
$status = IShare::STATUS_PENDING;
|
||||
foreach ($shares as $share) {
|
||||
$permissions |= $share->getPermissions();
|
||||
$superPermissions |= $share->getPermissions();
|
||||
$status = max($status, $share->getStatus());
|
||||
// update permissions
|
||||
$superPermissions |= $share->getPermissions();
|
||||
|
||||
// update share permission attributes
|
||||
$attributes = $share->getAttributes();
|
||||
if ($attributes !== null) {
|
||||
foreach ($attributes->toArray() as $attribute) {
|
||||
if ($superAttributes->getAttribute($attribute['scope'], $attribute['key']) === true) {
|
||||
// if super share attribute is already enabled, it is most permissive
|
||||
continue;
|
||||
}
|
||||
// update supershare attributes with subshare attribute
|
||||
$superAttributes->setAttribute($attribute['scope'], $attribute['key'], $attribute['enabled']);
|
||||
}
|
||||
}
|
||||
|
||||
// adjust target, for database consistency if needed
|
||||
if ($share->getTarget() !== $superShare->getTarget()) {
|
||||
// adjust target, for database consistency
|
||||
$share->setTarget($superShare->getTarget());
|
||||
try {
|
||||
$this->shareManager->moveShare($share, $user->getUID());
|
||||
|
|
@ -261,8 +280,9 @@ class MountProvider implements IMountProvider {
|
|||
}
|
||||
}
|
||||
|
||||
$superShare->setPermissions($permissions)
|
||||
->setStatus($status);
|
||||
$superShare->setPermissions($superPermissions);
|
||||
$superShare->setStatus($status);
|
||||
$superShare->setAttributes($superAttributes);
|
||||
|
||||
$result[] = [$superShare, $shares];
|
||||
}
|
||||
|
|
|
|||
121
apps/files_sharing/lib/ViewOnly.php
Normal file
121
apps/files_sharing/lib/ViewOnly.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Piotr Mrowczynski piotr@owncloud.com
|
||||
*
|
||||
* @copyright Copyright (c) 2019, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing;
|
||||
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\Node;
|
||||
use OCP\Files\NotFoundException;
|
||||
|
||||
/**
|
||||
* Handles restricting for download of files
|
||||
*/
|
||||
class ViewOnly {
|
||||
|
||||
/** @var Folder */
|
||||
private $userFolder;
|
||||
|
||||
public function __construct(Folder $userFolder) {
|
||||
$this->userFolder = $userFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $pathsToCheck
|
||||
* @return bool
|
||||
*/
|
||||
public function check(array $pathsToCheck): bool {
|
||||
// If any of elements cannot be downloaded, prevent whole download
|
||||
foreach ($pathsToCheck as $file) {
|
||||
try {
|
||||
$info = $this->userFolder->get($file);
|
||||
if ($info instanceof File) {
|
||||
// access to filecache is expensive in the loop
|
||||
if (!$this->checkFileInfo($info)) {
|
||||
return false;
|
||||
}
|
||||
} elseif ($info instanceof Folder) {
|
||||
// get directory content is rather cheap query
|
||||
if (!$this->dirRecursiveCheck($info)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Folder $dirInfo
|
||||
* @return bool
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
private function dirRecursiveCheck(Folder $dirInfo): bool {
|
||||
if (!$this->checkFileInfo($dirInfo)) {
|
||||
return false;
|
||||
}
|
||||
// If any of elements cannot be downloaded, prevent whole download
|
||||
$files = $dirInfo->getDirectoryListing();
|
||||
foreach ($files as $file) {
|
||||
if ($file instanceof File) {
|
||||
if (!$this->checkFileInfo($file)) {
|
||||
return false;
|
||||
}
|
||||
} elseif ($file instanceof Folder) {
|
||||
return $this->dirRecursiveCheck($file);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $fileInfo
|
||||
* @return bool
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
private function checkFileInfo(Node $fileInfo): bool {
|
||||
// Restrict view-only to nodes which are shared
|
||||
$storage = $fileInfo->getStorage();
|
||||
if (!$storage->instanceOfStorage(SharedStorage::class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extract extra permissions
|
||||
/** @var \OCA\Files_Sharing\SharedStorage $storage */
|
||||
$share = $storage->getShare();
|
||||
|
||||
$canDownload = true;
|
||||
|
||||
// Check if read-only and on whether permission can download is both set and disabled.
|
||||
$attributes = $share->getAttributes();
|
||||
if ($attributes !== null) {
|
||||
$canDownload = $attributes->getAttribute('permissions', 'download');
|
||||
}
|
||||
|
||||
if ($canDownload !== null && !$canDownload) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -78,6 +78,13 @@
|
|||
{{ t('files_sharing', 'Allow resharing') }}
|
||||
</ActionCheckbox>
|
||||
|
||||
<ActionCheckbox ref="canDownload"
|
||||
:checked.sync="canDownload"
|
||||
v-if="isSetDownloadButtonVisible"
|
||||
:disabled="saving || !canSetDownload">
|
||||
{{ allowDownloadText }}
|
||||
</ActionCheckbox>
|
||||
|
||||
<!-- expiration date -->
|
||||
<ActionCheckbox :checked.sync="hasExpirationDate"
|
||||
:disabled="config.isDefaultInternalExpireDateEnforced || saving"
|
||||
|
|
@ -271,6 +278,18 @@ export default {
|
|||
return (this.fileInfo.sharePermissions & OC.PERMISSION_SHARE) || this.canReshare
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharer set whether the sharee can download the file ?
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
canSetDownload() {
|
||||
// If the owner revoked the permission after the resharer granted it
|
||||
// the share still has the permission, and the resharer is still
|
||||
// allowed to revoke it too (but not to grant it again).
|
||||
return (this.fileInfo.canDownload() || this.canDownload)
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharee edit the shared file ?
|
||||
*/
|
||||
|
|
@ -319,6 +338,18 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharee download files or only view them ?
|
||||
*/
|
||||
canDownload: {
|
||||
get() {
|
||||
return this.share.hasDownloadPermission
|
||||
},
|
||||
set(checked) {
|
||||
this.updatePermissions({ isDownloadChecked: checked })
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Is this share readable
|
||||
* Needed for some federated shares that might have been added from file drop links
|
||||
|
|
@ -377,10 +408,46 @@ export default {
|
|||
return (typeof this.share.status === 'object' && !Array.isArray(this.share.status))
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
allowDownloadText() {
|
||||
if (this.isFolder) {
|
||||
return t('files_sharing', 'Allow download of office files')
|
||||
} else {
|
||||
return t('files_sharing', 'Allow download')
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isSetDownloadButtonVisible() {
|
||||
const allowedMimetypes = [
|
||||
// Office documents
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.oasis.opendocument.text',
|
||||
'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'application/vnd.oasis.opendocument.presentation',
|
||||
]
|
||||
|
||||
return this.isFolder || allowedMimetypes.includes(this.fileInfo.mimetype)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
updatePermissions({ isEditChecked = this.canEdit, isCreateChecked = this.canCreate, isDeleteChecked = this.canDelete, isReshareChecked = this.canReshare } = {}) {
|
||||
updatePermissions({
|
||||
isEditChecked = this.canEdit,
|
||||
isCreateChecked = this.canCreate,
|
||||
isDeleteChecked = this.canDelete,
|
||||
isReshareChecked = this.canReshare,
|
||||
isDownloadChecked = this.canDownload,
|
||||
} = {}) {
|
||||
// calc permissions if checked
|
||||
const permissions = 0
|
||||
| (this.hasRead ? this.permissionsRead : 0)
|
||||
|
|
@ -390,7 +457,10 @@ export default {
|
|||
| (isReshareChecked ? this.permissionsShare : 0)
|
||||
|
||||
this.share.permissions = permissions
|
||||
this.queueUpdate('permissions')
|
||||
if (this.share.hasDownloadPermission !== isDownloadChecked) {
|
||||
this.share.hasDownloadPermission = isDownloadChecked
|
||||
}
|
||||
this.queueUpdate('permissions', 'attributes')
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@
|
|||
<ActionSeparator />
|
||||
|
||||
<ActionCheckbox :checked.sync="share.hideDownload"
|
||||
:disabled="saving"
|
||||
:disabled="saving || canChangeHideDownload"
|
||||
@change="queueUpdate('hideDownload')">
|
||||
{{ t('files_sharing', 'Hide download') }}
|
||||
</ActionCheckbox>
|
||||
|
|
@ -607,6 +607,12 @@ export default {
|
|||
isPasswordPolicyEnabled() {
|
||||
return typeof this.config.passwordPolicy === 'object'
|
||||
},
|
||||
|
||||
canChangeHideDownload() {
|
||||
const hasDisabledDownload = (shareAttribute) => shareAttribute.key === 'download' && shareAttribute.scope === 'permissions' && shareAttribute.enabled === false
|
||||
|
||||
return this.fileInfo.shareAttributes.some(hasDisabledDownload)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -697,6 +703,7 @@ export default {
|
|||
shareType: ShareTypes.SHARE_TYPE_LINK,
|
||||
password: share.password,
|
||||
expireDate: share.expireDate,
|
||||
attributes: JSON.stringify(this.fileInfo.shareAttributes),
|
||||
// we do not allow setting the publicUpload
|
||||
// before the share creation.
|
||||
// Todo: We also need to fix the createShare method in
|
||||
|
|
@ -867,7 +874,6 @@ export default {
|
|||
this.$emit('remove:share', this.share)
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -478,6 +478,7 @@ export default {
|
|||
shareWith: value.shareWith,
|
||||
password,
|
||||
permissions: this.fileInfo.sharePermissions & OC.getCapabilities().files_sharing.default_permissions,
|
||||
attributes: JSON.stringify(this.fileInfo.shareAttributes),
|
||||
})
|
||||
|
||||
// If we had a password, we need to show it to the user as it was generated
|
||||
|
|
|
|||
|
|
@ -47,12 +47,13 @@ export default {
|
|||
* @param {boolean} [data.sendPasswordByTalk=false] send the password via a talk conversation
|
||||
* @param {string} [data.expireDate=''] expire the shareautomatically after
|
||||
* @param {string} [data.label=''] custom label
|
||||
* @param {string} [data.attributes=null] Share attributes encoded as json
|
||||
* @return {Share} the new share
|
||||
* @throws {Error}
|
||||
*/
|
||||
async createShare({ path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label }) {
|
||||
async createShare({ path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, attributes }) {
|
||||
try {
|
||||
const request = await axios.post(shareUrl, { path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label })
|
||||
const request = await axios.post(shareUrl, { path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, attributes })
|
||||
if (!request?.data?.ocs) {
|
||||
throw request
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,7 +229,13 @@ export default {
|
|||
const properties = {}
|
||||
// force value to string because that is what our
|
||||
// share api controller accepts
|
||||
propertyNames.map(p => (properties[p] = this.share[p].toString()))
|
||||
propertyNames.forEach(name => {
|
||||
if ((typeof this.share[name]) === 'object') {
|
||||
properties[name] = JSON.stringify(this.share[name])
|
||||
} else {
|
||||
properties[name] = this.share[name].toString()
|
||||
}
|
||||
})
|
||||
|
||||
this.updateQueue.add(async () => {
|
||||
this.saving = true
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ export default class Share {
|
|||
ocsData.hide_download = !!ocsData.hide_download
|
||||
ocsData.mail_send = !!ocsData.mail_send
|
||||
|
||||
if (ocsData.attributes) {
|
||||
try {
|
||||
ocsData.attributes = JSON.parse(ocsData.attributes)
|
||||
} catch (e) {
|
||||
console.warn('Could not parse share attributes returned by server: "' + ocsData.attributes + '"')
|
||||
}
|
||||
}
|
||||
ocsData.attributes = ocsData.attributes ?? []
|
||||
|
||||
// store state
|
||||
this._share = ocsData
|
||||
}
|
||||
|
|
@ -96,6 +105,17 @@ export default class Share {
|
|||
return this._share.permissions
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the share attributes
|
||||
*
|
||||
* @return {Array}
|
||||
* @readonly
|
||||
* @memberof Share
|
||||
*/
|
||||
get attributes() {
|
||||
return this._share.attributes
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the share permissions
|
||||
* See OC.PERMISSION_* variables
|
||||
|
|
@ -527,6 +547,47 @@ export default class Share {
|
|||
return !!((this.permissions & OC.PERMISSION_SHARE))
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this share have download permissions
|
||||
*
|
||||
* @return {boolean}
|
||||
* @readonly
|
||||
* @memberof Share
|
||||
*/
|
||||
get hasDownloadPermission() {
|
||||
for (const i in this._share.attributes) {
|
||||
const attr = this._share.attributes[i]
|
||||
if (attr.scope === 'permissions' && attr.key === 'download') {
|
||||
return attr.enabled
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
set hasDownloadPermission(enabled) {
|
||||
this.setAttribute('permissions', 'download', !!enabled)
|
||||
}
|
||||
|
||||
setAttribute(scope, key, enabled) {
|
||||
const attrUpdate = {
|
||||
scope,
|
||||
key,
|
||||
enabled,
|
||||
}
|
||||
|
||||
// try and replace existing
|
||||
for (const i in this._share.attributes) {
|
||||
const attr = this._share.attributes[i]
|
||||
if (attr.scope === attrUpdate.scope && attr.key === attrUpdate.key) {
|
||||
this._share.attributes[i] = attrUpdate
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this._share.attributes.push(attrUpdate)
|
||||
}
|
||||
|
||||
// PERMISSIONS Shortcuts for the CURRENT USER
|
||||
// ! the permissions above are the share settings,
|
||||
// ! meaning the permissions for the recipient
|
||||
|
|
|
|||
|
|
@ -92,7 +92,11 @@ import { getCapabilities } from '@nextcloud/capabilities'
|
|||
delete fileActions.actions.all.Details
|
||||
delete fileActions.actions.all.Goto
|
||||
}
|
||||
if (_.isFunction(fileData.canDownload) && !fileData.canDownload()) {
|
||||
delete fileActions.actions.all.Download
|
||||
}
|
||||
tr.attr('data-share-permissions', sharePermissions)
|
||||
tr.attr('data-share-attributes', JSON.stringify(fileData.shareAttributes))
|
||||
if (fileData.shareOwner) {
|
||||
tr.attr('data-share-owner', fileData.shareOwner)
|
||||
tr.attr('data-share-owner-id', fileData.shareOwnerId)
|
||||
|
|
@ -113,6 +117,7 @@ import { getCapabilities } from '@nextcloud/capabilities'
|
|||
var oldElementToFile = fileList.elementToFile
|
||||
fileList.elementToFile = function($el) {
|
||||
var fileInfo = oldElementToFile.apply(this, arguments)
|
||||
fileInfo.shareAttributes = JSON.parse($el.attr('data-share-attributes') || '[]')
|
||||
fileInfo.sharePermissions = $el.attr('data-share-permissions') || undefined
|
||||
fileInfo.shareOwner = $el.attr('data-share-owner') || undefined
|
||||
fileInfo.shareOwnerId = $el.attr('data-share-owner-id') || undefined
|
||||
|
|
|
|||
|
|
@ -948,8 +948,15 @@ class ApiTest extends TestCase {
|
|||
->setSharedBy(self::TEST_FILES_SHARING_API_USER1)
|
||||
->setSharedWith(self::TEST_FILES_SHARING_API_USER2)
|
||||
->setShareType(IShare::TYPE_USER)
|
||||
->setPermissions(19);
|
||||
->setPermissions(19)
|
||||
->setAttributes($this->shareManager->newShare()->newAttributes());
|
||||
|
||||
$this->assertNotNull($share1->getAttributes());
|
||||
$share1 = $this->shareManager->createShare($share1);
|
||||
$this->assertNull($share1->getAttributes());
|
||||
$this->assertEquals(19, $share1->getPermissions());
|
||||
// attributes get cleared when empty
|
||||
$this->assertNull($share1->getAttributes());
|
||||
|
||||
$share2 = $this->shareManager->newShare();
|
||||
$share2->setNode($node1)
|
||||
|
|
@ -957,14 +964,19 @@ class ApiTest extends TestCase {
|
|||
->setShareType(IShare::TYPE_LINK)
|
||||
->setPermissions(1);
|
||||
$share2 = $this->shareManager->createShare($share2);
|
||||
$this->assertEquals(1, $share2->getPermissions());
|
||||
|
||||
// update permissions
|
||||
$ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1);
|
||||
$ocs->updateShare($share1->getId(), 1);
|
||||
$ocs->updateShare(
|
||||
$share1->getId(), 1, null, null, null, null, null, null, null,
|
||||
'[{"scope": "app1", "key": "attr1", "enabled": true}]'
|
||||
);
|
||||
$ocs->cleanup();
|
||||
|
||||
$share1 = $this->shareManager->getShareById('ocinternal:' . $share1->getId());
|
||||
$this->assertEquals(1, $share1->getPermissions());
|
||||
$this->assertEquals(true, $share1->getAttributes()->getAttribute('app1', 'attr1'));
|
||||
|
||||
// update password for link share
|
||||
$this->assertNull($share2->getPassword());
|
||||
|
|
|
|||
236
apps/files_sharing/tests/ApplicationTest.php
Normal file
236
apps/files_sharing/tests/ApplicationTest.php
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright 2022, Vincent Petry <vincent@nextcloud.com>
|
||||
*
|
||||
* @author Vincent Petry <vincent@nextcloud.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Files_Sharing\Tests;
|
||||
|
||||
use OCP\Files\Events\BeforeDirectFileDownloadEvent;
|
||||
use OCP\Files\Events\BeforeZipCreatedEvent;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use OC\Share20\LegacyHooks;
|
||||
use OC\Share20\Manager;
|
||||
use OC\EventDispatcher\EventDispatcher;
|
||||
use OCA\Files_Sharing\AppInfo\Application;
|
||||
use OCA\Files_Sharing\SharedStorage;
|
||||
use OCP\Constants;
|
||||
use OCP\EventDispatcher\GenericEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Cache\ICacheEntry;
|
||||
use OCP\Files\Event\BeforeDirectGetEvent;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Share\IAttributes;
|
||||
use OCP\Share\IShare;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyDispatcher;
|
||||
use Test\TestCase;
|
||||
|
||||
class ApplicationTest extends TestCase {
|
||||
private Application $application;
|
||||
private IEventDispatcher $eventDispatcher;
|
||||
|
||||
/** @var IUserSession */
|
||||
private $userSession;
|
||||
|
||||
/** @var IRootFolder */
|
||||
private $rootFolder;
|
||||
|
||||
/** @var Manager */ private $manager;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->application = new Application([]);
|
||||
|
||||
$symfonyDispatcher = new SymfonyDispatcher();
|
||||
$this->eventDispatcher = new EventDispatcher(
|
||||
$symfonyDispatcher,
|
||||
$this->createMock(IServerContainer::class),
|
||||
$this->createMock(LoggerInterface::class)
|
||||
);
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
$this->rootFolder = $this->createMock(IRootFolder::class);
|
||||
|
||||
$this->application->registerDownloadEvents(
|
||||
$this->eventDispatcher,
|
||||
$this->userSession,
|
||||
$this->rootFolder
|
||||
);
|
||||
}
|
||||
|
||||
public function providesDataForCanGet(): array {
|
||||
// normal file (sender) - can download directly
|
||||
$senderFileStorage = $this->createMock(IStorage::class);
|
||||
$senderFileStorage->method('instanceOfStorage')->with(SharedStorage::class)->willReturn(false);
|
||||
$senderFile = $this->createMock(File::class);
|
||||
$senderFile->method('getStorage')->willReturn($senderFileStorage);
|
||||
$senderUserFolder = $this->createMock(Folder::class);
|
||||
$senderUserFolder->method('get')->willReturn($senderFile);
|
||||
|
||||
$result[] = [ '/bar.txt', $senderUserFolder, true ];
|
||||
|
||||
// shared file (receiver) with attribute secure-view-enabled set false -
|
||||
// can download directly
|
||||
$receiverFileShareAttributes = $this->createMock(IAttributes::class);
|
||||
$receiverFileShareAttributes->method('getAttribute')->with('permissions', 'download')->willReturn(true);
|
||||
$receiverFileShare = $this->createMock(IShare::class);
|
||||
$receiverFileShare->method('getAttributes')->willReturn($receiverFileShareAttributes);
|
||||
$receiverFileStorage = $this->createMock(SharedStorage::class);
|
||||
$receiverFileStorage->method('instanceOfStorage')->with(SharedStorage::class)->willReturn(true);
|
||||
$receiverFileStorage->method('getShare')->willReturn($receiverFileShare);
|
||||
$receiverFile = $this->createMock(File::class);
|
||||
$receiverFile->method('getStorage')->willReturn($receiverFileStorage);
|
||||
$receiverUserFolder = $this->createMock(Folder::class);
|
||||
$receiverUserFolder->method('get')->willReturn($receiverFile);
|
||||
|
||||
$result[] = [ '/share-bar.txt', $receiverUserFolder, true ];
|
||||
|
||||
// shared file (receiver) with attribute secure-view-enabled set true -
|
||||
// cannot download directly
|
||||
$secureReceiverFileShareAttributes = $this->createMock(IAttributes::class);
|
||||
$secureReceiverFileShareAttributes->method('getAttribute')->with('permissions', 'download')->willReturn(false);
|
||||
$secureReceiverFileShare = $this->createMock(IShare::class);
|
||||
$secureReceiverFileShare->method('getAttributes')->willReturn($secureReceiverFileShareAttributes);
|
||||
$secureReceiverFileStorage = $this->createMock(SharedStorage::class);
|
||||
$secureReceiverFileStorage->method('instanceOfStorage')->with(SharedStorage::class)->willReturn(true);
|
||||
$secureReceiverFileStorage->method('getShare')->willReturn($secureReceiverFileShare);
|
||||
$secureReceiverFile = $this->createMock(File::class);
|
||||
$secureReceiverFile->method('getStorage')->willReturn($secureReceiverFileStorage);
|
||||
$secureReceiverUserFolder = $this->createMock(Folder::class);
|
||||
$secureReceiverUserFolder->method('get')->willReturn($secureReceiverFile);
|
||||
|
||||
$result[] = [ '/secure-share-bar.txt', $secureReceiverUserFolder, false ];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providesDataForCanGet
|
||||
*/
|
||||
public function testCheckDirectCanBeDownloaded(string $path, Folder $userFolder, bool $run): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('test');
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
$this->userSession->method('isLoggedIn')->willReturn(true);
|
||||
$this->rootFolder->method('getUserFolder')->willReturn($userFolder);
|
||||
|
||||
// Simulate direct download of file
|
||||
$event = new BeforeDirectFileDownloadEvent($path);
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
|
||||
$this->assertEquals($run, $event->isSuccessful());
|
||||
}
|
||||
|
||||
public function providesDataForCanZip(): array {
|
||||
// Mock: Normal file/folder storage
|
||||
$nonSharedStorage = $this->createMock(IStorage::class);
|
||||
$nonSharedStorage->method('instanceOfStorage')->with(SharedStorage::class)->willReturn(false);
|
||||
|
||||
// Mock: Secure-view file/folder shared storage
|
||||
$secureReceiverFileShareAttributes = $this->createMock(IAttributes::class);
|
||||
$secureReceiverFileShareAttributes->method('getAttribute')->with('permissions', 'download')->willReturn(false);
|
||||
$secureReceiverFileShare = $this->createMock(IShare::class);
|
||||
$secureReceiverFileShare->method('getAttributes')->willReturn($secureReceiverFileShareAttributes);
|
||||
$secureSharedStorage = $this->createMock(SharedStorage::class);
|
||||
$secureSharedStorage->method('instanceOfStorage')->with(SharedStorage::class)->willReturn(true);
|
||||
$secureSharedStorage->method('getShare')->willReturn($secureReceiverFileShare);
|
||||
|
||||
// 1. can download zipped 2 non-shared files inside non-shared folder
|
||||
// 2. can download zipped non-shared folder
|
||||
$sender1File = $this->createMock(File::class);
|
||||
$sender1File->method('getStorage')->willReturn($nonSharedStorage);
|
||||
$sender1Folder = $this->createMock(Folder::class);
|
||||
$sender1Folder->method('getStorage')->willReturn($nonSharedStorage);
|
||||
$sender1Folder->method('getDirectoryListing')->willReturn([$sender1File, $sender1File]);
|
||||
$sender1RootFolder = $this->createMock(Folder::class);
|
||||
$sender1RootFolder->method('getStorage')->willReturn($nonSharedStorage);
|
||||
$sender1RootFolder->method('getDirectoryListing')->willReturn([$sender1Folder]);
|
||||
$sender1UserFolder = $this->createMock(Folder::class);
|
||||
$sender1UserFolder->method('get')->willReturn($sender1RootFolder);
|
||||
|
||||
$return[] = [ '/folder', ['bar1.txt', 'bar2.txt'], $sender1UserFolder, true ];
|
||||
$return[] = [ '/', ['folder'], $sender1UserFolder, true ];
|
||||
|
||||
// 3. cannot download zipped 1 non-shared file and 1 secure-shared inside non-shared folder
|
||||
$receiver1File = $this->createMock(File::class);
|
||||
$receiver1File->method('getStorage')->willReturn($nonSharedStorage);
|
||||
$receiver1SecureFile = $this->createMock(File::class);
|
||||
$receiver1SecureFile->method('getStorage')->willReturn($secureSharedStorage);
|
||||
$receiver1Folder = $this->createMock(Folder::class);
|
||||
$receiver1Folder->method('getStorage')->willReturn($nonSharedStorage);
|
||||
$receiver1Folder->method('getDirectoryListing')->willReturn([$receiver1File, $receiver1SecureFile]);
|
||||
$receiver1RootFolder = $this->createMock(Folder::class);
|
||||
$receiver1RootFolder->method('getStorage')->willReturn($nonSharedStorage);
|
||||
$receiver1RootFolder->method('getDirectoryListing')->willReturn([$receiver1Folder]);
|
||||
$receiver1UserFolder = $this->createMock(Folder::class);
|
||||
$receiver1UserFolder->method('get')->willReturn($receiver1RootFolder);
|
||||
|
||||
$return[] = [ '/folder', ['secured-bar1.txt', 'bar2.txt'], $receiver1UserFolder, false ];
|
||||
|
||||
// 4. cannot download zipped secure-shared folder
|
||||
$receiver2Folder = $this->createMock(Folder::class);
|
||||
$receiver2Folder->method('getStorage')->willReturn($secureSharedStorage);
|
||||
$receiver2RootFolder = $this->createMock(Folder::class);
|
||||
$receiver2RootFolder->method('getStorage')->willReturn($nonSharedStorage);
|
||||
$receiver2RootFolder->method('getDirectoryListing')->willReturn([$receiver2Folder]);
|
||||
$receiver2UserFolder = $this->createMock(Folder::class);
|
||||
$receiver2UserFolder->method('get')->willReturn($receiver2RootFolder);
|
||||
|
||||
$return[] = [ '/', ['secured-folder'], $receiver2UserFolder, false ];
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providesDataForCanZip
|
||||
*/
|
||||
public function testCheckZipCanBeDownloaded(string $dir, array $files, Folder $userFolder, bool $run): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('test');
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
$this->userSession->method('isLoggedIn')->willReturn(true);
|
||||
|
||||
$this->rootFolder->method('getUserFolder')->with('test')->willReturn($userFolder);
|
||||
|
||||
// Simulate zip download of folder folder
|
||||
$event = new BeforeZipCreatedEvent($dir, $files);
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
|
||||
$this->assertEquals($run, $event->isSuccessful());
|
||||
$this->assertEquals($run, $event->getErrorMessage() === null);
|
||||
}
|
||||
|
||||
public function testCheckFileUserNotFound(): void {
|
||||
$this->userSession->method('isLoggedIn')->willReturn(false);
|
||||
|
||||
// Simulate zip download of folder folder
|
||||
$event = new BeforeZipCreatedEvent('/test', ['test.txt']);
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
|
||||
// It should run as this would restrict e.g. share links otherwise
|
||||
$this->assertTrue($event->isSuccessful());
|
||||
$this->assertEquals(null, $event->getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ use OCP\Files\IRootFolder;
|
|||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\Storage;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IConfig;
|
||||
use OCP\IGroup;
|
||||
use OCP\IGroupManager;
|
||||
|
|
@ -58,6 +59,7 @@ use OCP\IUser;
|
|||
use OCP\IUserManager;
|
||||
use OCP\Lock\LockedException;
|
||||
use OCP\Share\Exceptions\GenericShareException;
|
||||
use OCP\Share\IAttributes as IShareAttributes;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
use Test\TestCase;
|
||||
|
|
@ -124,7 +126,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
->willReturn(true);
|
||||
$this->shareManager
|
||||
->expects($this->any())
|
||||
->method('shareProviderExists')->willReturn(true);
|
||||
->method('shareProviderExists')->willReturn(true);
|
||||
$this->groupManager = $this->createMock(IGroupManager::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
|
|
@ -194,6 +196,23 @@ class ShareAPIControllerTest extends TestCase {
|
|||
}
|
||||
|
||||
|
||||
private function mockShareAttributes() {
|
||||
$formattedShareAttributes = [
|
||||
[
|
||||
'scope' => 'permissions',
|
||||
'key' => 'download',
|
||||
'enabled' => true
|
||||
]
|
||||
];
|
||||
|
||||
$shareAttributes = $this->createMock(IShareAttributes::class);
|
||||
$shareAttributes->method('toArray')->willReturn($formattedShareAttributes);
|
||||
$shareAttributes->method('getAttribute')->with('permissions', 'download')->willReturn(true);
|
||||
|
||||
// send both IShare attributes class and expected json string
|
||||
return [$shareAttributes, \json_encode($formattedShareAttributes)];
|
||||
}
|
||||
|
||||
public function testDeleteShareShareNotFound() {
|
||||
$this->expectException(\OCP\AppFramework\OCS\OCSNotFoundException::class);
|
||||
$this->expectExceptionMessage('Wrong share ID, share does not exist');
|
||||
|
|
@ -505,7 +524,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
public function createShare($id, $shareType, $sharedWith, $sharedBy, $shareOwner, $path, $permissions,
|
||||
$shareTime, $expiration, $parent, $target, $mail_send, $note = '', $token = null,
|
||||
$password = null, $label = '') {
|
||||
$password = null, $label = '', $attributes = null) {
|
||||
$share = $this->getMockBuilder(IShare::class)->getMock();
|
||||
$share->method('getId')->willReturn($id);
|
||||
$share->method('getShareType')->willReturn($shareType);
|
||||
|
|
@ -516,6 +535,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$share->method('getPermissions')->willReturn($permissions);
|
||||
$share->method('getNote')->willReturn($note);
|
||||
$share->method('getLabel')->willReturn($label);
|
||||
$share->method('getAttributes')->willReturn($attributes);
|
||||
$time = new \DateTime();
|
||||
$time->setTimestamp($shareTime);
|
||||
$share->method('getShareTime')->willReturn($time);
|
||||
|
|
@ -565,6 +585,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$folder->method('getParent')->willReturn($parentFolder);
|
||||
$folder->method('getMimeType')->willReturn('myFolderMimeType');
|
||||
|
||||
[$shareAttributes, $shareAttributesReturnJson] = $this->mockShareAttributes();
|
||||
|
||||
// File shared with user
|
||||
$share = $this->createShare(
|
||||
100,
|
||||
|
|
@ -579,7 +601,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
6,
|
||||
'target',
|
||||
0,
|
||||
'personal note'
|
||||
'personal note',
|
||||
$shareAttributes,
|
||||
);
|
||||
$expected = [
|
||||
'id' => 100,
|
||||
|
|
@ -597,6 +620,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'token' => null,
|
||||
'expiration' => null,
|
||||
'permissions' => 4,
|
||||
'attributes' => $shareAttributesReturnJson,
|
||||
'stime' => 5,
|
||||
'parent' => null,
|
||||
'storage_id' => 'STORAGE',
|
||||
|
|
@ -613,6 +637,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'status' => [],
|
||||
'attributes' => null,
|
||||
];
|
||||
$data[] = [$share, $expected];
|
||||
|
||||
|
|
@ -630,7 +655,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
6,
|
||||
'target',
|
||||
0,
|
||||
'personal note'
|
||||
'personal note',
|
||||
$shareAttributes,
|
||||
);
|
||||
$expected = [
|
||||
'id' => 101,
|
||||
|
|
@ -647,6 +673,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'token' => null,
|
||||
'expiration' => null,
|
||||
'permissions' => 4,
|
||||
'attributes' => $shareAttributesReturnJson,
|
||||
'stime' => 5,
|
||||
'parent' => null,
|
||||
'storage_id' => 'STORAGE',
|
||||
|
|
@ -662,6 +689,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
];
|
||||
$data[] = [$share, $expected];
|
||||
|
||||
|
|
@ -702,6 +730,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'token' => 'token',
|
||||
'expiration' => '2000-01-02 00:00:00',
|
||||
'permissions' => 4,
|
||||
'attributes' => null,
|
||||
'stime' => 5,
|
||||
'parent' => null,
|
||||
'storage_id' => 'STORAGE',
|
||||
|
|
@ -718,6 +747,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
];
|
||||
$data[] = [$share, $expected];
|
||||
|
||||
|
|
@ -1646,8 +1676,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -1680,8 +1712,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -1732,8 +1766,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -1788,8 +1824,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -1847,8 +1885,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -1901,8 +1941,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -1935,8 +1977,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->willReturnSelf();
|
||||
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);
|
||||
|
|
@ -1956,8 +2000,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->willReturnSelf();
|
||||
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);
|
||||
|
|
@ -1978,8 +2024,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->willReturnSelf();
|
||||
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);
|
||||
|
|
@ -1999,8 +2047,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->willReturnSelf();
|
||||
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);
|
||||
|
|
@ -2035,8 +2085,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->willReturnSelf();
|
||||
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);
|
||||
|
|
@ -2071,8 +2123,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->willReturnSelf();
|
||||
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);
|
||||
|
|
@ -2114,8 +2168,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$path->method('getPath')->willReturn('valid-path');
|
||||
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->willReturnSelf();
|
||||
|
|
@ -2150,8 +2206,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->willReturnSelf();
|
||||
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);
|
||||
|
|
@ -2193,8 +2251,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->willReturnSelf();
|
||||
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);
|
||||
|
|
@ -2241,8 +2301,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -2313,8 +2375,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -2367,8 +2431,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -2451,8 +2517,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$path->method('getPath')->willReturn('valid-path');
|
||||
$userFolder->expects($this->once())
|
||||
|
|
@ -2494,8 +2562,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(File::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(false);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', false],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$userFolder->expects($this->once())
|
||||
->method('get')
|
||||
|
|
@ -2575,8 +2645,10 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$path = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$storage = $this->createMock(Storage::class);
|
||||
$storage->method('instanceOfStorage')
|
||||
->with('OCA\Files_Sharing\External\Storage')
|
||||
->willReturn(true);
|
||||
->willReturnMap([
|
||||
['OCA\Files_Sharing\External\Storage', true],
|
||||
['OCA\Files_Sharing\SharedStorage', false],
|
||||
]);
|
||||
$path->method('getStorage')->willReturn($storage);
|
||||
$path->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ);
|
||||
$userFolder->expects($this->once())
|
||||
|
|
@ -2935,8 +3007,17 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$this->expectExceptionMessage('Invalid date. Format must be YYYY-MM-DD');
|
||||
|
||||
$ocs = $this->mockFormatShare();
|
||||
$userFolder = $this->createMock(Folder::class);
|
||||
$userFolder->method('getById')
|
||||
->with(42)
|
||||
->willReturn([]);
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
->with($this->currentUser)
|
||||
->willReturn($userFolder);
|
||||
|
||||
$folder = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$folder->method('getId')
|
||||
->willReturn(42);
|
||||
|
||||
$share = \OC::$server->getShareManager()->newShare();
|
||||
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
|
||||
|
|
@ -2974,8 +3055,16 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$this->expectExceptionMessage('Public upload disabled by the administrator');
|
||||
|
||||
$ocs = $this->mockFormatShare();
|
||||
$userFolder = $this->createMock(Folder::class);
|
||||
$userFolder->method('getById')
|
||||
->with(42)
|
||||
->willReturn([]);
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
->with($this->currentUser)
|
||||
->willReturn($userFolder);
|
||||
|
||||
$folder = $this->getMockBuilder(Folder::class)->getMock();
|
||||
$folder->method('getId')->willReturn(42);
|
||||
|
||||
$share = \OC::$server->getShareManager()->newShare();
|
||||
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
|
||||
|
|
@ -2997,6 +3086,15 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$ocs = $this->mockFormatShare();
|
||||
|
||||
$file = $this->getMockBuilder(File::class)->getMock();
|
||||
$file->method('getId')
|
||||
->willReturn(42);
|
||||
$userFolder = $this->createMock(Folder::class);
|
||||
$userFolder->method('getById')
|
||||
->with(42)
|
||||
->willReturn([]);
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
->with($this->currentUser)
|
||||
->willReturn($userFolder);
|
||||
|
||||
$share = \OC::$server->getShareManager()->newShare();
|
||||
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
|
||||
|
|
@ -3010,13 +3108,21 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$ocs->updateShare(42, null, 'password', null, 'true', '');
|
||||
}
|
||||
|
||||
public function testUpdateLinkSharePasswordDoesNotChangeOther() {
|
||||
public function testUpdateLinkSharePasswordDoesNotChangeOther(): void {
|
||||
$ocs = $this->mockFormatShare();
|
||||
|
||||
$date = new \DateTime('2000-01-01');
|
||||
$date->setTime(0,0,0);
|
||||
|
||||
$node = $this->getMockBuilder(File::class)->getMock();
|
||||
$node->method('getId')->willReturn(42);
|
||||
$userFolder = $this->createMock(Folder::class);
|
||||
$userFolder->method('getById')
|
||||
->with(42)
|
||||
->willReturn([]);
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
->with($this->currentUser)
|
||||
->willReturn($userFolder);
|
||||
$share = $this->newShare();
|
||||
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
|
||||
->setSharedBy($this->currentUser)
|
||||
|
|
@ -3061,7 +3167,15 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$date = new \DateTime('2000-01-01');
|
||||
$date->setTime(0,0,0);
|
||||
|
||||
$userFolder = $this->createMock(Folder::class);
|
||||
$userFolder->method('getById')
|
||||
->with(42)
|
||||
->willReturn([]);
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
->with($this->currentUser)
|
||||
->willReturn($userFolder);
|
||||
$node = $this->getMockBuilder(File::class)->getMock();
|
||||
$node->method('getId')->willReturn(42);
|
||||
$share = $this->newShare();
|
||||
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
|
||||
->setSharedBy($this->currentUser)
|
||||
|
|
@ -3112,7 +3226,15 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$date = new \DateTime('2000-01-01');
|
||||
$date->setTime(0,0,0);
|
||||
|
||||
$userFolder = $this->createMock(Folder::class);
|
||||
$userFolder->method('getById')
|
||||
->with(42)
|
||||
->willReturn([]);
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
->with($this->currentUser)
|
||||
->willReturn($userFolder);
|
||||
$node = $this->getMockBuilder(File::class)->getMock();
|
||||
$node->method('getId')->willReturn(42);
|
||||
$share = $this->newShare();
|
||||
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
|
||||
->setSharedBy($this->currentUser)
|
||||
|
|
@ -3145,7 +3267,15 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$date = new \DateTime('2000-01-01');
|
||||
$date->setTime(0,0,0);
|
||||
|
||||
$userFolder = $this->createMock(Folder::class);
|
||||
$userFolder->method('getById')
|
||||
->with(42)
|
||||
->willReturn([]);
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
->with($this->currentUser)
|
||||
->willReturn($userFolder);
|
||||
$node = $this->getMockBuilder(File::class)->getMock();
|
||||
$node->method('getId')->willReturn(42);
|
||||
$share = $this->newShare();
|
||||
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
|
||||
->setSharedBy($this->currentUser)
|
||||
|
|
@ -3725,7 +3855,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$recipient = $this->getMockBuilder(IUser::class)->getMock();
|
||||
$recipient->method('getDisplayName')->willReturn('recipientDN');
|
||||
$recipient->method('getSystemEMailAddress')->willReturn('recipient');
|
||||
|
||||
[$shareAttributes, $shareAttributesReturnJson] = $this->mockShareAttributes();
|
||||
|
||||
$result = [];
|
||||
|
||||
|
|
@ -3735,6 +3865,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
->setSharedBy('initiator')
|
||||
->setShareOwner('owner')
|
||||
->setPermissions(\OCP\Constants::PERMISSION_READ)
|
||||
->setAttributes($shareAttributes)
|
||||
->setNode($file)
|
||||
->setShareTime(new \DateTime('2000-01-01T00:01:02'))
|
||||
->setTarget('myTarget')
|
||||
|
|
@ -3749,6 +3880,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
'permissions' => 1,
|
||||
'attributes' => $shareAttributesReturnJson,
|
||||
'stime' => 946684862,
|
||||
'parent' => null,
|
||||
'expiration' => null,
|
||||
|
|
@ -3775,6 +3907,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'status' => [],
|
||||
'attributes' => '[{"scope":"permissions","key":"download","enabled":true}]',
|
||||
], $share, [], false
|
||||
];
|
||||
// User backend up
|
||||
|
|
@ -3785,6 +3918,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiatorDN',
|
||||
'permissions' => 1,
|
||||
'attributes' => $shareAttributesReturnJson,
|
||||
'stime' => 946684862,
|
||||
'parent' => null,
|
||||
'expiration' => null,
|
||||
|
|
@ -3811,6 +3945,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'status' => [],
|
||||
'attributes' => '[{"scope":"permissions","key":"download","enabled":true}]',
|
||||
], $share, [
|
||||
['owner', $owner],
|
||||
['initiator', $initiator],
|
||||
|
|
@ -3837,6 +3972,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
'permissions' => 1,
|
||||
'attributes' => null,
|
||||
'stime' => 946684862,
|
||||
'parent' => null,
|
||||
'expiration' => null,
|
||||
|
|
@ -3863,6 +3999,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'status' => [],
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -3885,6 +4022,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
'permissions' => 1,
|
||||
'attributes' => null,
|
||||
'stime' => 946684862,
|
||||
'parent' => null,
|
||||
'expiration' => null,
|
||||
|
|
@ -3911,6 +4049,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => true,
|
||||
'can_delete' => true,
|
||||
'status' => [],
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -3935,6 +4074,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
'permissions' => 1,
|
||||
'attributes' => null,
|
||||
'stime' => 946684862,
|
||||
'parent' => null,
|
||||
'expiration' => null,
|
||||
|
|
@ -3959,6 +4099,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4005,6 +4146,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4030,6 +4172,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
'permissions' => 1,
|
||||
'attributes' => null,
|
||||
'stime' => 946684862,
|
||||
'parent' => null,
|
||||
'expiration' => '2001-01-02 00:00:00',
|
||||
|
|
@ -4057,6 +4200,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4110,6 +4254,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4157,6 +4302,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4204,6 +4350,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4228,6 +4375,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
'permissions' => 1,
|
||||
'attributes' => null,
|
||||
'stime' => 946684862,
|
||||
'parent' => null,
|
||||
'expiration' => null,
|
||||
|
|
@ -4253,6 +4401,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4300,6 +4449,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4347,6 +4497,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4411,6 +4562,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'password_expiration_time' => null,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4461,6 +4613,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'password_expiration_time' => null,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4510,6 +4663,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => true,
|
||||
'can_delete' => true,
|
||||
'status' => [],
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
||||
|
|
@ -4661,6 +4815,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'label' => '',
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, false, []
|
||||
];
|
||||
|
||||
|
|
@ -4707,6 +4862,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'label' => '',
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'attributes' => null,
|
||||
], $share, true, [
|
||||
'share_with_displayname' => 'recipientRoomName'
|
||||
]
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ use OCP\IConfig;
|
|||
use OCP\ILogger;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Share\IAttributes as IShareAttributes;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
|
|
@ -81,11 +82,35 @@ class MountProviderTest extends \Test\TestCase {
|
|||
$this->provider = new MountProvider($this->config, $this->shareManager, $this->logger, $eventDispatcher, $cacheFactory);
|
||||
}
|
||||
|
||||
private function makeMockShare($id, $nodeId, $owner = 'user2', $target = null, $permissions = 31) {
|
||||
private function makeMockShareAttributes($attrs) {
|
||||
if ($attrs === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$shareAttributes = $this->createMock(IShareAttributes::class);
|
||||
$shareAttributes->method('toArray')->willReturn($attrs);
|
||||
$shareAttributes->method('getAttribute')->will(
|
||||
$this->returnCallback(function ($scope, $key) use ($attrs) {
|
||||
$result = null;
|
||||
foreach ($attrs as $attr) {
|
||||
if ($attr['key'] === $key && $attr['scope'] === $scope) {
|
||||
$result = $attr['enabled'];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
})
|
||||
);
|
||||
return $shareAttributes;
|
||||
}
|
||||
|
||||
private function makeMockShare($id, $nodeId, $owner = 'user2', $target = null, $permissions = 31, $attributes = null) {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$share->expects($this->any())
|
||||
->method('getPermissions')
|
||||
->willReturn($permissions);
|
||||
$share->expects($this->any())
|
||||
->method('getAttributes')
|
||||
->will($this->returnValue($this->makeMockShareAttributes($attributes)));
|
||||
$share->expects($this->any())
|
||||
->method('getShareOwner')
|
||||
->willReturn($owner);
|
||||
|
|
@ -115,14 +140,16 @@ class MountProviderTest extends \Test\TestCase {
|
|||
public function testExcludeShares() {
|
||||
$rootFolder = $this->createMock(IRootFolder::class);
|
||||
$userManager = $this->createMock(IUserManager::class);
|
||||
$attr1 = [];
|
||||
$attr2 = [['scope' => 'permission', 'key' => 'download', 'enabled' => true]];
|
||||
$userShares = [
|
||||
$this->makeMockShare(1, 100, 'user2', '/share2', 0),
|
||||
$this->makeMockShare(2, 100, 'user2', '/share2', 31),
|
||||
$this->makeMockShare(1, 100, 'user2', '/share2', 0, $attr1),
|
||||
$this->makeMockShare(2, 100, 'user2', '/share2', 31, $attr2),
|
||||
];
|
||||
$groupShares = [
|
||||
$this->makeMockShare(3, 100, 'user2', '/share2', 0),
|
||||
$this->makeMockShare(4, 101, 'user2', '/share4', 31),
|
||||
$this->makeMockShare(5, 100, 'user1', '/share4', 31),
|
||||
$this->makeMockShare(3, 100, 'user2', '/share2', 0, $attr1),
|
||||
$this->makeMockShare(4, 101, 'user2', '/share4', 31, $attr2),
|
||||
$this->makeMockShare(5, 100, 'user1', '/share4', 31, $attr2),
|
||||
];
|
||||
$roomShares = [
|
||||
$this->makeMockShare(6, 102, 'user2', '/share6', 0),
|
||||
|
|
@ -173,12 +200,14 @@ class MountProviderTest extends \Test\TestCase {
|
|||
$this->assertEquals(100, $mountedShare1->getNodeId());
|
||||
$this->assertEquals('/share2', $mountedShare1->getTarget());
|
||||
$this->assertEquals(31, $mountedShare1->getPermissions());
|
||||
$this->assertEquals(true, $mountedShare1->getAttributes()->getAttribute('permission', 'download'));
|
||||
$mountedShare2 = $mounts[1]->getShare();
|
||||
$this->assertEquals('4', $mountedShare2->getId());
|
||||
$this->assertEquals('user2', $mountedShare2->getShareOwner());
|
||||
$this->assertEquals(101, $mountedShare2->getNodeId());
|
||||
$this->assertEquals('/share4', $mountedShare2->getTarget());
|
||||
$this->assertEquals(31, $mountedShare2->getPermissions());
|
||||
$this->assertEquals(true, $mountedShare2->getAttributes()->getAttribute('permission', 'download'));
|
||||
$mountedShare3 = $mounts[2]->getShare();
|
||||
$this->assertEquals('8', $mountedShare3->getId());
|
||||
$this->assertEquals('user2', $mountedShare3->getShareOwner());
|
||||
|
|
@ -200,27 +229,27 @@ class MountProviderTest extends \Test\TestCase {
|
|||
// #0: share as outsider with "group1" and "user1" with same permissions
|
||||
[
|
||||
[
|
||||
[1, 100, 'user2', '/share2', 31],
|
||||
[1, 100, 'user2', '/share2', 31, null],
|
||||
],
|
||||
[
|
||||
[2, 100, 'user2', '/share2', 31],
|
||||
[2, 100, 'user2', '/share2', 31, null],
|
||||
],
|
||||
[
|
||||
// combined, user share has higher priority
|
||||
['1', 100, 'user2', '/share2', 31],
|
||||
['1', 100, 'user2', '/share2', 31, []],
|
||||
],
|
||||
],
|
||||
// #1: share as outsider with "group1" and "user1" with different permissions
|
||||
[
|
||||
[
|
||||
[1, 100, 'user2', '/share', 31],
|
||||
[1, 100, 'user2', '/share', 31, [['scope' => 'permission', 'key' => 'download', 'enabled' => true], ['scope' => 'app', 'key' => 'attribute1', 'enabled' => true]]],
|
||||
],
|
||||
[
|
||||
[2, 100, 'user2', '/share', 15],
|
||||
[2, 100, 'user2', '/share', 15, [['scope' => 'permission', 'key' => 'download', 'enabled' => false], ['scope' => 'app', 'key' => 'attribute2', 'enabled' => false]]],
|
||||
],
|
||||
[
|
||||
// use highest permissions
|
||||
['1', 100, 'user2', '/share', 31],
|
||||
['1', 100, 'user2', '/share', 31, [['scope' => 'permission', 'key' => 'download', 'enabled' => true], ['scope' => 'app', 'key' => 'attribute1', 'enabled' => true], ['scope' => 'app', 'key' => 'attribute2', 'enabled' => false]]],
|
||||
],
|
||||
],
|
||||
// #2: share as outsider with "group1" and "group2" with same permissions
|
||||
|
|
@ -228,12 +257,12 @@ class MountProviderTest extends \Test\TestCase {
|
|||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user2', '/share', 31],
|
||||
[2, 100, 'user2', '/share', 31],
|
||||
[1, 100, 'user2', '/share', 31, null],
|
||||
[2, 100, 'user2', '/share', 31, []],
|
||||
],
|
||||
[
|
||||
// combined, first group share has higher priority
|
||||
['1', 100, 'user2', '/share', 31],
|
||||
['1', 100, 'user2', '/share', 31, []],
|
||||
],
|
||||
],
|
||||
// #3: share as outsider with "group1" and "group2" with different permissions
|
||||
|
|
@ -241,12 +270,12 @@ class MountProviderTest extends \Test\TestCase {
|
|||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user2', '/share', 31],
|
||||
[2, 100, 'user2', '/share', 15],
|
||||
[1, 100, 'user2', '/share', 31, [['scope' => 'permission', 'key' => 'download', 'enabled' => false]]],
|
||||
[2, 100, 'user2', '/share', 15, [['scope' => 'permission', 'key' => 'download', 'enabled' => true]]],
|
||||
],
|
||||
[
|
||||
// use higher permissions
|
||||
['1', 100, 'user2', '/share', 31],
|
||||
// use higher permissions (most permissive)
|
||||
['1', 100, 'user2', '/share', 31, [['scope' => 'permission', 'key' => 'download', 'enabled' => true]]],
|
||||
],
|
||||
],
|
||||
// #4: share as insider with "group1"
|
||||
|
|
@ -254,7 +283,7 @@ class MountProviderTest extends \Test\TestCase {
|
|||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user1', '/share', 31],
|
||||
[1, 100, 'user1', '/share', 31, []],
|
||||
],
|
||||
[
|
||||
// no received share since "user1" is the sharer/owner
|
||||
|
|
@ -265,8 +294,8 @@ class MountProviderTest extends \Test\TestCase {
|
|||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user1', '/share', 31],
|
||||
[2, 100, 'user1', '/share', 15],
|
||||
[1, 100, 'user1', '/share', 31, [['scope' => 'permission', 'key' => 'download', 'enabled' => true]]],
|
||||
[2, 100, 'user1', '/share', 15, [['scope' => 'permission', 'key' => 'download', 'enabled' => false]]],
|
||||
],
|
||||
[
|
||||
// no received share since "user1" is the sharer/owner
|
||||
|
|
@ -277,7 +306,7 @@ class MountProviderTest extends \Test\TestCase {
|
|||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user2', '/share', 0],
|
||||
[1, 100, 'user2', '/share', 0, []],
|
||||
],
|
||||
[
|
||||
// no received share since "user1" opted out
|
||||
|
|
@ -286,40 +315,40 @@ class MountProviderTest extends \Test\TestCase {
|
|||
// #7: share as outsider with "group1" and "user1" where recipient renamed in between
|
||||
[
|
||||
[
|
||||
[1, 100, 'user2', '/share2-renamed', 31],
|
||||
[1, 100, 'user2', '/share2-renamed', 31, []],
|
||||
],
|
||||
[
|
||||
[2, 100, 'user2', '/share2', 31],
|
||||
[2, 100, 'user2', '/share2', 31, []],
|
||||
],
|
||||
[
|
||||
// use target of least recent share
|
||||
['1', 100, 'user2', '/share2-renamed', 31],
|
||||
['1', 100, 'user2', '/share2-renamed', 31, []],
|
||||
],
|
||||
],
|
||||
// #8: share as outsider with "group1" and "user1" where recipient renamed in between
|
||||
[
|
||||
[
|
||||
[2, 100, 'user2', '/share2', 31],
|
||||
[2, 100, 'user2', '/share2', 31, []],
|
||||
],
|
||||
[
|
||||
[1, 100, 'user2', '/share2-renamed', 31],
|
||||
[1, 100, 'user2', '/share2-renamed', 31, []],
|
||||
],
|
||||
[
|
||||
// use target of least recent share
|
||||
['1', 100, 'user2', '/share2-renamed', 31],
|
||||
['1', 100, 'user2', '/share2-renamed', 31, []],
|
||||
],
|
||||
],
|
||||
// #9: share as outsider with "nullgroup" and "user1" where recipient renamed in between
|
||||
[
|
||||
[
|
||||
[2, 100, 'user2', '/share2', 31],
|
||||
[2, 100, 'user2', '/share2', 31, []],
|
||||
],
|
||||
[
|
||||
[1, 100, 'nullgroup', '/share2-renamed', 31],
|
||||
[1, 100, 'nullgroup', '/share2-renamed', 31, []],
|
||||
],
|
||||
[
|
||||
// use target of least recent share
|
||||
['1', 100, 'nullgroup', '/share2-renamed', 31],
|
||||
['1', 100, 'nullgroup', '/share2-renamed', 31, []],
|
||||
],
|
||||
true
|
||||
],
|
||||
|
|
@ -343,10 +372,10 @@ class MountProviderTest extends \Test\TestCase {
|
|||
$userManager = $this->createMock(IUserManager::class);
|
||||
|
||||
$userShares = array_map(function ($shareSpec) {
|
||||
return $this->makeMockShare($shareSpec[0], $shareSpec[1], $shareSpec[2], $shareSpec[3], $shareSpec[4]);
|
||||
return $this->makeMockShare($shareSpec[0], $shareSpec[1], $shareSpec[2], $shareSpec[3], $shareSpec[4], $shareSpec[5]);
|
||||
}, $userShares);
|
||||
$groupShares = array_map(function ($shareSpec) {
|
||||
return $this->makeMockShare($shareSpec[0], $shareSpec[1], $shareSpec[2], $shareSpec[3], $shareSpec[4]);
|
||||
return $this->makeMockShare($shareSpec[0], $shareSpec[1], $shareSpec[2], $shareSpec[3], $shareSpec[4], $shareSpec[5]);
|
||||
}, $groupShares);
|
||||
|
||||
$this->user->expects($this->any())
|
||||
|
|
@ -400,6 +429,11 @@ class MountProviderTest extends \Test\TestCase {
|
|||
$this->assertEquals($expectedShare[2], $share->getShareOwner());
|
||||
$this->assertEquals($expectedShare[3], $share->getTarget());
|
||||
$this->assertEquals($expectedShare[4], $share->getPermissions());
|
||||
if ($expectedShare[5] === null) {
|
||||
$this->assertNull($share->getAttributes());
|
||||
} else {
|
||||
$this->assertEquals($expectedShare[5], $share->getAttributes()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ import escapeHTML from 'escape-html'
|
|||
Client.PROPERTY_GETCONTENTLENGTH = '{' + Client.NS_DAV + '}getcontentlength'
|
||||
Client.PROPERTY_ISENCRYPTED = '{' + Client.NS_DAV + '}is-encrypted'
|
||||
Client.PROPERTY_SHARE_PERMISSIONS = '{' + Client.NS_OCS + '}share-permissions'
|
||||
Client.PROPERTY_SHARE_ATTRIBUTES = '{' + Client.NS_NEXTCLOUD + '}share-attributes'
|
||||
Client.PROPERTY_QUOTA_AVAILABLE_BYTES = '{' + Client.NS_DAV + '}quota-available-bytes'
|
||||
|
||||
Client.PROTOCOL_HTTP = 'http'
|
||||
|
|
@ -160,6 +161,10 @@ import escapeHTML from 'escape-html'
|
|||
* Share permissions
|
||||
*/
|
||||
[Client.NS_OCS, 'share-permissions'],
|
||||
/**
|
||||
* Share attributes
|
||||
*/
|
||||
[Client.NS_NEXTCLOUD, 'share-attributes'],
|
||||
]
|
||||
|
||||
/**
|
||||
|
|
@ -416,6 +421,18 @@ import escapeHTML from 'escape-html'
|
|||
data.sharePermissions = parseInt(sharePermissionsProp)
|
||||
}
|
||||
|
||||
const shareAttributesProp = props[Client.PROPERTY_SHARE_ATTRIBUTES]
|
||||
if (!_.isUndefined(shareAttributesProp)) {
|
||||
try {
|
||||
data.shareAttributes = JSON.parse(shareAttributesProp)
|
||||
} catch (e) {
|
||||
console.warn('Could not parse share attributes returned by server: "' + shareAttributesProp + '"')
|
||||
data.shareAttributes = [];
|
||||
}
|
||||
} else {
|
||||
data.shareAttributes = [];
|
||||
}
|
||||
|
||||
const mounTypeProp = props['{' + Client.NS_NEXTCLOUD + '}mount-type']
|
||||
if (!_.isUndefined(mounTypeProp)) {
|
||||
data.mountType = mounTypeProp
|
||||
|
|
|
|||
|
|
@ -155,7 +155,23 @@
|
|||
*/
|
||||
sharePermissions: null,
|
||||
|
||||
/**
|
||||
* @type Array
|
||||
*/
|
||||
shareAttributes: [],
|
||||
|
||||
quotaAvailableBytes: -1,
|
||||
|
||||
canDownload: function() {
|
||||
for (const i in this.shareAttributes) {
|
||||
const attr = this.shareAttributes[i]
|
||||
if (attr.scope === 'permissions' && attr.key === 'download') {
|
||||
return attr.enabled
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
if (!OC.Files) {
|
||||
|
|
|
|||
4
dist/core-files_client.js
vendored
4
dist/core-files_client.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-files_client.js.map
vendored
2
dist/core-files_client.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-files_fileinfo.js
vendored
4
dist/core-files_fileinfo.js
vendored
|
|
@ -1,3 +1,3 @@
|
|||
/*! For license information please see core-files_fileinfo.js.LICENSE.txt */
|
||||
!function(i){var t=function(i){var t=this;_.each(i,(function(i,e){_.isFunction(i)||(t[e]=i)})),_.isUndefined(this.id)||(this.id=parseInt(i.id,10)),this.path=i.path||"","dir"===this.type?this.mimetype="httpd/unix-directory":this.mimetype=this.mimetype||"application/octet-stream",this.type||("httpd/unix-directory"===this.mimetype?this.type="dir":this.type="file")};t.prototype={id:null,name:null,path:null,mimetype:null,icon:null,type:null,permissions:null,mtime:null,etag:null,mountType:null,hasPreview:!0,sharePermissions:null,quotaAvailableBytes:-1},i.Files||(i.Files={}),i.Files.FileInfo=t}(OC);
|
||||
//# sourceMappingURL=core-files_fileinfo.js.map?v=d9cc5e8823977a1e870b
|
||||
!function(t){var i=function(t){var i=this;_.each(t,(function(t,e){_.isFunction(t)||(i[e]=t)})),_.isUndefined(this.id)||(this.id=parseInt(t.id,10)),this.path=t.path||"","dir"===this.type?this.mimetype="httpd/unix-directory":this.mimetype=this.mimetype||"application/octet-stream",this.type||("httpd/unix-directory"===this.mimetype?this.type="dir":this.type="file")};i.prototype={id:null,name:null,path:null,mimetype:null,icon:null,type:null,permissions:null,mtime:null,etag:null,mountType:null,hasPreview:!0,sharePermissions:null,shareAttributes:[],quotaAvailableBytes:-1,canDownload:function(){for(var t in this.shareAttributes){var i=this.shareAttributes[t];if("permissions"===i.scope&&"download"===i.key)return i.enabled}return!0}},t.Files||(t.Files={}),t.Files.FileInfo=i}(OC);
|
||||
//# sourceMappingURL=core-files_fileinfo.js.map?v=d5c54f8e5b3834c089a0
|
||||
2
dist/core-files_fileinfo.js.map
vendored
2
dist/core-files_fileinfo.js.map
vendored
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"core-files_fileinfo.js?v=d9cc5e8823977a1e870b","mappings":";CA0BA,SAAUA,GAUT,IAAMC,EAAW,SAASC,GACzB,IAAMC,EAAOC,KACbC,EAAEC,KAAKJ,GAAM,SAASK,EAAOC,GACvBH,EAAEI,WAAWF,KACjBJ,EAAKK,GAAOD,MAITF,EAAEK,YAAYN,KAAKO,MACvBP,KAAKO,GAAKC,SAASV,EAAKS,GAAI,KAI7BP,KAAKS,KAAOX,EAAKW,MAAQ,GAEP,QAAdT,KAAKU,KACRV,KAAKW,SAAW,uBAEhBX,KAAKW,SAAWX,KAAKW,UAAY,2BAG7BX,KAAKU,OACa,yBAAlBV,KAAKW,SACRX,KAAKU,KAAO,MAEZV,KAAKU,KAAO,SAQfb,EAASe,UAAY,CAMpBL,GAAI,KAOJM,KAAM,KAQNJ,KAAM,KAONE,SAAU,KASVG,KAAM,KAQNJ,KAAM,KAQNK,YAAa,KAObC,MAAO,KAOPC,KAAM,KASNC,UAAW,KAKXC,YAAY,EAKZC,iBAAkB,KAElBC,qBAAsB,GAGlBzB,EAAG0B,QACP1B,EAAG0B,MAAQ,IAEZ1B,EAAG0B,MAAMzB,SAAWA,EAzIrB,CA0IGD","sources":["webpack:///nextcloud/core/src/files/fileinfo.js"],"sourcesContent":["/**\n * Copyright (c) 2015\n *\n * @author Julius Härtl <jus@bitgrid.net>\n * @author Robin Appelman <robin@icewind.nl>\n * @author Roeland Jago Douma <roeland@famdouma.nl>\n * @author Vincent Petry <vincent@nextcloud.com>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\n/* eslint-disable */\n(function(OC) {\n\n\t/**\n\t * @class OC.Files.FileInfo\n\t * @classdesc File information\n\t *\n\t * @param {Object} data file data, see attributes for details\n\t *\n\t * @since 8.2\n\t */\n\tconst FileInfo = function(data) {\n\t\tconst self = this\n\t\t_.each(data, function(value, key) {\n\t\t\tif (!_.isFunction(value)) {\n\t\t\t\tself[key] = value\n\t\t\t}\n\t\t})\n\n\t\tif (!_.isUndefined(this.id)) {\n\t\t\tthis.id = parseInt(data.id, 10)\n\t\t}\n\n\t\t// TODO: normalize path\n\t\tthis.path = data.path || ''\n\n\t\tif (this.type === 'dir') {\n\t\t\tthis.mimetype = 'httpd/unix-directory'\n\t\t} else {\n\t\t\tthis.mimetype = this.mimetype || 'application/octet-stream'\n\t\t}\n\n\t\tif (!this.type) {\n\t\t\tif (this.mimetype === 'httpd/unix-directory') {\n\t\t\t\tthis.type = 'dir'\n\t\t\t} else {\n\t\t\t\tthis.type = 'file'\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @memberof OC.Files\n\t */\n\tFileInfo.prototype = {\n\t\t/**\n\t\t * File id\n\t\t *\n\t\t * @type int\n\t\t */\n\t\tid: null,\n\n\t\t/**\n\t\t * File name\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tname: null,\n\n\t\t/**\n\t\t * Path leading to the file, without the file name,\n\t\t * and with a leading slash.\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tpath: null,\n\n\t\t/**\n\t\t * Mime type\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tmimetype: null,\n\n\t\t/**\n\t\t * Icon URL.\n\t\t *\n\t\t * Can be used to override the mime type icon.\n\t\t *\n\t\t * @type String\n\t\t */\n\t\ticon: null,\n\n\t\t/**\n\t\t * File type. 'file' for files, 'dir' for directories.\n\t\t *\n\t\t * @type String\n\t\t * @deprecated rely on mimetype instead\n\t\t */\n\t\ttype: null,\n\n\t\t/**\n\t\t * Permissions.\n\t\t *\n\t\t * @see OC#PERMISSION_ALL for permissions\n\t\t * @type int\n\t\t */\n\t\tpermissions: null,\n\n\t\t/**\n\t\t * Modification time\n\t\t *\n\t\t * @type int\n\t\t */\n\t\tmtime: null,\n\n\t\t/**\n\t\t * Etag\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tetag: null,\n\n\t\t/**\n\t\t * Mount type.\n\t\t *\n\t\t * One of null, \"external-root\", \"shared\" or \"shared-root\"\n\t\t *\n\t\t * @type string\n\t\t */\n\t\tmountType: null,\n\n\t\t/**\n\t\t * @type boolean\n\t\t */\n\t\thasPreview: true,\n\n\t\t/**\n\t\t * @type int\n\t\t */\n\t\tsharePermissions: null,\n\n\t\tquotaAvailableBytes: -1,\n\t}\n\n\tif (!OC.Files) {\n\t\tOC.Files = {}\n\t}\n\tOC.Files.FileInfo = FileInfo\n})(OC)\n"],"names":["OC","FileInfo","data","self","this","_","each","value","key","isFunction","isUndefined","id","parseInt","path","type","mimetype","prototype","name","icon","permissions","mtime","etag","mountType","hasPreview","sharePermissions","quotaAvailableBytes","Files"],"sourceRoot":""}
|
||||
{"version":3,"file":"core-files_fileinfo.js?v=d5c54f8e5b3834c089a0","mappings":";CA0BA,SAAUA,GAUT,IAAMC,EAAW,SAASC,GACzB,IAAMC,EAAOC,KACbC,EAAEC,KAAKJ,GAAM,SAASK,EAAOC,GACvBH,EAAEI,WAAWF,KACjBJ,EAAKK,GAAOD,MAITF,EAAEK,YAAYN,KAAKO,MACvBP,KAAKO,GAAKC,SAASV,EAAKS,GAAI,KAI7BP,KAAKS,KAAOX,EAAKW,MAAQ,GAEP,QAAdT,KAAKU,KACRV,KAAKW,SAAW,uBAEhBX,KAAKW,SAAWX,KAAKW,UAAY,2BAG7BX,KAAKU,OACa,yBAAlBV,KAAKW,SACRX,KAAKU,KAAO,MAEZV,KAAKU,KAAO,SAQfb,EAASe,UAAY,CAMpBL,GAAI,KAOJM,KAAM,KAQNJ,KAAM,KAONE,SAAU,KASVG,KAAM,KAQNJ,KAAM,KAQNK,YAAa,KAObC,MAAO,KAOPC,KAAM,KASNC,UAAW,KAKXC,YAAY,EAKZC,iBAAkB,KAKlBC,gBAAiB,GAEjBC,qBAAsB,EAEtBC,YAAa,WACZ,IAAK,IAAMC,KAAKxB,KAAKqB,gBAAiB,CACrC,IAAMI,EAAOzB,KAAKqB,gBAAgBG,GAClC,GAAmB,gBAAfC,EAAKC,OAAwC,aAAbD,EAAKrB,IACxC,OAAOqB,EAAKE,QAId,OAAO,IAIJ/B,EAAGgC,QACPhC,EAAGgC,MAAQ,IAEZhC,EAAGgC,MAAM/B,SAAWA,EAzJrB,CA0JGD","sources":["webpack:///nextcloud/core/src/files/fileinfo.js"],"sourcesContent":["/**\n * Copyright (c) 2015\n *\n * @author Julius Härtl <jus@bitgrid.net>\n * @author Robin Appelman <robin@icewind.nl>\n * @author Roeland Jago Douma <roeland@famdouma.nl>\n * @author Vincent Petry <vincent@nextcloud.com>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\n/* eslint-disable */\n(function(OC) {\n\n\t/**\n\t * @class OC.Files.FileInfo\n\t * @classdesc File information\n\t *\n\t * @param {Object} data file data, see attributes for details\n\t *\n\t * @since 8.2\n\t */\n\tconst FileInfo = function(data) {\n\t\tconst self = this\n\t\t_.each(data, function(value, key) {\n\t\t\tif (!_.isFunction(value)) {\n\t\t\t\tself[key] = value\n\t\t\t}\n\t\t})\n\n\t\tif (!_.isUndefined(this.id)) {\n\t\t\tthis.id = parseInt(data.id, 10)\n\t\t}\n\n\t\t// TODO: normalize path\n\t\tthis.path = data.path || ''\n\n\t\tif (this.type === 'dir') {\n\t\t\tthis.mimetype = 'httpd/unix-directory'\n\t\t} else {\n\t\t\tthis.mimetype = this.mimetype || 'application/octet-stream'\n\t\t}\n\n\t\tif (!this.type) {\n\t\t\tif (this.mimetype === 'httpd/unix-directory') {\n\t\t\t\tthis.type = 'dir'\n\t\t\t} else {\n\t\t\t\tthis.type = 'file'\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @memberof OC.Files\n\t */\n\tFileInfo.prototype = {\n\t\t/**\n\t\t * File id\n\t\t *\n\t\t * @type int\n\t\t */\n\t\tid: null,\n\n\t\t/**\n\t\t * File name\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tname: null,\n\n\t\t/**\n\t\t * Path leading to the file, without the file name,\n\t\t * and with a leading slash.\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tpath: null,\n\n\t\t/**\n\t\t * Mime type\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tmimetype: null,\n\n\t\t/**\n\t\t * Icon URL.\n\t\t *\n\t\t * Can be used to override the mime type icon.\n\t\t *\n\t\t * @type String\n\t\t */\n\t\ticon: null,\n\n\t\t/**\n\t\t * File type. 'file' for files, 'dir' for directories.\n\t\t *\n\t\t * @type String\n\t\t * @deprecated rely on mimetype instead\n\t\t */\n\t\ttype: null,\n\n\t\t/**\n\t\t * Permissions.\n\t\t *\n\t\t * @see OC#PERMISSION_ALL for permissions\n\t\t * @type int\n\t\t */\n\t\tpermissions: null,\n\n\t\t/**\n\t\t * Modification time\n\t\t *\n\t\t * @type int\n\t\t */\n\t\tmtime: null,\n\n\t\t/**\n\t\t * Etag\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tetag: null,\n\n\t\t/**\n\t\t * Mount type.\n\t\t *\n\t\t * One of null, \"external-root\", \"shared\" or \"shared-root\"\n\t\t *\n\t\t * @type string\n\t\t */\n\t\tmountType: null,\n\n\t\t/**\n\t\t * @type boolean\n\t\t */\n\t\thasPreview: true,\n\n\t\t/**\n\t\t * @type int\n\t\t */\n\t\tsharePermissions: null,\n\n\t\t/**\n\t\t * @type Array\n\t\t */\n\t\tshareAttributes: [],\n\n\t\tquotaAvailableBytes: -1,\n\n\t\tcanDownload: function() {\n\t\t\tfor (const i in this.shareAttributes) {\n\t\t\t\tconst attr = this.shareAttributes[i]\n\t\t\t\tif (attr.scope === 'permissions' && attr.key === 'download') {\n\t\t\t\t\treturn attr.enabled\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true\n\t\t},\n\t}\n\n\tif (!OC.Files) {\n\t\tOC.Files = {}\n\t}\n\tOC.Files.FileInfo = FileInfo\n})(OC)\n"],"names":["OC","FileInfo","data","self","this","_","each","value","key","isFunction","isUndefined","id","parseInt","path","type","mimetype","prototype","name","icon","permissions","mtime","etag","mountType","hasPreview","sharePermissions","shareAttributes","quotaAvailableBytes","canDownload","i","attr","scope","enabled","Files"],"sourceRoot":""}
|
||||
4
dist/files-sidebar.js
vendored
4
dist/files-sidebar.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-sidebar.js.map
vendored
2
dist/files-sidebar.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-additionalScripts.js
vendored
4
dist/files_sharing-additionalScripts.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-additionalScripts.js.map
vendored
2
dist/files_sharing-additionalScripts.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-files_sharing_tab.js
vendored
4
dist/files_sharing-files_sharing_tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-files_sharing_tab.js.map
vendored
2
dist/files_sharing-files_sharing_tab.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -266,8 +266,10 @@ return array(
|
|||
'OCP\\Files\\Config\\IUserMountCache' => $baseDir . '/lib/public/Files/Config/IUserMountCache.php',
|
||||
'OCP\\Files\\EmptyFileNameException' => $baseDir . '/lib/public/Files/EmptyFileNameException.php',
|
||||
'OCP\\Files\\EntityTooLargeException' => $baseDir . '/lib/public/Files/EntityTooLargeException.php',
|
||||
'OCP\\Files\\Events\\BeforeDirectFileDownloadEvent' => $baseDir . '/lib/public/Files/Events/BeforeDirectFileDownloadEvent.php',
|
||||
'OCP\\Files\\Events\\BeforeFileScannedEvent' => $baseDir . '/lib/public/Files/Events/BeforeFileScannedEvent.php',
|
||||
'OCP\\Files\\Events\\BeforeFolderScannedEvent' => $baseDir . '/lib/public/Files/Events/BeforeFolderScannedEvent.php',
|
||||
'OCP\\Files\\Events\\BeforeZipCreatedEvent' => $baseDir . '/lib/public/Files/Events/BeforeZipCreatedEvent.php',
|
||||
'OCP\\Files\\Events\\FileCacheUpdated' => $baseDir . '/lib/public/Files/Events/FileCacheUpdated.php',
|
||||
'OCP\\Files\\Events\\FileScannedEvent' => $baseDir . '/lib/public/Files/Events/FileScannedEvent.php',
|
||||
'OCP\\Files\\Events\\FolderScannedEvent' => $baseDir . '/lib/public/Files/Events/FolderScannedEvent.php',
|
||||
|
|
@ -539,6 +541,7 @@ return array(
|
|||
'OCP\\Share\\Exceptions\\GenericShareException' => $baseDir . '/lib/public/Share/Exceptions/GenericShareException.php',
|
||||
'OCP\\Share\\Exceptions\\IllegalIDChangeException' => $baseDir . '/lib/public/Share/Exceptions/IllegalIDChangeException.php',
|
||||
'OCP\\Share\\Exceptions\\ShareNotFound' => $baseDir . '/lib/public/Share/Exceptions/ShareNotFound.php',
|
||||
'OCP\\Share\\IAttributes' => $baseDir . '/lib/public/Share/IAttributes.php',
|
||||
'OCP\\Share\\IManager' => $baseDir . '/lib/public/Share/IManager.php',
|
||||
'OCP\\Share\\IProviderFactory' => $baseDir . '/lib/public/Share/IProviderFactory.php',
|
||||
'OCP\\Share\\IShare' => $baseDir . '/lib/public/Share/IShare.php',
|
||||
|
|
@ -1512,6 +1515,7 @@ return array(
|
|||
'OC\\Share20\\Manager' => $baseDir . '/lib/private/Share20/Manager.php',
|
||||
'OC\\Share20\\ProviderFactory' => $baseDir . '/lib/private/Share20/ProviderFactory.php',
|
||||
'OC\\Share20\\Share' => $baseDir . '/lib/private/Share20/Share.php',
|
||||
'OC\\Share20\\ShareAttributes' => $baseDir . '/lib/private/Share20/ShareAttributes.php',
|
||||
'OC\\Share20\\ShareHelper' => $baseDir . '/lib/private/Share20/ShareHelper.php',
|
||||
'OC\\Share20\\UserRemovedListener' => $baseDir . '/lib/private/Share20/UserRemovedListener.php',
|
||||
'OC\\Share\\Constants' => $baseDir . '/lib/private/Share/Constants.php',
|
||||
|
|
|
|||
|
|
@ -299,8 +299,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OCP\\Files\\Config\\IUserMountCache' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IUserMountCache.php',
|
||||
'OCP\\Files\\EmptyFileNameException' => __DIR__ . '/../../..' . '/lib/public/Files/EmptyFileNameException.php',
|
||||
'OCP\\Files\\EntityTooLargeException' => __DIR__ . '/../../..' . '/lib/public/Files/EntityTooLargeException.php',
|
||||
'OCP\\Files\\Events\\BeforeDirectFileDownloadEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/BeforeDirectFileDownloadEvent.php',
|
||||
'OCP\\Files\\Events\\BeforeFileScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/BeforeFileScannedEvent.php',
|
||||
'OCP\\Files\\Events\\BeforeFolderScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/BeforeFolderScannedEvent.php',
|
||||
'OCP\\Files\\Events\\BeforeZipCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/BeforeZipCreatedEvent.php',
|
||||
'OCP\\Files\\Events\\FileCacheUpdated' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FileCacheUpdated.php',
|
||||
'OCP\\Files\\Events\\FileScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FileScannedEvent.php',
|
||||
'OCP\\Files\\Events\\FolderScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FolderScannedEvent.php',
|
||||
|
|
@ -572,6 +574,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OCP\\Share\\Exceptions\\GenericShareException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/GenericShareException.php',
|
||||
'OCP\\Share\\Exceptions\\IllegalIDChangeException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/IllegalIDChangeException.php',
|
||||
'OCP\\Share\\Exceptions\\ShareNotFound' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/ShareNotFound.php',
|
||||
'OCP\\Share\\IAttributes' => __DIR__ . '/../../..' . '/lib/public/Share/IAttributes.php',
|
||||
'OCP\\Share\\IManager' => __DIR__ . '/../../..' . '/lib/public/Share/IManager.php',
|
||||
'OCP\\Share\\IProviderFactory' => __DIR__ . '/../../..' . '/lib/public/Share/IProviderFactory.php',
|
||||
'OCP\\Share\\IShare' => __DIR__ . '/../../..' . '/lib/public/Share/IShare.php',
|
||||
|
|
@ -1545,6 +1548,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\Share20\\Manager' => __DIR__ . '/../../..' . '/lib/private/Share20/Manager.php',
|
||||
'OC\\Share20\\ProviderFactory' => __DIR__ . '/../../..' . '/lib/private/Share20/ProviderFactory.php',
|
||||
'OC\\Share20\\Share' => __DIR__ . '/../../..' . '/lib/private/Share20/Share.php',
|
||||
'OC\\Share20\\ShareAttributes' => __DIR__ . '/../../..' . '/lib/private/Share20/ShareAttributes.php',
|
||||
'OC\\Share20\\ShareHelper' => __DIR__ . '/../../..' . '/lib/private/Share20/ShareHelper.php',
|
||||
'OC\\Share20\\UserRemovedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserRemovedListener.php',
|
||||
'OC\\Share\\Constants' => __DIR__ . '/../../..' . '/lib/private/Share/Constants.php',
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ use OCP\IUserManager;
|
|||
use OCP\L10N\IFactory;
|
||||
use OCP\Mail\IMailer;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IAttributes;
|
||||
use OCP\Share\IShare;
|
||||
use OCP\Share\IShareProvider;
|
||||
|
||||
|
|
@ -174,6 +175,8 @@ class DefaultShareProvider implements IShareProvider {
|
|||
if (method_exists($share, 'getParent')) {
|
||||
$qb->setValue('parent', $qb->createNamedParameter($share->getParent()));
|
||||
}
|
||||
|
||||
$qb->setValue('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0, IQueryBuilder::PARAM_INT));
|
||||
} else {
|
||||
throw new \Exception('invalid share type!');
|
||||
}
|
||||
|
|
@ -193,6 +196,12 @@ class DefaultShareProvider implements IShareProvider {
|
|||
// set the permissions
|
||||
$qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions()));
|
||||
|
||||
// set share attributes
|
||||
$shareAttributes = $this->formatShareAttributes(
|
||||
$share->getAttributes()
|
||||
);
|
||||
$qb->setValue('attributes', $qb->createNamedParameter($shareAttributes));
|
||||
|
||||
// Set who created this share
|
||||
$qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
|
||||
|
||||
|
|
@ -248,6 +257,8 @@ class DefaultShareProvider implements IShareProvider {
|
|||
public function update(\OCP\Share\IShare $share) {
|
||||
$originalShare = $this->getShareById($share->getId());
|
||||
|
||||
$shareAttributes = $this->formatShareAttributes($share->getAttributes());
|
||||
|
||||
if ($share->getShareType() === IShare::TYPE_USER) {
|
||||
/*
|
||||
* We allow updating the recipient on user shares.
|
||||
|
|
@ -259,6 +270,7 @@ class DefaultShareProvider implements IShareProvider {
|
|||
->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
|
||||
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
|
||||
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
|
||||
->set('attributes', $qb->createNamedParameter($shareAttributes))
|
||||
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
|
||||
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
|
||||
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
|
||||
|
|
@ -272,6 +284,7 @@ class DefaultShareProvider implements IShareProvider {
|
|||
->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
|
||||
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
|
||||
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
|
||||
->set('attributes', $qb->createNamedParameter($shareAttributes))
|
||||
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
|
||||
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
|
||||
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
|
||||
|
|
@ -301,6 +314,7 @@ class DefaultShareProvider implements IShareProvider {
|
|||
->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
|
||||
->andWhere($qb->expr()->neq('permissions', $qb->createNamedParameter(0)))
|
||||
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
|
||||
->set('attributes', $qb->createNamedParameter($shareAttributes))
|
||||
->execute();
|
||||
} elseif ($share->getShareType() === IShare::TYPE_LINK) {
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
|
|
@ -311,6 +325,7 @@ class DefaultShareProvider implements IShareProvider {
|
|||
->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
|
||||
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
|
||||
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
|
||||
->set('attributes', $qb->createNamedParameter($shareAttributes))
|
||||
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
|
||||
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
|
||||
->set('token', $qb->createNamedParameter($share->getToken()))
|
||||
|
|
@ -611,6 +626,10 @@ class DefaultShareProvider implements IShareProvider {
|
|||
$data = $stmt->fetch();
|
||||
$stmt->closeCursor();
|
||||
|
||||
$shareAttributes = $this->formatShareAttributes(
|
||||
$share->getAttributes()
|
||||
);
|
||||
|
||||
if ($data === false) {
|
||||
// No usergroup share yet. Create one.
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
|
|
@ -626,6 +645,7 @@ class DefaultShareProvider implements IShareProvider {
|
|||
'file_source' => $qb->createNamedParameter($share->getNodeId()),
|
||||
'file_target' => $qb->createNamedParameter($share->getTarget()),
|
||||
'permissions' => $qb->createNamedParameter($share->getPermissions()),
|
||||
'attributes' => $qb->createNamedParameter($shareAttributes),
|
||||
'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
|
||||
])->execute();
|
||||
} else {
|
||||
|
|
@ -1073,6 +1093,8 @@ class DefaultShareProvider implements IShareProvider {
|
|||
$share->setToken($data['token']);
|
||||
}
|
||||
|
||||
$share = $this->updateShareAttributes($share, $data['attributes']);
|
||||
|
||||
$share->setSharedBy($data['uid_initiator']);
|
||||
$share->setShareOwner($data['uid_owner']);
|
||||
|
||||
|
|
@ -1540,4 +1562,48 @@ class DefaultShareProvider implements IShareProvider {
|
|||
}
|
||||
$cursor->closeCursor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load from database format (JSON string) to IAttributes
|
||||
*
|
||||
* @return IShare the modified share
|
||||
*/
|
||||
private function updateShareAttributes(IShare $share, ?string $data): IShare {
|
||||
if ($data !== null && $data !== '') {
|
||||
$attributes = new ShareAttributes();
|
||||
$compressedAttributes = \json_decode($data, true);
|
||||
if ($compressedAttributes === false || $compressedAttributes === null) {
|
||||
return $share;
|
||||
}
|
||||
foreach ($compressedAttributes as $compressedAttribute) {
|
||||
$attributes->setAttribute(
|
||||
$compressedAttribute[0],
|
||||
$compressedAttribute[1],
|
||||
$compressedAttribute[2]
|
||||
);
|
||||
}
|
||||
$share->setAttributes($attributes);
|
||||
}
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format IAttributes to database format (JSON string)
|
||||
*/
|
||||
private function formatShareAttributes(?IAttributes $attributes): ?string {
|
||||
if ($attributes === null || empty($attributes->toArray())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$compressedAttributes = [];
|
||||
foreach ($attributes->toArray() as $attribute) {
|
||||
$compressedAttributes[] = [
|
||||
0 => $attribute['scope'],
|
||||
1 => $attribute['key'],
|
||||
2 => $attribute['enabled']
|
||||
];
|
||||
}
|
||||
return \json_encode($compressedAttributes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1093,6 +1093,7 @@ class Manager implements IManager {
|
|||
'shareWith' => $share->getSharedWith(),
|
||||
'uidOwner' => $share->getSharedBy(),
|
||||
'permissions' => $share->getPermissions(),
|
||||
'attributes' => $share->getAttributes() !== null ? $share->getAttributes()->toArray() : null,
|
||||
'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ use OCP\Files\Node;
|
|||
use OCP\Files\NotFoundException;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Share\Exceptions\IllegalIDChangeException;
|
||||
use OCP\Share\IAttributes;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
class Share implements IShare {
|
||||
|
|
@ -65,6 +66,8 @@ class Share implements IShare {
|
|||
private $shareOwner;
|
||||
/** @var int */
|
||||
private $permissions;
|
||||
/** @var IAttributes */
|
||||
private $attributes;
|
||||
/** @var int */
|
||||
private $status;
|
||||
/** @var string */
|
||||
|
|
@ -332,6 +335,28 @@ class Share implements IShare {
|
|||
return $this->permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function newAttributes(): IAttributes {
|
||||
return new ShareAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setAttributes(?IAttributes $attributes) {
|
||||
$this->attributes = $attributes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getAttributes(): ?IAttributes {
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
|
@ -511,7 +536,7 @@ class Share implements IShare {
|
|||
* Set the parent of this share
|
||||
*
|
||||
* @param int parent
|
||||
* @return \OCP\Share\IShare
|
||||
* @return IShare
|
||||
* @deprecated The new shares do not have parents. This is just here for legacy reasons.
|
||||
*/
|
||||
public function setParent($parent) {
|
||||
|
|
|
|||
73
lib/private/Share20/ShareAttributes.php
Normal file
73
lib/private/Share20/ShareAttributes.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Piotr Mrowczynski <piotr@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2019, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OC\Share20;
|
||||
|
||||
use OCP\Share\IAttributes;
|
||||
|
||||
class ShareAttributes implements IAttributes {
|
||||
|
||||
/** @var array */
|
||||
private $attributes;
|
||||
|
||||
public function __construct() {
|
||||
$this->attributes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setAttribute($scope, $key, $enabled) {
|
||||
if (!\array_key_exists($scope, $this->attributes)) {
|
||||
$this->attributes[$scope] = [];
|
||||
}
|
||||
$this->attributes[$scope][$key] = $enabled;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getAttribute($scope, $key) {
|
||||
if (\array_key_exists($scope, $this->attributes) &&
|
||||
\array_key_exists($key, $this->attributes[$scope])) {
|
||||
return $this->attributes[$scope][$key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function toArray() {
|
||||
$result = [];
|
||||
foreach ($this->attributes as $scope => $keys) {
|
||||
foreach ($keys as $key => $enabled) {
|
||||
$result[] = [
|
||||
"scope" => $scope,
|
||||
"key" => $key,
|
||||
"enabled" => $enabled
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -44,10 +44,12 @@ use bantu\IniGetWrapper\IniGetWrapper;
|
|||
use OC\Files\View;
|
||||
use OC\Streamer;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
use OCP\Files\Events\BeforeZipCreatedEvent;
|
||||
use OCP\Files\Events\BeforeDirectFileDownloadEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
|
||||
/**
|
||||
* Class for file server access
|
||||
*
|
||||
*/
|
||||
class OC_Files {
|
||||
public const FILE = 1;
|
||||
|
|
@ -167,6 +169,14 @@ class OC_Files {
|
|||
}
|
||||
}
|
||||
|
||||
//Dispatch an event to see if any apps have problem with download
|
||||
$event = new BeforeZipCreatedEvent($dir, is_array($files) ? $files : [$files]);
|
||||
$dispatcher = \OCP\Server::get(IEventDispatcher::class);
|
||||
$dispatcher->dispatchTyped($event);
|
||||
if ((!$event->isSuccessful()) || $event->getErrorMessage() !== null) {
|
||||
throw new \OC\ForbiddenException($event->getErrorMessage());
|
||||
}
|
||||
|
||||
$streamer = new Streamer(\OC::$server->getRequest(), $fileSize, $numberOfFiles);
|
||||
OC_Util::obEnd();
|
||||
|
||||
|
|
@ -222,13 +232,16 @@ class OC_Files {
|
|||
self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
|
||||
OC::$server->getLogger()->logException($ex);
|
||||
$l = \OC::$server->getL10N('lib');
|
||||
\OC_Template::printErrorPage($l->t('Cannot read file'), $ex->getMessage(), 200);
|
||||
\OC_Template::printErrorPage($l->t('Cannot download file'), $ex->getMessage(), 200);
|
||||
} catch (\Exception $ex) {
|
||||
self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
|
||||
OC::$server->getLogger()->logException($ex);
|
||||
$l = \OC::$server->getL10N('lib');
|
||||
$hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
|
||||
\OC_Template::printErrorPage($l->t('Cannot read file'), $hint, 200);
|
||||
if ($event && $event->getErrorMessage() !== null) {
|
||||
$hint .= ' ' . $event->getErrorMessage();
|
||||
}
|
||||
\OC_Template::printErrorPage($l->t('Cannot download file'), $hint, 200);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,6 +300,7 @@ class OC_Files {
|
|||
* @param string $name
|
||||
* @param string $dir
|
||||
* @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header
|
||||
* @throws \OC\ForbiddenException
|
||||
*/
|
||||
private static function getSingleFile($view, $dir, $name, $params) {
|
||||
$filename = $dir . '/' . $name;
|
||||
|
|
@ -322,6 +336,19 @@ class OC_Files {
|
|||
$rangeArray = self::parseHttpRangeHeader(substr($params['range'], 6), $fileSize);
|
||||
}
|
||||
|
||||
$dispatcher = \OC::$server->query(IEventDispatcher::class);
|
||||
$event = new BeforeDirectFileDownloadEvent($filename);
|
||||
$dispatcher->dispatchTyped($event);
|
||||
|
||||
if (!\OC\Files\Filesystem::isReadable($filename) || $event->getErrorMessage()) {
|
||||
if ($event->getErrorMessage()) {
|
||||
$msg = $event->getErrorMessage();
|
||||
} else {
|
||||
$msg = 'Access denied';
|
||||
}
|
||||
throw new \OC\ForbiddenException($msg);
|
||||
}
|
||||
|
||||
self::sendHeaders($filename, $name, $rangeArray);
|
||||
|
||||
if (isset($params['head']) && $params['head']) {
|
||||
|
|
|
|||
84
lib/public/Files/Events/BeforeDirectFileDownloadEvent.php
Normal file
84
lib/public/Files/Events/BeforeDirectFileDownloadEvent.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCP\Files\Events;
|
||||
|
||||
use OCP\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* This event is triggered when a user tries to download a file
|
||||
* directly.
|
||||
*
|
||||
* @since 25.0.0
|
||||
*/
|
||||
class BeforeDirectFileDownloadEvent extends Event {
|
||||
private string $path;
|
||||
private bool $successful = true;
|
||||
private ?string $errorMessage = null;
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function __construct(string $path) {
|
||||
parent::__construct();
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function getPath(): string {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function isSuccessful(): bool {
|
||||
return $this->successful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the event was successful
|
||||
*
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function setSuccessful(bool $successful): void {
|
||||
$this->successful = $successful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error message, if any
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function getErrorMessage(): ?string {
|
||||
return $this->errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function setErrorMessage(string $errorMessage): void {
|
||||
$this->errorMessage = $errorMessage;
|
||||
}
|
||||
}
|
||||
91
lib/public/Files/Events/BeforeZipCreatedEvent.php
Normal file
91
lib/public/Files/Events/BeforeZipCreatedEvent.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Files\Events;
|
||||
|
||||
use OCP\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
class BeforeZipCreatedEvent extends Event {
|
||||
private string $directory;
|
||||
private array $files;
|
||||
private bool $successful = true;
|
||||
private ?string $errorMessage = null;
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function __construct(string $directory, array $files) {
|
||||
parent::__construct();
|
||||
$this->directory = $directory;
|
||||
$this->files = $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function getDirectory(): string {
|
||||
return $this->directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function getFiles(): array {
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function isSuccessful(): bool {
|
||||
return $this->successful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the event was successful
|
||||
*
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function setSuccessful(bool $successful): void {
|
||||
$this->successful = $successful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error message, if any
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function getErrorMessage(): ?string {
|
||||
return $this->errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function setErrorMessage(string $errorMessage): void {
|
||||
$this->errorMessage = $errorMessage;
|
||||
}
|
||||
}
|
||||
68
lib/public/Share/IAttributes.php
Normal file
68
lib/public/Share/IAttributes.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Piotr Mrowczynski <piotr@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2019, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCP\Share;
|
||||
|
||||
/**
|
||||
* Interface IAttributes
|
||||
*
|
||||
* @package OCP\Share
|
||||
* @since 25.0.0
|
||||
*/
|
||||
interface IAttributes {
|
||||
|
||||
/**
|
||||
* Sets an attribute enabled/disabled. If the key did not exist before it will be created.
|
||||
*
|
||||
* @param string $scope scope
|
||||
* @param string $key key
|
||||
* @param bool $enabled enabled
|
||||
* @return IAttributes The modified object
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function setAttribute($scope, $key, $enabled);
|
||||
|
||||
/**
|
||||
* Returns if attribute is enabled/disabled for given scope id and key.
|
||||
* If attribute does not exist, returns null
|
||||
*
|
||||
* @param string $scope scope
|
||||
* @param string $key key
|
||||
* @return bool|null
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function getAttribute($scope, $key);
|
||||
|
||||
/**
|
||||
* Formats the IAttributes object to array with the following format:
|
||||
* [
|
||||
* 0 => [
|
||||
* "scope" => <string>,
|
||||
* "key" => <string>,
|
||||
* "enabled" => <bool>
|
||||
* ],
|
||||
* ...
|
||||
* ]
|
||||
*
|
||||
* @return array formatted IAttributes
|
||||
* @since 25.0.0
|
||||
*/
|
||||
public function toArray();
|
||||
}
|
||||
|
|
@ -36,7 +36,9 @@ use OCP\Files\NotFoundException;
|
|||
use OCP\Share\Exceptions\IllegalIDChangeException;
|
||||
|
||||
/**
|
||||
* Interface IShare
|
||||
* This interface allows to represent a share object.
|
||||
*
|
||||
* This interface must not be implemented in your application.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*/
|
||||
|
|
@ -300,7 +302,7 @@ interface IShare {
|
|||
* See \OCP\Constants::PERMISSION_*
|
||||
*
|
||||
* @param int $permissions
|
||||
* @return \OCP\Share\IShare The modified object
|
||||
* @return IShare The modified object
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function setPermissions($permissions);
|
||||
|
|
@ -314,6 +316,31 @@ interface IShare {
|
|||
*/
|
||||
public function getPermissions();
|
||||
|
||||
/**
|
||||
* Create share attributes object
|
||||
*
|
||||
* @since 25.0.0
|
||||
* @return IAttributes
|
||||
*/
|
||||
public function newAttributes(): IAttributes;
|
||||
|
||||
/**
|
||||
* Set share attributes
|
||||
*
|
||||
* @param ?IAttributes $attributes
|
||||
* @since 25.0.0
|
||||
* @return IShare The modified object
|
||||
*/
|
||||
public function setAttributes(?IAttributes $attributes);
|
||||
|
||||
/**
|
||||
* Get share attributes
|
||||
*
|
||||
* @since 25.0.0
|
||||
* @return ?IAttributes
|
||||
*/
|
||||
public function getAttributes(): ?IAttributes;
|
||||
|
||||
/**
|
||||
* Set the accepted status
|
||||
* See self::STATUS_*
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
namespace Test\Share20;
|
||||
|
||||
use OC\Share20\DefaultShareProvider;
|
||||
use OC\Share20\ShareAttributes;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\Defaults;
|
||||
use OCP\Files\File;
|
||||
|
|
@ -703,6 +704,11 @@ class DefaultShareProviderTest extends \Test\TestCase {
|
|||
$share->setSharedWithDisplayName('Displayed Name');
|
||||
$share->setSharedWithAvatar('/path/to/image.svg');
|
||||
$share->setPermissions(1);
|
||||
|
||||
$attrs = new ShareAttributes();
|
||||
$attrs->setAttribute('permissions', 'download', true);
|
||||
$share->setAttributes($attrs);
|
||||
|
||||
$share->setTarget('/target');
|
||||
|
||||
$share2 = $this->provider->create($share);
|
||||
|
|
@ -723,6 +729,17 @@ class DefaultShareProviderTest extends \Test\TestCase {
|
|||
$this->assertSame('/path/to/image.svg', $share->getSharedWithAvatar());
|
||||
$this->assertSame(null, $share2->getSharedWithDisplayName());
|
||||
$this->assertSame(null, $share2->getSharedWithAvatar());
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
[
|
||||
'scope' => 'permissions',
|
||||
'key' => 'download',
|
||||
'enabled' => true
|
||||
]
|
||||
],
|
||||
$share->getAttributes()->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testCreateGroupShare() {
|
||||
|
|
@ -760,6 +777,9 @@ class DefaultShareProviderTest extends \Test\TestCase {
|
|||
$share->setSharedWithDisplayName('Displayed Name');
|
||||
$share->setSharedWithAvatar('/path/to/image.svg');
|
||||
$share->setTarget('/target');
|
||||
$attrs = new ShareAttributes();
|
||||
$attrs->setAttribute('permissions', 'download', true);
|
||||
$share->setAttributes($attrs);
|
||||
|
||||
$share2 = $this->provider->create($share);
|
||||
|
||||
|
|
@ -779,6 +799,17 @@ class DefaultShareProviderTest extends \Test\TestCase {
|
|||
$this->assertSame('/path/to/image.svg', $share->getSharedWithAvatar());
|
||||
$this->assertSame(null, $share2->getSharedWithDisplayName());
|
||||
$this->assertSame(null, $share2->getSharedWithAvatar());
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
[
|
||||
'scope' => 'permissions',
|
||||
'key' => 'download',
|
||||
'enabled' => true
|
||||
]
|
||||
],
|
||||
$share->getAttributes()->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function testCreateLinkShare() {
|
||||
|
|
|
|||
|
|
@ -593,7 +593,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
}
|
||||
|
||||
public function createShare($id, $type, $path, $sharedWith, $sharedBy, $shareOwner,
|
||||
$permissions, $expireDate = null, $password = null) {
|
||||
$permissions, $expireDate = null, $password = null, $attributes = null) {
|
||||
$share = $this->createMock(IShare::class);
|
||||
|
||||
$share->method('getShareType')->willReturn($type);
|
||||
|
|
@ -602,6 +602,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
$share->method('getShareOwner')->willReturn($shareOwner);
|
||||
$share->method('getNode')->willReturn($path);
|
||||
$share->method('getPermissions')->willReturn($permissions);
|
||||
$share->method('getAttributes')->willReturn($attributes);
|
||||
$share->method('getExpirationDate')->willReturn($expireDate);
|
||||
$share->method('getPassword')->willReturn($password);
|
||||
|
||||
|
|
@ -3039,6 +3040,8 @@ class ManagerTest extends \Test\TestCase {
|
|||
$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
|
||||
|
||||
$share = $this->manager->newShare();
|
||||
$attrs = $this->manager->newShare()->newAttributes();
|
||||
$attrs->setAttribute('app1', 'perm1', true);
|
||||
$share->setProviderId('foo')
|
||||
->setId('42')
|
||||
->setShareType(IShare::TYPE_USER);
|
||||
|
|
@ -3129,6 +3132,8 @@ class ManagerTest extends \Test\TestCase {
|
|||
$manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare);
|
||||
|
||||
$share = $this->manager->newShare();
|
||||
$attrs = $this->manager->newShare()->newAttributes();
|
||||
$attrs->setAttribute('app1', 'perm1', true);
|
||||
$share->setProviderId('foo')
|
||||
->setId('42')
|
||||
->setShareType(IShare::TYPE_USER)
|
||||
|
|
@ -3136,6 +3141,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
->setShareOwner('newUser')
|
||||
->setSharedBy('sharer')
|
||||
->setPermissions(31)
|
||||
->setAttributes($attrs)
|
||||
->setNode($node);
|
||||
|
||||
$this->defaultProvider->expects($this->once())
|
||||
|
|
@ -3160,6 +3166,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
'uidOwner' => 'sharer',
|
||||
'permissions' => 31,
|
||||
'path' => '/myPath',
|
||||
'attributes' => $attrs->toArray(),
|
||||
]);
|
||||
|
||||
$manager->updateShare($share);
|
||||
|
|
|
|||
Loading…
Reference in a new issue