From 76b04abf8d4b9a919ec44be8b760dd4ba5f66b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 26 Oct 2017 21:50:13 +0200 Subject: [PATCH 1/8] Add public-files to DAV including ACL handling --- apps/dav/lib/DAV/PublicAuth.php | 1 + .../lib/Files/PublicFiles/RootCollection.php | 88 ++++++++++++ apps/dav/lib/Files/PublicFiles/ShareNode.php | 55 ++++++++ apps/dav/lib/Files/PublicFiles/SharedFile.php | 104 ++++++++++++++ .../lib/Files/PublicFiles/SharedFolder.php | 128 ++++++++++++++++++ apps/dav/lib/RootCollection.php | 7 +- lib/private/Files/Node/Node.php | 4 + 7 files changed, 385 insertions(+), 2 deletions(-) create mode 100644 apps/dav/lib/Files/PublicFiles/RootCollection.php create mode 100644 apps/dav/lib/Files/PublicFiles/ShareNode.php create mode 100644 apps/dav/lib/Files/PublicFiles/SharedFile.php create mode 100644 apps/dav/lib/Files/PublicFiles/SharedFolder.php diff --git a/apps/dav/lib/DAV/PublicAuth.php b/apps/dav/lib/DAV/PublicAuth.php index 8b854691cc84..aaa5c6b7df6c 100644 --- a/apps/dav/lib/DAV/PublicAuth.php +++ b/apps/dav/lib/DAV/PublicAuth.php @@ -35,6 +35,7 @@ class PublicAuth implements BackendInterface { public function __construct() { $this->publicURLs = [ 'public-calendars', + 'public-files', 'principals/system/public' ]; } diff --git a/apps/dav/lib/Files/PublicFiles/RootCollection.php b/apps/dav/lib/Files/PublicFiles/RootCollection.php new file mode 100644 index 000000000000..e0f64d684cc7 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/RootCollection.php @@ -0,0 +1,88 @@ + + * + * @copyright Copyright (c) 2017, 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 + * + */ + +namespace OCA\DAV\Files\PublicFiles; + +use OC\Share\Constants; +use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IManager; +use OCP\Share\IShare; +use Sabre\DAV\Collection; +use Sabre\DAV\Exception\MethodNotAllowed; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\SimpleCollection; +use Sabre\DAV\SimpleFile; + +class RootCollection extends Collection { + + /** @var IManager */ + private $shareManager; + /** @var \OCP\IL10N */ + protected $l10n; + + /** + * If this value is set to true, it effectively disables listing of users + * it still allows user to find other users if they have an exact url. + * + * @var bool + */ + public $disableListing = false; + + function __construct() { + $this->l10n = \OC::$server->getL10N('dav'); + $this->shareManager = \OC::$server->getShareManager(); + } + + /** + * @inheritdoc + */ + function getName() { + return 'public-files'; + } + + /** + * @inheritdoc + */ + function getChild($name) { + try { + $share = $this->shareManager->getShareByToken($name); + $password = $share->getPassword(); + // TODO: check password + return new ShareNode($share); + } catch (ShareNotFound $ex) { + throw new NotFound(); + } + } + + /** + * @inheritdoc + */ + function getChildren() { + if ($this->disableListing) { + throw new MethodNotAllowed('Listing members of this collection is disabled'); + } + + $shares = $this->shareManager->getAllSharedWith(null, [Constants::SHARE_TYPE_LINK]); + return array_map(function(IShare $share) { + return new ShareNode($share); + }, $shares); + } +} diff --git a/apps/dav/lib/Files/PublicFiles/ShareNode.php b/apps/dav/lib/Files/PublicFiles/ShareNode.php new file mode 100644 index 000000000000..e3e2c695f77e --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/ShareNode.php @@ -0,0 +1,55 @@ +share = $share; + } + /** + * Returns an array with all the child nodes + * + * @return INode[] + */ + function getChildren() { + if ($this->share->getNodeType() === 'folder') { + $nodes = $this->share->getNode()->getDirectoryListing(); + } else { + $nodes = [$this->share->getNode()]; + } + return array_map(function(Node $node) { + if ($node->getType() === FileInfo::TYPE_FOLDER) { + return new SharedFolder($node, $this->share); + } + return new SharedFile($node, $this->share); + }, $nodes); + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + return $this->share->getToken(); + } +} \ No newline at end of file diff --git a/apps/dav/lib/Files/PublicFiles/SharedFile.php b/apps/dav/lib/Files/PublicFiles/SharedFile.php new file mode 100644 index 000000000000..73dad4f138d9 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/SharedFile.php @@ -0,0 +1,104 @@ + + * + * @copyright Copyright (c) 2017, 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 + * + */ + + +namespace OCA\DAV\Files\PublicFiles; + + +use OCP\Share\IShare; +use Sabre\DAV\File; +use Sabre\DAVACL\ACLTrait; +use Sabre\DAVACL\IACL; + +/** + * Class MetaFile + * This is a Sabre based implementation of a file living in the /meta resource. + * + * @package OCA\DAV\Meta + */ +class SharedFile extends File implements IACL { + + use ACLTrait; + + /** @var \OCP\Files\File */ + private $file; + + /** + * MetaFolder constructor. + * + * @param \OCP\Files\File $file + * @param IShare $share + */ + public function __construct(\OCP\Files\File $file, IShare $share) { + $this->file = $file; + } + + /** + * @inheritdoc + */ + function getName() { + return $this->file->getName(); + } + + public function getSize() { + return $this->file->getSize(); + } + + public function getContentType() { + return $this->file->getMimeType(); + } + + public function getETag() { + return $this->file->getETag(); + } + + function getLastModified() { + return $this->file->getMTime(); + } + + function delete() { + // TODO: check permissions - via ACL? + $this->file->delete(); + } + +// function setName($name) { +// $this->file->setName($name); +// } + + function getOwner() { + return ''; + } + + function getACL() { + return [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/system/public', + 'protected' => true, + ] + ]; + } +} diff --git a/apps/dav/lib/Files/PublicFiles/SharedFolder.php b/apps/dav/lib/Files/PublicFiles/SharedFolder.php new file mode 100644 index 000000000000..86480463cee6 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/SharedFolder.php @@ -0,0 +1,128 @@ + + * + * @copyright Copyright (c) 2017, 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 + * + */ + + +namespace OCA\DAV\Files\PublicFiles; + + +use OCP\Constants; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\Node; +use OCP\Share\IShare; +use Sabre\DAV\Collection; +use Sabre\DAVACL\ACLTrait; +use Sabre\DAVACL\IACL; + +/** + * Class MetaFolder + * This is a Sabre based implementation of a folder living in the /meta resource. + * + * @package OCA\DAV\Meta + */ +class SharedFolder extends Collection implements IACL { + use ACLTrait; + + /** @var Folder */ + private $folder; + /** @var IShare */ + private $share; + + /** + * MetaFolder constructor. + * + * @param Folder $folder + */ + public function __construct(Folder $folder, IShare $share) { + $this->folder = $folder; + $this->share = $share; + } + + /** + * @inheritdoc + */ + function getChildren() { + $nodes = $this->folder->getDirectoryListing(); + return array_map(function($node) { + return $this->nodeFactory($node); + }, $nodes); + } + + /** + * @inheritdoc + */ + function getName() { + return $this->folder->getName(); + } + + function getLastModified() { + return $this->folder->getMTime(); + } + + function createDirectory($name) { + $this->folder->newFolder($name); + } + + function createFile($name, $data = null) { + $file = $this->folder->newFile($name); + $file->putContent($data); + + } + + private function nodeFactory(Node $node) { + if ($node instanceof Folder) { + return new SharedFolder($node, $this->share); + } + if ($node instanceof File) { + return new SharedFile($node, $this->share); + } + throw new \InvalidArgumentException(); + } + + function getACL() { + $acl = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/system/public', + 'protected' => true, + ] + ]; + + // TODO: add more acl to convert the logic + if ($this->share->getPermissions() & Constants::PERMISSION_DELETE === Constants::PERMISSION_DELETE) { + $acl[]= [ + [ + 'privilege' => '{DAV:}unbind', + 'principal' => 'principals/system/public', + 'protected' => true, + ] + ]; + } + + return $acl; + } + +} diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index 4d10aaad038a..ac731a37ff77 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -65,6 +65,8 @@ public function __construct() { $calendarRoot->disableListing = $disableListing; $publicCalendarRoot = new PublicCalendarRoot($caldavBackend); $publicCalendarRoot->disableListing = $disableListing; + $publicFilesRoot = new Files\PublicFiles\RootCollection(); + $publicFilesRoot->disableListing = $disableListing; $systemTagCollection = new SystemTag\SystemTagsByIdCollection( \OC::$server->getSystemTagManager(), @@ -112,8 +114,9 @@ public function __construct() { $systemTagRelationsCollection, $uploadCollection, $avatarCollection, - new \OCA\DAV\Meta\RootCollection(\OC::$server->getLazyRootFolder()), - $queueCollection + new Meta\RootCollection(\OC::$server->getLazyRootFolder()), + $queueCollection, + $publicFilesRoot ]; parent::__construct('root', $children); diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index 8bb9a4b3b515..ecdf175a3284 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -424,4 +424,8 @@ public function move($targetPath) { throw new NotPermittedException('No permission to move to path ' . $targetPath); } } + + public function getView() { + return $this->view; + } } From 4c72bc4796c0cf1c844b36c5f4e01349e44f8f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 27 Oct 2017 09:13:26 +0200 Subject: [PATCH 2/8] Add password verification for public shares --- apps/dav/lib/DAV/PublicAuth.php | 4 - .../Files/PublicFiles/PublicSharingAuth.php | 115 ++++++++++++++++++ .../lib/Files/PublicFiles/RootCollection.php | 11 +- apps/dav/lib/Files/PublicFiles/ShareNode.php | 13 +- apps/dav/lib/Files/PublicFiles/SharedFile.php | 26 ++-- .../lib/Files/PublicFiles/SharedFolder.php | 20 +-- apps/dav/lib/Server.php | 4 + 7 files changed, 160 insertions(+), 33 deletions(-) create mode 100644 apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php diff --git a/apps/dav/lib/DAV/PublicAuth.php b/apps/dav/lib/DAV/PublicAuth.php index aaa5c6b7df6c..6ae6aeecab54 100644 --- a/apps/dav/lib/DAV/PublicAuth.php +++ b/apps/dav/lib/DAV/PublicAuth.php @@ -29,13 +29,9 @@ class PublicAuth implements BackendInterface { /** @var string[] */ private $publicURLs; - /** - * @param string[] $publicURLs - */ public function __construct() { $this->publicURLs = [ 'public-calendars', - 'public-files', 'principals/system/public' ]; } diff --git a/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php new file mode 100644 index 000000000000..6d7581d2a45d --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php @@ -0,0 +1,115 @@ + + * + * @copyright Copyright (c) 2017, 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 + * + */ +namespace OCA\DAV\Files\PublicFiles; + +use OCP\Share\IManager; +use OCP\Share\IShare; +use Sabre\DAV\Auth\Backend\AbstractBasic; +use Sabre\DAV\Server; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +class PublicSharingAuth extends AbstractBasic { + + /** @var Server */ + private $server; + /** @var IShare */ + private $share; + /** @var IManager */ + private $shareManager; + + /** + * PublicSharingAuth constructor. + * + * @param Server $server + */ + public function __construct(Server $server, IManager $manager) { + $this->server = $server; + $this->shareManager = $manager; + $this->principalPrefix = 'principals/system/'; + $this->setRealm('owncloud/share'); + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + public function check(RequestInterface $request, ResponseInterface $response) { + $node = $this->server->tree->getNodeForPath($request->getPath()); + if (!$node instanceof ShareNode && !$node instanceof SharedFile && !$node instanceof SharedFolder) { + return [true, "principals/system/public"]; + } + $this->share = $node->getShare(); + $password = $this->share->getPassword(); + if ($password === null) { + return [true, "principals/system/public"]; + } + + return parent::check($request, $response); + } + + /** + * @inheritdoc + */ + public function challenge(RequestInterface $request, ResponseInterface $response) { + } + + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $username + * @param string $password + * @return bool + */ + protected function validateUserPass($username, $password) { + if ($username !== 'public') { + return false; + } + return $this->shareManager->checkPassword($this->share, $password); + } +} diff --git a/apps/dav/lib/Files/PublicFiles/RootCollection.php b/apps/dav/lib/Files/PublicFiles/RootCollection.php index e0f64d684cc7..124e941de4f4 100644 --- a/apps/dav/lib/Files/PublicFiles/RootCollection.php +++ b/apps/dav/lib/Files/PublicFiles/RootCollection.php @@ -46,7 +46,7 @@ class RootCollection extends Collection { */ public $disableListing = false; - function __construct() { + public function __construct() { $this->l10n = \OC::$server->getL10N('dav'); $this->shareManager = \OC::$server->getShareManager(); } @@ -54,18 +54,17 @@ function __construct() { /** * @inheritdoc */ - function getName() { + public function getName() { return 'public-files'; } /** * @inheritdoc */ - function getChild($name) { + public function getChild($name) { try { $share = $this->shareManager->getShareByToken($name); $password = $share->getPassword(); - // TODO: check password return new ShareNode($share); } catch (ShareNotFound $ex) { throw new NotFound(); @@ -75,13 +74,13 @@ function getChild($name) { /** * @inheritdoc */ - function getChildren() { + public function getChildren() { if ($this->disableListing) { throw new MethodNotAllowed('Listing members of this collection is disabled'); } $shares = $this->shareManager->getAllSharedWith(null, [Constants::SHARE_TYPE_LINK]); - return array_map(function(IShare $share) { + return \array_map(function (IShare $share) { return new ShareNode($share); }, $shares); } diff --git a/apps/dav/lib/Files/PublicFiles/ShareNode.php b/apps/dav/lib/Files/PublicFiles/ShareNode.php index e3e2c695f77e..aa165a69cbd4 100644 --- a/apps/dav/lib/Files/PublicFiles/ShareNode.php +++ b/apps/dav/lib/Files/PublicFiles/ShareNode.php @@ -8,7 +8,6 @@ namespace OCA\DAV\Files\PublicFiles; - use OCP\Files\FileInfo; use OCP\Files\Node; use OCP\Share\IShare; @@ -28,13 +27,13 @@ public function __construct(IShare $share) { * * @return INode[] */ - function getChildren() { + public function getChildren() { if ($this->share->getNodeType() === 'folder') { $nodes = $this->share->getNode()->getDirectoryListing(); } else { $nodes = [$this->share->getNode()]; } - return array_map(function(Node $node) { + return \array_map(function (Node $node) { if ($node->getType() === FileInfo::TYPE_FOLDER) { return new SharedFolder($node, $this->share); } @@ -49,7 +48,11 @@ function getChildren() { * * @return string */ - function getName() { + public function getName() { return $this->share->getToken(); } -} \ No newline at end of file + + public function getShare() { + return $this->share; + } +} diff --git a/apps/dav/lib/Files/PublicFiles/SharedFile.php b/apps/dav/lib/Files/PublicFiles/SharedFile.php index 73dad4f138d9..5787975891f1 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFile.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFile.php @@ -19,10 +19,8 @@ * */ - namespace OCA\DAV\Files\PublicFiles; - use OCP\Share\IShare; use Sabre\DAV\File; use Sabre\DAVACL\ACLTrait; @@ -35,11 +33,14 @@ * @package OCA\DAV\Meta */ class SharedFile extends File implements IACL { - use ACLTrait; /** @var \OCP\Files\File */ private $file; + /** + * @var IShare + */ + private $share; /** * MetaFolder constructor. @@ -49,15 +50,20 @@ class SharedFile extends File implements IACL { */ public function __construct(\OCP\Files\File $file, IShare $share) { $this->file = $file; + $this->share = $share; } /** * @inheritdoc */ - function getName() { + public function getName() { return $this->file->getName(); } + public function get() { + return $this->file->fopen('r'); + } + public function getSize() { return $this->file->getSize(); } @@ -70,11 +76,11 @@ public function getETag() { return $this->file->getETag(); } - function getLastModified() { + public function getLastModified() { return $this->file->getMTime(); } - function delete() { + public function delete() { // TODO: check permissions - via ACL? $this->file->delete(); } @@ -83,11 +89,11 @@ function delete() { // $this->file->setName($name); // } - function getOwner() { + public function getOwner() { return ''; } - function getACL() { + public function getACL() { return [ [ 'privilege' => '{DAV:}all', @@ -101,4 +107,8 @@ function getACL() { ] ]; } + + public function getShare() { + return $this->share; + } } diff --git a/apps/dav/lib/Files/PublicFiles/SharedFolder.php b/apps/dav/lib/Files/PublicFiles/SharedFolder.php index 86480463cee6..8d8c4112efaa 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFolder.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFolder.php @@ -19,10 +19,8 @@ * */ - namespace OCA\DAV\Files\PublicFiles; - use OCP\Constants; use OCP\Files\File; use OCP\Files\Folder; @@ -59,9 +57,9 @@ public function __construct(Folder $folder, IShare $share) { /** * @inheritdoc */ - function getChildren() { + public function getChildren() { $nodes = $this->folder->getDirectoryListing(); - return array_map(function($node) { + return \array_map(function ($node) { return $this->nodeFactory($node); }, $nodes); } @@ -69,22 +67,21 @@ function getChildren() { /** * @inheritdoc */ - function getName() { + public function getName() { return $this->folder->getName(); } - function getLastModified() { + public function getLastModified() { return $this->folder->getMTime(); } - function createDirectory($name) { + public function createDirectory($name) { $this->folder->newFolder($name); } - function createFile($name, $data = null) { + public function createFile($name, $data = null) { $file = $this->folder->newFile($name); $file->putContent($data); - } private function nodeFactory(Node $node) { @@ -97,7 +94,7 @@ private function nodeFactory(Node $node) { throw new \InvalidArgumentException(); } - function getACL() { + public function getACL() { $acl = [ [ 'privilege' => '{DAV:}all', @@ -125,4 +122,7 @@ function getACL() { return $acl; } + public function getShare() { + return $this->share; + } } diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 01c4100eb73f..af34f573ce79 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -55,6 +55,7 @@ use OCA\DAV\Files\PreviewPlugin; use OCA\DAV\JobStatus\Entity\JobStatusMapper; use OCA\DAV\Meta\MetaPlugin; +use OCA\DAV\Files\PublicFiles\PublicSharingAuth; use OCA\DAV\SystemTag\SystemTagPlugin; use OCA\DAV\TrashBin\TrashBinPlugin; use OCA\DAV\Upload\ChunkingPlugin; @@ -120,6 +121,9 @@ public function __construct(IRequest $request, $baseUri) { $this->server->addPlugin(new BlockLegacyClientPlugin($config)); $this->server->addPlugin(new CorsPlugin(\OC::$server->getUserSession())); $authPlugin = new Plugin(); + if ($this->isRequestForSubtree(['public-files'])) { + $authPlugin->addBackend(new PublicSharingAuth($this->server, \OC::$server->getShareManager())); + } $authPlugin->addBackend(new PublicAuth()); $this->server->addPlugin($authPlugin); From e9c2fa6a3bcc4de58b0acc8fe858a427574e260b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 26 Jul 2019 15:08:23 +0200 Subject: [PATCH 3/8] Redirect public link to phoenix --- core/routes.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/routes.php b/core/routes.php index 97e109a8c69b..9429fb62e077 100644 --- a/core/routes.php +++ b/core/routes.php @@ -92,7 +92,13 @@ }); // Sharing routes -$this->create('files_sharing.sharecontroller.showShare', '/s/{token}')->action(function ($urlParams) { +$this->create('files_sharing.sharecontroller.showShare', '/s/{token}')->action(static function ($urlParams) { + $phoenixBaseUrl = \OC::$server->getConfig()->getSystemValue('phoenix.baseUrl', null); + if ($phoenixBaseUrl) { + $token = $urlParams['token']; + \OC_Response::redirect("$phoenixBaseUrl/index.html#/s/$token"); + return; + } $app = new \OCA\Files_Sharing\AppInfo\Application($urlParams); $app->dispatch('ShareController', 'showShare'); }); From 6a563c785f3478c97e75c75a8d34988a8833a321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 29 Jul 2019 18:40:15 +0200 Subject: [PATCH 4/8] Add some properties to public files resource --- .../Files/PublicFiles/IPublicSharedNode.php | 42 ++++++ .../Files/PublicFiles/PublicFilesPlugin.php | 102 +++++++++++++ apps/dav/lib/Files/PublicFiles/ShareNode.php | 19 +++ apps/dav/lib/Files/PublicFiles/SharedFile.php | 45 +++++- .../lib/Files/PublicFiles/SharedFolder.php | 36 ++++- apps/dav/lib/Server.php | 136 ++++++++++-------- .../PublicFiles/PublicFilesPluginTests.php | 68 +++++++++ 7 files changed, 378 insertions(+), 70 deletions(-) create mode 100644 apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php create mode 100644 apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php create mode 100644 apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTests.php diff --git a/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php b/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php new file mode 100644 index 000000000000..63ad2485fa96 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php @@ -0,0 +1,42 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\Files\PublicFiles; + +use OCP\Files\Node; +use OCP\Share\IShare; + +interface IPublicSharedNode { + /** + * @return IShare + */ + public function getShare(); + + /** + * @return Node + */ + public function getNode(); + + /** + * @return string + */ + public function getDavPermissions(); +} diff --git a/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php b/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php new file mode 100644 index 000000000000..11f9b7e29b31 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php @@ -0,0 +1,102 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\Files\PublicFiles; + +use OCA\DAV\Connector\Sabre\FilesPlugin; +use Sabre\DAV\INode; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\DAV\Xml\Property\GetLastModified; + +class PublicFilesPlugin extends ServerPlugin { + const PUBLIC_LINK_ITEM_TYPE = '{http://owncloud.org/ns}public-link-item-type'; + const PUBLIC_LINK_PERMISSION = '{http://owncloud.org/ns}public-link-permission'; + const PUBLIC_LINK_EXPIRATION = '{http://owncloud.org/ns}public-link-expiration'; + const PUBLIC_LINK_SHARE_DATETIME = '{http://owncloud.org/ns}public-link-share-datetime'; + const PUBLIC_LINK_SHARE_OWNER = '{http://owncloud.org/ns}public-link-share-owner'; + + /** @var Server */ + private $server; + + public function initialize(Server $server) { + $this->server = $server; + + $this->server->on('propFind', [$this, 'propFind']); + } + + public function propFind(PropFind $propFind, INode $node) { + // properties about the share + if ($node instanceof ShareNode) { + $propFind->handle(self::PUBLIC_LINK_ITEM_TYPE, static function () use ($node) { + return $node->getShare()->getNodeType(); + }); + + $propFind->handle(self::PUBLIC_LINK_PERMISSION, static function () use ($node) { + return $node->getShare()->getPermissions(); + }); + + $propFind->handle(self::PUBLIC_LINK_EXPIRATION, static function () use ($node) { + $expire = $node->getShare()->getExpirationDate(); + if ($expire) { + return new GetLastModified($expire); + } + return null; + }); + + $propFind->handle(self::PUBLIC_LINK_SHARE_DATETIME, static function () use ($node) { + return new GetLastModified($node->getShare()->getShareTime()); + }); + + $propFind->handle(self::PUBLIC_LINK_SHARE_OWNER, static function () use ($node) { + return $node->getShare()->getShareOwner(); + }); + + $propFind->handle(FilesPlugin::PERMISSIONS_PROPERTYNAME, static function () use ($node) { + return $node->getPermissions(); + }); + } + + // properties about the resources within the public link + if ($node instanceof IPublicSharedNode) { + $propFind->handle(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, static function () use ($node) { + return $node->getNode()->getId(); + }); + + $propFind->handle(FilesPlugin::PERMISSIONS_PROPERTYNAME, static function () use ($node) { + return $node->getDavPermissions(); + }); + + $propFind->handle(FilesPlugin::OWNER_ID_PROPERTYNAME, static function () use ($node) { + $owner = $node->getNode()->getOwner(); + return $owner->getUID(); + }); + $propFind->handle(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, static function () use ($node) { + $owner = $node->getNode()->getOwner(); + return $owner->getDisplayName(); + }); + $propFind->handle(FilesPlugin::SIZE_PROPERTYNAME, static function () use ($node) { + return $node->getNode()->getSize(); + }); + } + } +} diff --git a/apps/dav/lib/Files/PublicFiles/ShareNode.php b/apps/dav/lib/Files/PublicFiles/ShareNode.php index aa165a69cbd4..84ed6ae0939c 100644 --- a/apps/dav/lib/Files/PublicFiles/ShareNode.php +++ b/apps/dav/lib/Files/PublicFiles/ShareNode.php @@ -8,6 +8,7 @@ namespace OCA\DAV\Files\PublicFiles; +use OCP\Constants; use OCP\Files\FileInfo; use OCP\Files\Node; use OCP\Share\IShare; @@ -55,4 +56,22 @@ public function getName() { public function getShare() { return $this->share; } + + public function getPermissions() { + $p = ''; + if ($this->checkPermissions(Constants::PERMISSION_DELETE)) { + $p .= 'D'; + } + if ($this->checkPermissions(Constants::PERMISSION_UPDATE)) { + $p .= 'NV'; // Renameable, Moveable + } + if ($this->checkPermissions(Constants::PERMISSION_CREATE)) { + $p .= 'CK'; + } + return $p; + } + + protected function checkPermissions($permissions) { + return ($this->share->getPermissions() & $permissions) === $permissions; + } } diff --git a/apps/dav/lib/Files/PublicFiles/SharedFile.php b/apps/dav/lib/Files/PublicFiles/SharedFile.php index 5787975891f1..d52139ef4968 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFile.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFile.php @@ -2,7 +2,7 @@ /** * @author Thomas Müller * - * @copyright Copyright (c) 2017, ownCloud GmbH + * @copyright Copyright (c) 2019, ownCloud GmbH * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify @@ -21,6 +21,9 @@ namespace OCA\DAV\Files\PublicFiles; +use OCA\DAV\Files\IFileNode; +use OCP\Constants; +use OCP\Files\Node; use OCP\Share\IShare; use Sabre\DAV\File; use Sabre\DAVACL\ACLTrait; @@ -32,7 +35,7 @@ * * @package OCA\DAV\Meta */ -class SharedFile extends File implements IACL { +class SharedFile extends File implements IACL, IFileNode, IPublicSharedNode { use ACLTrait; /** @var \OCP\Files\File */ @@ -85,10 +88,6 @@ public function delete() { $this->file->delete(); } -// function setName($name) { -// $this->file->setName($name); -// } - public function getOwner() { return ''; } @@ -111,4 +110,38 @@ public function getACL() { public function getShare() { return $this->share; } + + /** + * @return Node + */ + public function getNode() { + return $this->file; + } + + /** + * @return string + */ + public function getDavPermissions() { + $node = $this->getNode(); + $p = ''; + if ($node->isDeletable() && $this->checkSharePermissions(Constants::PERMISSION_DELETE)) { + $p .= 'D'; + } + if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $p .= 'NV'; // Renameable, Moveable + } + if ($node->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $p .= 'W'; + } + } else { + if ($node->isCreatable() && $this->checkSharePermissions(Constants::PERMISSION_CREATE)) { + $p .= 'CK'; + } + } + return $p; + } + protected function checkSharePermissions($permissions) { + return ($this->share->getPermissions() & $permissions) === $permissions; + } } diff --git a/apps/dav/lib/Files/PublicFiles/SharedFolder.php b/apps/dav/lib/Files/PublicFiles/SharedFolder.php index 8d8c4112efaa..a0281b670aa1 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFolder.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFolder.php @@ -36,7 +36,7 @@ * * @package OCA\DAV\Meta */ -class SharedFolder extends Collection implements IACL { +class SharedFolder extends Collection implements IACL, IPublicSharedNode { use ACLTrait; /** @var Folder */ @@ -125,4 +125,38 @@ public function getACL() { public function getShare() { return $this->share; } + + /** + * @return Node + */ + public function getNode() { + return $this->folder; + } + + /** + * @return string + */ + public function getDavPermissions() { + $node = $this->getNode(); + $p = ''; + if ($node->isDeletable() && $this->checkSharePermissions(Constants::PERMISSION_DELETE)) { + $p .= 'D'; + } + if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $p .= 'NV'; // Renameable, Moveable + } + if ($node->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $p .= 'W'; + } + } else { + if ($node->isCreatable() && $this->checkSharePermissions(Constants::PERMISSION_CREATE)) { + $p .= 'CK'; + } + } + return $p; + } + protected function checkSharePermissions($permissions) { + return ($this->share->getPermissions() & $permissions) === $permissions; + } } diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index af34f573ce79..db762b73465a 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -26,8 +26,10 @@ */ namespace OCA\DAV; +use OC; use OC\Files\Filesystem; use OCA\DAV\AppInfo\PluginManager; +use OCA\DAV\CalDAV\Publishing\PublishPlugin; use OCA\DAV\CalDAV\Schedule\IMipPlugin; use OCA\DAV\CardDAV\ImageExportPlugin; use OCA\DAV\Connector\Sabre\Auth; @@ -37,8 +39,11 @@ use OCA\DAV\Connector\Sabre\CorsPlugin; use OCA\DAV\Connector\Sabre\DavAclPlugin; use OCA\DAV\Connector\Sabre\DummyGetResponsePlugin; +use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin; use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\DAV\Connector\Sabre\FilesReportPlugin; +use OCA\DAV\Connector\Sabre\FilesSearchReportPlugin; +use OCA\DAV\Connector\Sabre\LockPlugin; use OCA\DAV\Connector\Sabre\MaintenancePlugin; use OCA\DAV\Connector\Sabre\QuotaPlugin; use OCA\DAV\Connector\Sabre\SharesPlugin; @@ -53,16 +58,20 @@ use OCA\DAV\Files\BrowserErrorPagePlugin; use OCA\DAV\Files\FileLocksBackend; use OCA\DAV\Files\PreviewPlugin; +use OCA\DAV\Files\PublicFiles\PublicFilesPlugin; use OCA\DAV\JobStatus\Entity\JobStatusMapper; use OCA\DAV\Meta\MetaPlugin; use OCA\DAV\Files\PublicFiles\PublicSharingAuth; use OCA\DAV\SystemTag\SystemTagPlugin; use OCA\DAV\TrashBin\TrashBinPlugin; use OCA\DAV\Upload\ChunkingPlugin; +use OCP\AppFramework\QueryException; use OCP\IRequest; use OCP\SabrePluginEvent; +use Sabre\CalDAV\ICSExportPlugin; use Sabre\CardDAV\VCFExportPlugin; use Sabre\DAV\Auth\Plugin; +use Sabre\DAV\Exception; class Server { @@ -79,37 +88,37 @@ class Server { * * @param IRequest $request * @param string $baseUri - * @throws \OCP\AppFramework\QueryException - * @throws \Sabre\DAV\Exception + * @throws QueryException + * @throws Exception */ public function __construct(IRequest $request, $baseUri) { $this->request = $request; $this->baseUri = $baseUri; - $logger = \OC::$server->getLogger(); - $dispatcher = \OC::$server->getEventDispatcher(); + $logger = OC::$server->getLogger(); + $dispatcher = OC::$server->getEventDispatcher(); $root = new RootCollection(); - $tree = new \OCA\DAV\Tree($root); - $this->server = new \OCA\DAV\Connector\Sabre\Server($tree); + $tree = new Tree($root); + $this->server = new Connector\Sabre\Server($tree); - $config = \OC::$server->getConfig(); + $config = OC::$server->getConfig(); if ($config->getSystemValue('dav.enable.async', false)) { $this->server->addPlugin(new LazyOpsPlugin( - \OC::$server->getUserSession(), - \OC::$server->getURLGenerator(), - \OC::$server->getShutdownHandler(), - \OC::$server->query(JobStatusMapper::class), - \OC::$server->getLogger() + OC::$server->getUserSession(), + OC::$server->getURLGenerator(), + OC::$server->getShutdownHandler(), + OC::$server->query(JobStatusMapper::class), + OC::$server->getLogger() )); } // Backends $authBackend = new Auth( - \OC::$server->getSession(), - \OC::$server->getUserSession(), - \OC::$server->getRequest(), - \OC::$server->getTwoFactorAuthManager(), - \OC::$server->getAccountModuleManager() + OC::$server->getSession(), + OC::$server->getUserSession(), + OC::$server->getRequest(), + OC::$server->getTwoFactorAuthManager(), + OC::$server->getAccountModuleManager() ); // Set URL explicitly due to reverse-proxy situations @@ -119,10 +128,10 @@ public function __construct(IRequest $request, $baseUri) { $this->server->addPlugin(new MaintenancePlugin($config)); $this->server->addPlugin(new ValidateRequestPlugin('dav')); $this->server->addPlugin(new BlockLegacyClientPlugin($config)); - $this->server->addPlugin(new CorsPlugin(\OC::$server->getUserSession())); + $this->server->addPlugin(new CorsPlugin(OC::$server->getUserSession())); $authPlugin = new Plugin(); if ($this->isRequestForSubtree(['public-files'])) { - $authPlugin->addBackend(new PublicSharingAuth($this->server, \OC::$server->getShareManager())); + $authPlugin->addBackend(new PublicSharingAuth($this->server, OC::$server->getShareManager())); } $authPlugin->addBackend(new PublicAuth()); $this->server->addPlugin($authPlugin); @@ -135,16 +144,17 @@ public function __construct(IRequest $request, $baseUri) { $authPlugin->addBackend($authBackend); // debugging - if (\OC::$server->getConfig()->getSystemValue('debug', false)) { + if (OC::$server->getConfig()->getSystemValue('debug', false)) { $this->server->addPlugin(new \Sabre\DAV\Browser\Plugin()); } else { $this->server->addPlugin(new DummyGetResponsePlugin()); } - $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $logger)); - $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin()); + $this->server->addPlugin(new ExceptionLoggerPlugin('webdav', $logger)); + $this->server->addPlugin(new LockPlugin()); $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); - $this->server->addPlugin(new \Sabre\DAV\Locks\Plugin(new FileLocksBackend($this->server->tree, false, \OC::$server->getTimeFactory()))); + $this->server->addPlugin(new \Sabre\DAV\Locks\Plugin(new FileLocksBackend($this->server->tree, false, OC::$server->getTimeFactory()))); + $this->server->addPlugin(new PublicFilesPlugin()); // ACL plugin not used in files subtree, also it causes issues // with performance and locking issues because it will query @@ -162,67 +172,67 @@ public function __construct(IRequest $request, $baseUri) { // calendar plugins if ($this->isRequestForSubtree(['calendars', 'public-calendars', 'principals'])) { - $mailer = \OC::$server->getMailer(); - $this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin()); - $this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); - $this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin()); + $mailer = OC::$server->getMailer(); + $this->server->addPlugin(new CalDAV\Plugin()); + $this->server->addPlugin(new ICSExportPlugin()); + $this->server->addPlugin(new CalDAV\Schedule\Plugin()); $this->server->addPlugin(new IMipPlugin($mailer, $logger, $request)); $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); - $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); - $this->server->addPlugin(new \OCA\DAV\CalDAV\Publishing\PublishPlugin( - \OC::$server->getConfig(), - \OC::$server->getURLGenerator() + $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, OC::$server->getRequest())); + $this->server->addPlugin(new PublishPlugin( + OC::$server->getConfig(), + OC::$server->getURLGenerator() )); } // addressbook plugins if ($this->isRequestForSubtree(['addressbooks', 'principals'])) { - $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); - $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin()); + $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, OC::$server->getRequest())); + $this->server->addPlugin(new CardDAV\Plugin()); $this->server->addPlugin(new VCFExportPlugin()); - $this->server->addPlugin(new ImageExportPlugin(\OC::$server->getLogger())); + $this->server->addPlugin(new ImageExportPlugin(OC::$server->getLogger())); } // system tags plugins $this->server->addPlugin(new SystemTagPlugin( - \OC::$server->getSystemTagManager(), - \OC::$server->getGroupManager(), - \OC::$server->getUserSession() + OC::$server->getSystemTagManager(), + OC::$server->getGroupManager(), + OC::$server->getUserSession() )); $this->server->addPlugin(new CopyEtagHeaderPlugin()); $this->server->addPlugin(new ChunkingPlugin()); $this->server->addPlugin(new TrashBinPlugin()); $this->server->addPlugin(new MetaPlugin( - \OC::$server->getUserSession(), - \OC::$server->getLazyRootFolder() + OC::$server->getUserSession(), + OC::$server->getLazyRootFolder() )); // Allow view-only plugin for webdav requests $this->server->addPlugin(new ViewOnlyPlugin( - \OC::$server->getLogger() + OC::$server->getLogger() )); if (BrowserErrorPagePlugin::isBrowserRequest($request)) { $this->server->addPlugin(new BrowserErrorPagePlugin()); } - $this->server->addPlugin(new PreviewPlugin(\OC::$server->getTimeFactory(), \OC::$server->getPreviewManager())); + $this->server->addPlugin(new PreviewPlugin(OC::$server->getTimeFactory(), OC::$server->getPreviewManager())); // wait with registering these until auth is handled and the filesystem is setup $this->server->on('beforeMethod:*', function () use ($root) { // custom properties plugin must be the last one - $userSession = \OC::$server->getUserSession(); + $userSession = OC::$server->getUserSession(); $user = $userSession->getUser(); if ($user !== null) { $view = Filesystem::getView(); $this->server->addPlugin( new FilesPlugin( $this->server->tree, - \OC::$server->getConfig(), + OC::$server->getConfig(), $this->request, false, - !\OC::$server->getConfig()->getSystemValue('debug', false) + !OC::$server->getConfig()->getSystemValue('debug', false) ) ); @@ -231,9 +241,9 @@ public function __construct(IRequest $request, $baseUri) { $filePropertiesPlugin = new FileCustomPropertiesPlugin( new FileCustomPropertiesBackend( $this->server->tree, - \OC::$server->getDatabaseConnection(), - \OC::$server->getUserSession()->getUser(), - \OC::$server->getRootFolder() + OC::$server->getDatabaseConnection(), + OC::$server->getUserSession()->getUser(), + OC::$server->getRootFolder() ) ); $this->server->addPlugin($filePropertiesPlugin); @@ -241,9 +251,9 @@ public function __construct(IRequest $request, $baseUri) { $miscPropertiesPlugin = new \Sabre\DAV\PropertyStorage\Plugin( new MiscCustomPropertiesBackend( $this->server->tree, - \OC::$server->getDatabaseConnection(), - \OC::$server->getUserSession()->getUser(), - \OC::$server->getRootFolder() + OC::$server->getDatabaseConnection(), + OC::$server->getUserSession()->getUser(), + OC::$server->getRootFolder() ) ); $this->server->addPlugin($miscPropertiesPlugin); @@ -255,18 +265,18 @@ public function __construct(IRequest $request, $baseUri) { } $this->server->addPlugin( new TagsPlugin( - $this->server->tree, \OC::$server->getTagManager() + $this->server->tree, OC::$server->getTagManager() ) ); // TODO: switch to LazyUserFolder - $userFolder = \OC::$server->getUserFolder(); + $userFolder = OC::$server->getUserFolder(); $this->server->addPlugin(new SharesPlugin( $this->server->tree, $userSession, - \OC::$server->getShareManager() + OC::$server->getShareManager() )); $this->server->addPlugin(new CommentPropertiesPlugin( - \OC::$server->getCommentsManager(), + OC::$server->getCommentsManager(), $userSession )); @@ -274,25 +284,25 @@ public function __construct(IRequest $request, $baseUri) { $this->server->addPlugin(new FilesReportPlugin( $this->server->tree, $view, - \OC::$server->getSystemTagManager(), - \OC::$server->getSystemTagObjectMapper(), - \OC::$server->getTagManager(), + OC::$server->getSystemTagManager(), + OC::$server->getSystemTagObjectMapper(), + OC::$server->getTagManager(), $userSession, - \OC::$server->getGroupManager(), + OC::$server->getGroupManager(), $userFolder )); } $this->server->addPlugin( - new \OCA\DAV\Connector\Sabre\FilesSearchReportPlugin( - \OC::$server->getSearch() + new FilesSearchReportPlugin( + OC::$server->getSearch() ) ); } // register plugins from apps $pluginManager = new PluginManager( - \OC::$server, - \OC::$server->getAppManager() + OC::$server, + OC::$server->getAppManager() ); foreach ($pluginManager->getAppPlugins() as $appPlugin) { $this->server->addPlugin($appPlugin); @@ -304,7 +314,7 @@ public function __construct(IRequest $request, $baseUri) { } public function exec() { - $this->server->exec(); + $this->server->start(); } /** diff --git a/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTests.php b/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTests.php new file mode 100644 index 000000000000..96c0892a12c0 --- /dev/null +++ b/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTests.php @@ -0,0 +1,68 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\Tests\Unit\Files\PublicFiles; + +use OCA\DAV\Files\PublicFiles\PublicFilesPlugin; +use OCA\DAV\Files\PublicFiles\ShareNode; +use OCP\Share\IShare; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Sabre\DAV\Xml\Property\GetLastModified; +use Test\TestCase; + +class PublicFilesPluginTests extends TestCase { + public function testInit() { + $server = $this->createMock(Server::class); + $server->expects($this->once())->method('on')->with('propFind'); + + $plugin = new PublicFilesPlugin(); + $plugin->initialize($server); + } + + /** + * @dataProvider providesMethods + */ + public function testPropFindShareNode($expectedMethod, $expectedMethodReturn, $prop, $methodReturnValue = null) { + if ($methodReturnValue === null) { + $methodReturnValue = $expectedMethodReturn; + } + $node = $this->createMock(ShareNode::class); + $share = $this->createMock(IShare::class); + $node->method('getShare')->willReturn($share); + $share->expects(self::once())->method($expectedMethod)->willReturn($methodReturnValue); + $propFind = new PropFind('', [$prop]); + $plugin = new PublicFilesPlugin(); + $plugin->propFind($propFind, $node); + + self::assertEquals($expectedMethodReturn, $propFind->get($prop)); + } + + public function providesMethods() { + return [ + ['getShareOwner', 'alice', PublicFilesPlugin::PUBLIC_LINK_SHARE_OWNER], + ['getExpirationDate', new GetLastModified(123456), PublicFilesPlugin::PUBLIC_LINK_EXPIRATION, 123456], + ['getPermissions', 1, PublicFilesPlugin::PUBLIC_LINK_PERMISSION], + ['getNodeType', 'file', PublicFilesPlugin::PUBLIC_LINK_ITEM_TYPE], + ['getShareTime', new GetLastModified(123456), PublicFilesPlugin::PUBLIC_LINK_SHARE_DATETIME, 123456], + ]; + } +} From 19510d0c4cc3aeb3c86f0829aaf45ec6a8591a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 5 Aug 2019 10:22:27 +0200 Subject: [PATCH 5/8] Allow write operations on public-files --- .../Files/PublicFiles/PublicSharingAuth.php | 25 +++++++++-- apps/dav/lib/Files/PublicFiles/ShareNode.php | 44 +++++++++++++++++++ apps/dav/lib/Files/PublicFiles/SharedFile.php | 39 ++++++++++++++-- .../lib/Files/PublicFiles/SharedFolder.php | 43 +++++++++++++++--- lib/private/Share/Constants.php | 2 +- lib/public/Files/Node.php | 1 + 6 files changed, 141 insertions(+), 13 deletions(-) diff --git a/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php index 6d7581d2a45d..debfe71acfb7 100644 --- a/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php +++ b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php @@ -23,9 +23,12 @@ use OCP\Share\IManager; use OCP\Share\IShare; use Sabre\DAV\Auth\Backend\AbstractBasic; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\INode; use Sabre\DAV\Server; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; +use function explode; class PublicSharingAuth extends AbstractBasic { @@ -40,6 +43,7 @@ class PublicSharingAuth extends AbstractBasic { * PublicSharingAuth constructor. * * @param Server $server + * @param IManager $manager */ public function __construct(Server $server, IManager $manager) { $this->server = $server; @@ -75,16 +79,17 @@ public function __construct(Server $server, IManager $manager) { * @param RequestInterface $request * @param ResponseInterface $response * @return array + * @throws NotFound */ public function check(RequestInterface $request, ResponseInterface $response) { - $node = $this->server->tree->getNodeForPath($request->getPath()); + $node = $this->resolveShare($request->getPath()); if (!$node instanceof ShareNode && !$node instanceof SharedFile && !$node instanceof SharedFolder) { - return [true, "principals/system/public"]; + return [true, 'principals/system/public']; } $this->share = $node->getShare(); $password = $this->share->getPassword(); if ($password === null) { - return [true, "principals/system/public"]; + return [true, 'principals/system/public']; } return parent::check($request, $response); @@ -112,4 +117,18 @@ protected function validateUserPass($username, $password) { } return $this->shareManager->checkPassword($this->share, $password); } + + /** + * @param string $path + * @return INode + * @throws NotFound + */ + private function resolveShare($path) { + $elements = \explode('/', $path); + if ($elements[0] !== 'public-files') { + return null; + } + + return $this->server->tree->getNodeForPath($elements[0] .'/' . $elements[1]); + } } diff --git a/apps/dav/lib/Files/PublicFiles/ShareNode.php b/apps/dav/lib/Files/PublicFiles/ShareNode.php index 84ed6ae0939c..a8202aa4118c 100644 --- a/apps/dav/lib/Files/PublicFiles/ShareNode.php +++ b/apps/dav/lib/Files/PublicFiles/ShareNode.php @@ -11,8 +11,11 @@ use OCP\Constants; use OCP\Files\FileInfo; use OCP\Files\Node; +use OCP\Files\NotPermittedException; use OCP\Share\IShare; use Sabre\DAV\Collection; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\INode; class ShareNode extends Collection { @@ -42,6 +45,47 @@ public function getChildren() { }, $nodes); } + public function createDirectory($name) { + if (!$this->checkPermissions(Constants::PERMISSION_CREATE)) { + throw new Forbidden('Permission denied to create directory'); + } + if ($this->share->getNodeType() !== 'folder') { + throw new Forbidden('Permission denied to create directory'); + } + try { + $this->share->getNode()->newFolder($name); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to create directory'); + } + } + + public function createFile($name, $data = null) { + if (!$this->checkPermissions(Constants::PERMISSION_CREATE)) { + throw new Forbidden('Permission denied to create directory'); + } + if ($this->share->getNodeType() !== 'folder') { + throw new Forbidden('Permission denied to create directory'); + } + try { + $file = $this->share->getNode()->newFile($name); + $file->putContent(data); + return $file->getEtag(); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to create directory'); + } + } + + public function delete() { + if (!$this->checkPermissions(Constants::PERMISSION_DELETE)) { + throw new Forbidden('Permission denied to delete a resource'); + } + try { + $this->share->getNode()->delete(); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to create directory'); + } + } + /** * Returns the name of the node. * diff --git a/apps/dav/lib/Files/PublicFiles/SharedFile.php b/apps/dav/lib/Files/PublicFiles/SharedFile.php index d52139ef4968..b78781efe2d7 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFile.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFile.php @@ -24,7 +24,9 @@ use OCA\DAV\Files\IFileNode; use OCP\Constants; use OCP\Files\Node; +use OCP\Files\NotPermittedException; use OCP\Share\IShare; +use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\File; use Sabre\DAVACL\ACLTrait; use Sabre\DAVACL\IACL; @@ -84,8 +86,29 @@ public function getLastModified() { } public function delete() { - // TODO: check permissions - via ACL? - $this->file->delete(); + try { + $this->file->delete(); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to create directory'); + } + } + + public function put($data) { + try { + $this->file->putContent($data); + return $this->file->getEtag(); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to create directory'); + } + } + + public function setName($name) { + try { + $newPath = $this->file->getParent()->getPath() . '/' . $name; + $this->file->move($newPath); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to rename file'); + } } public function getOwner() { @@ -93,7 +116,7 @@ public function getOwner() { } public function getACL() { - return [ + $acl = [ [ 'privilege' => '{DAV:}all', 'principal' => '{DAV:}owner', @@ -105,6 +128,16 @@ public function getACL() { 'protected' => true, ] ]; + if ($this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $acl[] = + [ + 'privilege' => '{DAV:}write-content', + 'principal' => 'principals/system/public', + 'protected' => true, + ]; + } + + return $acl; } public function getShare() { diff --git a/apps/dav/lib/Files/PublicFiles/SharedFolder.php b/apps/dav/lib/Files/PublicFiles/SharedFolder.php index a0281b670aa1..f8c8ba43b8e8 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFolder.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFolder.php @@ -25,8 +25,10 @@ use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\Node; +use OCP\Files\NotPermittedException; use OCP\Share\IShare; use Sabre\DAV\Collection; +use Sabre\DAV\Exception\Forbidden; use Sabre\DAVACL\ACLTrait; use Sabre\DAVACL\IACL; @@ -48,6 +50,7 @@ class SharedFolder extends Collection implements IACL, IPublicSharedNode { * MetaFolder constructor. * * @param Folder $folder + * @param IShare $share */ public function __construct(Folder $folder, IShare $share) { $this->folder = $folder; @@ -76,7 +79,11 @@ public function getLastModified() { } public function createDirectory($name) { - $this->folder->newFolder($name); + try { + $this->folder->newFolder($name); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to create directory'); + } } public function createFile($name, $data = null) { @@ -84,6 +91,23 @@ public function createFile($name, $data = null) { $file->putContent($data); } + public function delete() { + try { + $this->folder->delete(); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to delete node'); + } + } + + public function setName($name) { + try { + $newPath = $this->folder->getParent()->getPath() . '/' . $name; + $this->folder->move($newPath); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to rename file'); + } + } + private function nodeFactory(Node $node) { if ($node instanceof Folder) { return new SharedFolder($node, $this->share); @@ -108,15 +132,21 @@ public function getACL() { ] ]; - // TODO: add more acl to convert the logic - if ($this->share->getPermissions() & Constants::PERMISSION_DELETE === Constants::PERMISSION_DELETE) { - $acl[]= [ + if ($this->checkSharePermissions(Constants::PERMISSION_DELETE)) { + $acl[] = [ 'privilege' => '{DAV:}unbind', 'principal' => 'principals/system/public', 'protected' => true, - ] - ]; + ]; + } + if ($this->checkSharePermissions(Constants::PERMISSION_CREATE)) { + $acl[] = + [ + 'privilege' => '{DAV:}bind', + 'principal' => 'principals/system/public', + 'protected' => true, + ]; } return $acl; @@ -156,6 +186,7 @@ public function getDavPermissions() { } return $p; } + protected function checkSharePermissions($permissions) { return ($this->share->getPermissions() & $permissions) === $permissions; } diff --git a/lib/private/Share/Constants.php b/lib/private/Share/Constants.php index 75007efcda63..aa99210a7147 100644 --- a/lib/private/Share/Constants.php +++ b/lib/private/Share/Constants.php @@ -43,7 +43,7 @@ class Constants { const FORMAT_STATUSES = -2; const FORMAT_SOURCES = -3; // ToDo Check if it is still in use otherwise remove it - const RESPONSE_FORMAT = 'json'; // default resonse format for ocs calls + const RESPONSE_FORMAT = 'json'; // default response format for ocs calls const TOKEN_LENGTH = 15; // old (oc7) length is 32, keep token length in db at least that for compatibility diff --git a/lib/public/Files/Node.php b/lib/public/Files/Node.php index c391095be404..b69a5c90f76c 100644 --- a/lib/public/Files/Node.php +++ b/lib/public/Files/Node.php @@ -52,6 +52,7 @@ public function move($targetPath); /** * Delete the file or folder * @return void + * @throws NotPermittedException * @since 6.0.0 */ public function delete(); From 923e772925a18b5d22fe5fcd219fddf76a874833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 6 Aug 2019 16:22:37 +0200 Subject: [PATCH 6/8] Add PublicLinkEventsPlugin for auditing public webdav operations --- apps/dav/lib/Server.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index db762b73465a..31e791273e79 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -59,6 +59,7 @@ use OCA\DAV\Files\FileLocksBackend; use OCA\DAV\Files\PreviewPlugin; use OCA\DAV\Files\PublicFiles\PublicFilesPlugin; +use OCA\DAV\Files\Sharing\PublicLinkEventsPlugin; use OCA\DAV\JobStatus\Entity\JobStatusMapper; use OCA\DAV\Meta\MetaPlugin; use OCA\DAV\Files\PublicFiles\PublicSharingAuth; @@ -132,6 +133,7 @@ public function __construct(IRequest $request, $baseUri) { $authPlugin = new Plugin(); if ($this->isRequestForSubtree(['public-files'])) { $authPlugin->addBackend(new PublicSharingAuth($this->server, OC::$server->getShareManager())); + $this->server->addPlugin(new PublicLinkEventsPlugin(\OC::$server->getEventDispatcher())); } $authPlugin->addBackend(new PublicAuth()); $this->server->addPlugin($authPlugin); @@ -323,7 +325,7 @@ public function exec() { */ private function isRequestForSubtree(array $subTrees) { foreach ($subTrees as $subTree) { - $subTree = \trim($subTree, " /"); + $subTree = \trim($subTree, ' /'); if (\strpos($this->server->getRequestUri(), "$subTree/") === 0) { return true; } From 9a9e0b6d75cddce6f035895573b8e4df72493628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 6 Aug 2019 17:34:20 +0200 Subject: [PATCH 7/8] Add more unit tests --- .../Files/PublicFiles/IPublicSharedNode.php | 6 ++ .../Files/PublicFiles/PublicFilesPlugin.php | 6 ++ .../Files/PublicFiles/PublicSharingAuth.php | 7 ++ .../lib/Files/PublicFiles/RootCollection.php | 10 +- apps/dav/lib/Files/PublicFiles/ShareNode.php | 38 +++++-- apps/dav/lib/Files/PublicFiles/SharedFile.php | 15 ++- .../lib/Files/PublicFiles/SharedFolder.php | 9 +- ...ginTests.php => PublicFilesPluginTest.php} | 2 +- .../PublicFiles/PublicSharingAuthTest.php | 99 +++++++++++++++++++ 9 files changed, 173 insertions(+), 19 deletions(-) rename apps/dav/tests/unit/Files/PublicFiles/{PublicFilesPluginTests.php => PublicFilesPluginTest.php} (98%) create mode 100644 apps/dav/tests/unit/Files/PublicFiles/PublicSharingAuthTest.php diff --git a/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php b/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php index 63ad2485fa96..4bd32313812f 100644 --- a/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php +++ b/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php @@ -24,6 +24,12 @@ use OCP\Files\Node; use OCP\Share\IShare; +/** + * Interface IPublicSharedNode - common interface of all files and folders + * in a shared node + * + * @package OCA\DAV\Files\PublicFiles + */ interface IPublicSharedNode { /** * @return IShare diff --git a/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php b/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php index 11f9b7e29b31..a06dfa5d74eb 100644 --- a/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php +++ b/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php @@ -28,6 +28,12 @@ use Sabre\DAV\ServerPlugin; use Sabre\DAV\Xml\Property\GetLastModified; +/** + * Class PublicFilesPlugin - additional PROPFIND properties for public shared + * files and folders are handled with this plugin + * + * @package OCA\DAV\Files\PublicFiles + */ class PublicFilesPlugin extends ServerPlugin { const PUBLIC_LINK_ITEM_TYPE = '{http://owncloud.org/ns}public-link-item-type'; const PUBLIC_LINK_PERMISSION = '{http://owncloud.org/ns}public-link-permission'; diff --git a/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php index debfe71acfb7..ac768de366c0 100644 --- a/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php +++ b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php @@ -30,6 +30,12 @@ use Sabre\HTTP\ResponseInterface; use function explode; +/** + * Class PublicSharingAuth - sabre dav auth backend to handle password for + * public shared files and folders + * + * @package OCA\DAV\Files\PublicFiles + */ class PublicSharingAuth extends AbstractBasic { /** @var Server */ @@ -99,6 +105,7 @@ public function check(RequestInterface $request, ResponseInterface $response) { * @inheritdoc */ public function challenge(RequestInterface $request, ResponseInterface $response) { + // intentionally left empty - no need to challenge the user here } /** diff --git a/apps/dav/lib/Files/PublicFiles/RootCollection.php b/apps/dav/lib/Files/PublicFiles/RootCollection.php index 124e941de4f4..0a86577f289f 100644 --- a/apps/dav/lib/Files/PublicFiles/RootCollection.php +++ b/apps/dav/lib/Files/PublicFiles/RootCollection.php @@ -31,6 +31,13 @@ use Sabre\DAV\SimpleCollection; use Sabre\DAV\SimpleFile; +/** + * Class RootCollection - represents the list of public shares. + * Only in debug mode all shares will be listed. + * In production enumerating public shares is considered an information disclosure. + * + * @package OCA\DAV\Files\PublicFiles + */ class RootCollection extends Collection { /** @var IManager */ @@ -64,7 +71,6 @@ public function getName() { public function getChild($name) { try { $share = $this->shareManager->getShareByToken($name); - $password = $share->getPassword(); return new ShareNode($share); } catch (ShareNotFound $ex) { throw new NotFound(); @@ -80,7 +86,7 @@ public function getChildren() { } $shares = $this->shareManager->getAllSharedWith(null, [Constants::SHARE_TYPE_LINK]); - return \array_map(function (IShare $share) { + return \array_map(static function (IShare $share) { return new ShareNode($share); }, $shares); } diff --git a/apps/dav/lib/Files/PublicFiles/ShareNode.php b/apps/dav/lib/Files/PublicFiles/ShareNode.php index a8202aa4118c..bdb4c0d87857 100644 --- a/apps/dav/lib/Files/PublicFiles/ShareNode.php +++ b/apps/dav/lib/Files/PublicFiles/ShareNode.php @@ -1,9 +1,22 @@ + * + * @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 + * */ namespace OCA\DAV\Files\PublicFiles; @@ -15,14 +28,23 @@ use OCP\Share\IShare; use Sabre\DAV\Collection; use Sabre\DAV\Exception\Forbidden; -use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\INode; +/** + * Class ShareNode - root node of a public share + * + * @package OCA\DAV\Files\PublicFiles + */ class ShareNode extends Collection { /** @var IShare */ private $share; + /** + * ShareNode constructor. + * + * @param IShare $share + */ public function __construct(IShare $share) { $this->share = $share; } @@ -61,17 +83,17 @@ public function createDirectory($name) { public function createFile($name, $data = null) { if (!$this->checkPermissions(Constants::PERMISSION_CREATE)) { - throw new Forbidden('Permission denied to create directory'); + throw new Forbidden('Permission denied to create file'); } if ($this->share->getNodeType() !== 'folder') { - throw new Forbidden('Permission denied to create directory'); + throw new Forbidden('Permission denied to create file'); } try { $file = $this->share->getNode()->newFile($name); $file->putContent(data); return $file->getEtag(); } catch (NotPermittedException $ex) { - throw new Forbidden('Permission denied to create directory'); + throw new Forbidden('Permission denied to create file'); } } diff --git a/apps/dav/lib/Files/PublicFiles/SharedFile.php b/apps/dav/lib/Files/PublicFiles/SharedFile.php index b78781efe2d7..05a6ca3d0c18 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFile.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFile.php @@ -23,7 +23,9 @@ use OCA\DAV\Files\IFileNode; use OCP\Constants; +use OCP\Files\InvalidPathException; use OCP\Files\Node; +use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\Share\IShare; use Sabre\DAV\Exception\Forbidden; @@ -32,10 +34,9 @@ use Sabre\DAVACL\IACL; /** - * Class MetaFile - * This is a Sabre based implementation of a file living in the /meta resource. + * Class SharedFile - represents a file living in a public shared folder * - * @package OCA\DAV\Meta + * @package OCA\DAV\Files\PublicFiles */ class SharedFile extends File implements IACL, IFileNode, IPublicSharedNode { use ACLTrait; @@ -66,7 +67,11 @@ public function getName() { } public function get() { - return $this->file->fopen('r'); + try { + return $this->file->fopen('r'); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to read this file'); + } } public function getSize() { @@ -153,6 +158,8 @@ public function getNode() { /** * @return string + * @throws InvalidPathException + * @throws NotFoundException */ public function getDavPermissions() { $node = $this->getNode(); diff --git a/apps/dav/lib/Files/PublicFiles/SharedFolder.php b/apps/dav/lib/Files/PublicFiles/SharedFolder.php index f8c8ba43b8e8..4eaae557633e 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFolder.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFolder.php @@ -2,7 +2,7 @@ /** * @author Thomas Müller * - * @copyright Copyright (c) 2017, ownCloud GmbH + * @copyright Copyright (c) 2019, ownCloud GmbH * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify @@ -33,10 +33,9 @@ use Sabre\DAVACL\IACL; /** - * Class MetaFolder - * This is a Sabre based implementation of a folder living in the /meta resource. + * Class SharedFolder - represents a folder living in a public shared folder * - * @package OCA\DAV\Meta + * @package OCA\DAV\Files\PublicFiles */ class SharedFolder extends Collection implements IACL, IPublicSharedNode { use ACLTrait; @@ -165,6 +164,8 @@ public function getNode() { /** * @return string + * @throws \OCP\Files\InvalidPathException + * @throws \OCP\Files\NotFoundException */ public function getDavPermissions() { $node = $this->getNode(); diff --git a/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTests.php b/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.php similarity index 98% rename from apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTests.php rename to apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.php index 96c0892a12c0..9e498585c054 100644 --- a/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTests.php +++ b/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.php @@ -29,7 +29,7 @@ use Sabre\DAV\Xml\Property\GetLastModified; use Test\TestCase; -class PublicFilesPluginTests extends TestCase { +class PublicFilesPluginTest extends TestCase { public function testInit() { $server = $this->createMock(Server::class); $server->expects($this->once())->method('on')->with('propFind'); diff --git a/apps/dav/tests/unit/Files/PublicFiles/PublicSharingAuthTest.php b/apps/dav/tests/unit/Files/PublicFiles/PublicSharingAuthTest.php new file mode 100644 index 000000000000..71c7fda35a52 --- /dev/null +++ b/apps/dav/tests/unit/Files/PublicFiles/PublicSharingAuthTest.php @@ -0,0 +1,99 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\Tests\Unit\Files\PublicFiles; + +use OCA\DAV\Files\PublicFiles\PublicFilesPlugin; +use OCA\DAV\Files\PublicFiles\PublicSharingAuth; +use OCA\DAV\Files\PublicFiles\ShareNode; +use OCP\Share\IManager; +use OCP\Share\IShare; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Sabre\DAV\Tree; +use Sabre\DAV\Xml\Property\GetLastModified; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Test\TestCase; + +class PublicSharingAuthTest extends TestCase { + /** + * @dataProvider providesCheckData + */ + public function testCheck($expectedResult, $shareNode, $authHeader = null) { + $tree = $this->createMock(Tree::class); + $server = $this->createMock(Server::class); + $server->tree = $tree; + $manager = $this->createMock(IManager::class); + $manager->method('checkPassword')->willReturnCallback(static function ($share, $password) { + return $share->getPassword() === $password; + }); + + $tree->method('getNodeForPath')->willReturn($shareNode); + $auth = new PublicSharingAuth($server, $manager); + + $req = $this->createMock(RequestInterface::class); + $req->method('getPath')->willReturn('public-files/123456'); + if ($auth) { + $req->method('getHeader')->willReturnCallback(static function ($key) use ($authHeader) { + if ($key === 'Authorization') { + return $authHeader; + } + return null; + }); + } + $resp = $this->createMock(ResponseInterface::class); + $result = $auth->check($req, $resp); + + self::assertEquals($expectedResult, $result); + } + + public function providesCheckData() { + $validResult = [true, 'principals/system/public']; + $authHeaderMissing = [false, 'No \'Authorization: Basic\' header found. Either the client didn\'t send one, or the server is misconfigured']; + $wrongUserOrPassword = [false, 'Username or password was incorrect']; + $shareWithoutPassword = $this->createShareNode(); + $shareWithPassword = $this->createShareNode('123456'); + + return [ + 'not a share node' => [$validResult, null], + 'no password' => [$validResult, $shareWithoutPassword], + 'with password - but no auth header' => [$authHeaderMissing, $shareWithPassword], + 'with password - and valid auth header' => [$validResult, $shareWithPassword, 'Basic cHVibGljOjEyMzQ1Ng=='], + 'with password - and wrong password in auth header' => [$wrongUserOrPassword, $shareWithPassword, 'Basic cHViaWM6MTIzNDU2'], + 'with password - and invalid auth header' => [$authHeaderMissing, $shareWithPassword, 'Basic 1111111'], + ]; + } + + /** + * @return MockObject + */ + private function createShareNode($password = null) { + $shareWithoutPassword = $this->createMock(ShareNode::class); + $share = $this->createMock(IShare::class); + if ($password) { + $share->method('getPassword')->willReturn($password); + } + $shareWithoutPassword->method('getShare')->willReturn($share); + return $shareWithoutPassword; + } +} From 5ecf09935e3ea996bdc6709e2ec37d11ed1724cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 8 Aug 2019 16:45:52 +0200 Subject: [PATCH 8/8] [To-Be-Squashed] Cleanup codebase --- .../Files/PublicFiles/NodeFactoryTrait.php | 44 ++++ .../Files/PublicFiles/PublicFilesPlugin.php | 2 +- ...ShareNode.php => PublicSharedRootNode.php} | 27 ++- .../Files/PublicFiles/PublicSharingAuth.php | 4 +- .../lib/Files/PublicFiles/RootCollection.php | 4 +- apps/dav/lib/Files/PublicFiles/SharedFile.php | 119 +--------- .../lib/Files/PublicFiles/SharedFolder.php | 91 +------- .../lib/Files/PublicFiles/SharedNodeTrait.php | 220 ++++++++++++++++++ .../PublicFiles/PublicFilesPluginTest.php | 6 +- .../PublicFiles/PublicSharingAuthTest.php | 10 +- 10 files changed, 303 insertions(+), 224 deletions(-) create mode 100644 apps/dav/lib/Files/PublicFiles/NodeFactoryTrait.php rename apps/dav/lib/Files/PublicFiles/{ShareNode.php => PublicSharedRootNode.php} (82%) create mode 100644 apps/dav/lib/Files/PublicFiles/SharedNodeTrait.php diff --git a/apps/dav/lib/Files/PublicFiles/NodeFactoryTrait.php b/apps/dav/lib/Files/PublicFiles/NodeFactoryTrait.php new file mode 100644 index 000000000000..bdaceb7944e7 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/NodeFactoryTrait.php @@ -0,0 +1,44 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\Files\PublicFiles; + +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\Node; +use OCP\Share\IShare; + +/** + * Trait NodeFactoryTrait + * + * @package OCA\DAV\Files\PublicFiles + */ +trait NodeFactoryTrait { + private function nodeFactory(Node $node, IShare $share) { + if ($node instanceof Folder) { + return new SharedFolder($node, $share); + } + if ($node instanceof File) { + return new SharedFile($node, $share); + } + throw new \InvalidArgumentException(); + } +} diff --git a/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php b/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php index a06dfa5d74eb..f4a98e7c3c7b 100644 --- a/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php +++ b/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php @@ -52,7 +52,7 @@ public function initialize(Server $server) { public function propFind(PropFind $propFind, INode $node) { // properties about the share - if ($node instanceof ShareNode) { + if ($node instanceof PublicSharedRootNode) { $propFind->handle(self::PUBLIC_LINK_ITEM_TYPE, static function () use ($node) { return $node->getShare()->getNodeType(); }); diff --git a/apps/dav/lib/Files/PublicFiles/ShareNode.php b/apps/dav/lib/Files/PublicFiles/PublicSharedRootNode.php similarity index 82% rename from apps/dav/lib/Files/PublicFiles/ShareNode.php rename to apps/dav/lib/Files/PublicFiles/PublicSharedRootNode.php index bdb4c0d87857..6ba63c86d714 100644 --- a/apps/dav/lib/Files/PublicFiles/ShareNode.php +++ b/apps/dav/lib/Files/PublicFiles/PublicSharedRootNode.php @@ -23,7 +23,9 @@ use OCP\Constants; use OCP\Files\FileInfo; +use OCP\Files\InvalidPathException; use OCP\Files\Node; +use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\Share\IShare; use Sabre\DAV\Collection; @@ -31,17 +33,18 @@ use Sabre\DAV\INode; /** - * Class ShareNode - root node of a public share + * Class PublicSharedRootNode - root node of a public share * * @package OCA\DAV\Files\PublicFiles */ -class ShareNode extends Collection { +class PublicSharedRootNode extends Collection { + use NodeFactoryTrait; /** @var IShare */ private $share; /** - * ShareNode constructor. + * PublicSharedRootNode constructor. * * @param IShare $share */ @@ -60,10 +63,7 @@ public function getChildren() { $nodes = [$this->share->getNode()]; } return \array_map(function (Node $node) { - if ($node->getType() === FileInfo::TYPE_FOLDER) { - return new SharedFolder($node, $this->share); - } - return new SharedFile($node, $this->share); + return $this->nodeFactory($node, $this->share); }, $nodes); } @@ -72,7 +72,7 @@ public function createDirectory($name) { throw new Forbidden('Permission denied to create directory'); } if ($this->share->getNodeType() !== 'folder') { - throw new Forbidden('Permission denied to create directory'); + throw new Forbidden('Creating a folder in a file is not allowed'); } try { $this->share->getNode()->newFolder($name); @@ -81,6 +81,13 @@ public function createDirectory($name) { } } + /** + * @param string $name + * @param resource|string|null $data + * @return string|null - the quoted etag - see base class + * @throws Forbidden + * @throws NotFoundException + */ public function createFile($name, $data = null) { if (!$this->checkPermissions(Constants::PERMISSION_CREATE)) { throw new Forbidden('Permission denied to create file'); @@ -94,6 +101,10 @@ public function createFile($name, $data = null) { return $file->getEtag(); } catch (NotPermittedException $ex) { throw new Forbidden('Permission denied to create file'); + } catch (InvalidPathException $ex) { + throw new Forbidden('Permission denied to create file'); + } catch (NotFoundException $ex) { + throw new Forbidden('Permission denied to create file'); } } diff --git a/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php index ac768de366c0..dc1744df8cc4 100644 --- a/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php +++ b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php @@ -89,7 +89,7 @@ public function __construct(Server $server, IManager $manager) { */ public function check(RequestInterface $request, ResponseInterface $response) { $node = $this->resolveShare($request->getPath()); - if (!$node instanceof ShareNode && !$node instanceof SharedFile && !$node instanceof SharedFolder) { + if (!$node instanceof PublicSharedRootNode) { return [true, 'principals/system/public']; } $this->share = $node->getShare(); @@ -127,7 +127,7 @@ protected function validateUserPass($username, $password) { /** * @param string $path - * @return INode + * @return INode|null * @throws NotFound */ private function resolveShare($path) { diff --git a/apps/dav/lib/Files/PublicFiles/RootCollection.php b/apps/dav/lib/Files/PublicFiles/RootCollection.php index 0a86577f289f..f502a72da244 100644 --- a/apps/dav/lib/Files/PublicFiles/RootCollection.php +++ b/apps/dav/lib/Files/PublicFiles/RootCollection.php @@ -71,7 +71,7 @@ public function getName() { public function getChild($name) { try { $share = $this->shareManager->getShareByToken($name); - return new ShareNode($share); + return new PublicSharedRootNode($share); } catch (ShareNotFound $ex) { throw new NotFound(); } @@ -87,7 +87,7 @@ public function getChildren() { $shares = $this->shareManager->getAllSharedWith(null, [Constants::SHARE_TYPE_LINK]); return \array_map(static function (IShare $share) { - return new ShareNode($share); + return new PublicSharedRootNode($share); }, $shares); } } diff --git a/apps/dav/lib/Files/PublicFiles/SharedFile.php b/apps/dav/lib/Files/PublicFiles/SharedFile.php index 05a6ca3d0c18..dc09bd35c9ae 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFile.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFile.php @@ -22,15 +22,10 @@ namespace OCA\DAV\Files\PublicFiles; use OCA\DAV\Files\IFileNode; -use OCP\Constants; -use OCP\Files\InvalidPathException; -use OCP\Files\Node; -use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\Share\IShare; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\File; -use Sabre\DAVACL\ACLTrait; use Sabre\DAVACL\IACL; /** @@ -39,14 +34,10 @@ * @package OCA\DAV\Files\PublicFiles */ class SharedFile extends File implements IACL, IFileNode, IPublicSharedNode { - use ACLTrait; + use SharedNodeTrait; /** @var \OCP\Files\File */ private $file; - /** - * @var IShare - */ - private $share; /** * MetaFolder constructor. @@ -56,16 +47,10 @@ class SharedFile extends File implements IACL, IFileNode, IPublicSharedNode { */ public function __construct(\OCP\Files\File $file, IShare $share) { $this->file = $file; + $this->node = $file; $this->share = $share; } - /** - * @inheritdoc - */ - public function getName() { - return $this->file->getName(); - } - public function get() { try { return $this->file->fopen('r'); @@ -74,114 +59,16 @@ public function get() { } } - public function getSize() { - return $this->file->getSize(); - } - public function getContentType() { return $this->file->getMimeType(); } - public function getETag() { - return $this->file->getETag(); - } - - public function getLastModified() { - return $this->file->getMTime(); - } - - public function delete() { - try { - $this->file->delete(); - } catch (NotPermittedException $ex) { - throw new Forbidden('Permission denied to create directory'); - } - } - public function put($data) { try { $this->file->putContent($data); return $this->file->getEtag(); } catch (NotPermittedException $ex) { - throw new Forbidden('Permission denied to create directory'); - } - } - - public function setName($name) { - try { - $newPath = $this->file->getParent()->getPath() . '/' . $name; - $this->file->move($newPath); - } catch (NotPermittedException $ex) { - throw new Forbidden('Permission denied to rename file'); - } - } - - public function getOwner() { - return ''; - } - - public function getACL() { - $acl = [ - [ - 'privilege' => '{DAV:}all', - 'principal' => '{DAV:}owner', - 'protected' => true, - ], - [ - 'privilege' => '{DAV:}read', - 'principal' => 'principals/system/public', - 'protected' => true, - ] - ]; - if ($this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { - $acl[] = - [ - 'privilege' => '{DAV:}write-content', - 'principal' => 'principals/system/public', - 'protected' => true, - ]; + throw new Forbidden('Permission denied to change data'); } - - return $acl; - } - - public function getShare() { - return $this->share; - } - - /** - * @return Node - */ - public function getNode() { - return $this->file; - } - - /** - * @return string - * @throws InvalidPathException - * @throws NotFoundException - */ - public function getDavPermissions() { - $node = $this->getNode(); - $p = ''; - if ($node->isDeletable() && $this->checkSharePermissions(Constants::PERMISSION_DELETE)) { - $p .= 'D'; - } - if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { - $p .= 'NV'; // Renameable, Moveable - } - if ($node->getType() === \OCP\Files\FileInfo::TYPE_FILE) { - if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { - $p .= 'W'; - } - } else { - if ($node->isCreatable() && $this->checkSharePermissions(Constants::PERMISSION_CREATE)) { - $p .= 'CK'; - } - } - return $p; - } - protected function checkSharePermissions($permissions) { - return ($this->share->getPermissions() & $permissions) === $permissions; } } diff --git a/apps/dav/lib/Files/PublicFiles/SharedFolder.php b/apps/dav/lib/Files/PublicFiles/SharedFolder.php index 4eaae557633e..39a069c6d6ca 100644 --- a/apps/dav/lib/Files/PublicFiles/SharedFolder.php +++ b/apps/dav/lib/Files/PublicFiles/SharedFolder.php @@ -22,14 +22,11 @@ namespace OCA\DAV\Files\PublicFiles; use OCP\Constants; -use OCP\Files\File; use OCP\Files\Folder; -use OCP\Files\Node; use OCP\Files\NotPermittedException; use OCP\Share\IShare; use Sabre\DAV\Collection; use Sabre\DAV\Exception\Forbidden; -use Sabre\DAVACL\ACLTrait; use Sabre\DAVACL\IACL; /** @@ -38,21 +35,20 @@ * @package OCA\DAV\Files\PublicFiles */ class SharedFolder extends Collection implements IACL, IPublicSharedNode { - use ACLTrait; + use SharedNodeTrait, NodeFactoryTrait; /** @var Folder */ private $folder; - /** @var IShare */ - private $share; /** - * MetaFolder constructor. + * SharedFolder constructor. * * @param Folder $folder * @param IShare $share */ public function __construct(Folder $folder, IShare $share) { $this->folder = $folder; + $this->node = $folder; $this->share = $share; } @@ -62,21 +58,10 @@ public function __construct(Folder $folder, IShare $share) { public function getChildren() { $nodes = $this->folder->getDirectoryListing(); return \array_map(function ($node) { - return $this->nodeFactory($node); + return $this->nodeFactory($node, $this->share); }, $nodes); } - /** - * @inheritdoc - */ - public function getName() { - return $this->folder->getName(); - } - - public function getLastModified() { - return $this->folder->getMTime(); - } - public function createDirectory($name) { try { $this->folder->newFolder($name); @@ -90,33 +75,6 @@ public function createFile($name, $data = null) { $file->putContent($data); } - public function delete() { - try { - $this->folder->delete(); - } catch (NotPermittedException $ex) { - throw new Forbidden('Permission denied to delete node'); - } - } - - public function setName($name) { - try { - $newPath = $this->folder->getParent()->getPath() . '/' . $name; - $this->folder->move($newPath); - } catch (NotPermittedException $ex) { - throw new Forbidden('Permission denied to rename file'); - } - } - - private function nodeFactory(Node $node) { - if ($node instanceof Folder) { - return new SharedFolder($node, $this->share); - } - if ($node instanceof File) { - return new SharedFile($node, $this->share); - } - throw new \InvalidArgumentException(); - } - public function getACL() { $acl = [ [ @@ -150,45 +108,4 @@ public function getACL() { return $acl; } - - public function getShare() { - return $this->share; - } - - /** - * @return Node - */ - public function getNode() { - return $this->folder; - } - - /** - * @return string - * @throws \OCP\Files\InvalidPathException - * @throws \OCP\Files\NotFoundException - */ - public function getDavPermissions() { - $node = $this->getNode(); - $p = ''; - if ($node->isDeletable() && $this->checkSharePermissions(Constants::PERMISSION_DELETE)) { - $p .= 'D'; - } - if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { - $p .= 'NV'; // Renameable, Moveable - } - if ($node->getType() === \OCP\Files\FileInfo::TYPE_FILE) { - if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { - $p .= 'W'; - } - } else { - if ($node->isCreatable() && $this->checkSharePermissions(Constants::PERMISSION_CREATE)) { - $p .= 'CK'; - } - } - return $p; - } - - protected function checkSharePermissions($permissions) { - return ($this->share->getPermissions() & $permissions) === $permissions; - } } diff --git a/apps/dav/lib/Files/PublicFiles/SharedNodeTrait.php b/apps/dav/lib/Files/PublicFiles/SharedNodeTrait.php new file mode 100644 index 000000000000..e402cfc51aa2 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/SharedNodeTrait.php @@ -0,0 +1,220 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\Files\PublicFiles; + +use OCP\Constants; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\Node; +use OCP\Files\NotPermittedException; +use OCP\Share\IShare; +use Sabre\DAV\Collection; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAVACL\ACLTrait; +use Sabre\DAVACL\IACL; + +/** + * Trait SharedNodeTrait - common method implementations of SharedFile and SharedFolder + * + * @package OCA\DAV\Files\PublicFiles + */ +trait SharedNodeTrait { + + /**@var Node */ + private $node; + /** @var IShare */ + private $share; + + public function getName() { + return $this->node->getName(); + } + + public function getLastModified() { + return $this->node->getMTime(); + } + + public function getETag() { + return $this->node->getETag(); + } + + public function delete() { + try { + $this->node->delete(); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to delete node'); + } + } + + public function setName($name) { + try { + $newPath = $this->node->getParent()->getPath() . '/' . $name; + $this->node->move($newPath); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to rename node'); + } + } + + /** + * Returns the owner principal. + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + public function getOwner() { + return null; + } + + /** + * Returns a group principal. + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + public function getGroup() { + return null; + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + public function getACL() { + $acl = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/system/public', + 'protected' => true, + ] + ]; + + if (($this->node instanceof File) && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $acl[] = + [ + 'privilege' => '{DAV:}write-content', + 'principal' => 'principals/system/public', + 'protected' => true, + ]; + } + if ($this->node instanceof Folder) { + if ($this->checkSharePermissions(Constants::PERMISSION_DELETE)) { + $acl[] = + [ + 'privilege' => '{DAV:}unbind', + 'principal' => 'principals/system/public', + 'protected' => true, + ]; + } + if ($this->checkSharePermissions(Constants::PERMISSION_CREATE)) { + $acl[] = + [ + 'privilege' => '{DAV:}bind', + 'principal' => 'principals/system/public', + 'protected' => true, + ]; + } + } + + return $acl; + } + + /** + * Updates the ACL. + * + * This method will receive a list of new ACE's as an array argument. + * + * @param array $acl + */ + public function setACL(array $acl) { + throw new \Sabre\DAV\Exception\Forbidden('Setting ACL is not supported on this node'); + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + public function getSupportedPrivilegeSet() { + return null; + } + public function getShare() { + return $this->share; + } + + /** + * @return Node + */ + public function getNode() { + return $this->node; + } + + /** + * @return string + * @throws \OCP\Files\InvalidPathException + * @throws \OCP\Files\NotFoundException + */ + public function getDavPermissions() { + $node = $this->getNode(); + $p = ''; + if ($node->isDeletable() && $this->checkSharePermissions(Constants::PERMISSION_DELETE)) { + $p .= 'D'; + } + if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $p .= 'NV'; // Renameable, Moveable + } + if ($node->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { + $p .= 'W'; + } + } else { + if ($node->isCreatable() && $this->checkSharePermissions(Constants::PERMISSION_CREATE)) { + $p .= 'CK'; + } + } + return $p; + } + + protected function checkSharePermissions($permissions) { + return ($this->share->getPermissions() & $permissions) === $permissions; + } +} diff --git a/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.php b/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.php index 9e498585c054..e803434896ea 100644 --- a/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.php +++ b/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.php @@ -22,7 +22,7 @@ namespace OCA\DAV\Tests\Unit\Files\PublicFiles; use OCA\DAV\Files\PublicFiles\PublicFilesPlugin; -use OCA\DAV\Files\PublicFiles\ShareNode; +use OCA\DAV\Files\PublicFiles\PublicSharedRootNode; use OCP\Share\IShare; use Sabre\DAV\PropFind; use Sabre\DAV\Server; @@ -41,11 +41,11 @@ public function testInit() { /** * @dataProvider providesMethods */ - public function testPropFindShareNode($expectedMethod, $expectedMethodReturn, $prop, $methodReturnValue = null) { + public function testPropFindPublicSharedRootNode($expectedMethod, $expectedMethodReturn, $prop, $methodReturnValue = null) { if ($methodReturnValue === null) { $methodReturnValue = $expectedMethodReturn; } - $node = $this->createMock(ShareNode::class); + $node = $this->createMock(PublicSharedRootNode::class); $share = $this->createMock(IShare::class); $node->method('getShare')->willReturn($share); $share->expects(self::once())->method($expectedMethod)->willReturn($methodReturnValue); diff --git a/apps/dav/tests/unit/Files/PublicFiles/PublicSharingAuthTest.php b/apps/dav/tests/unit/Files/PublicFiles/PublicSharingAuthTest.php index 71c7fda35a52..5b908bbb54b7 100644 --- a/apps/dav/tests/unit/Files/PublicFiles/PublicSharingAuthTest.php +++ b/apps/dav/tests/unit/Files/PublicFiles/PublicSharingAuthTest.php @@ -23,7 +23,7 @@ use OCA\DAV\Files\PublicFiles\PublicFilesPlugin; use OCA\DAV\Files\PublicFiles\PublicSharingAuth; -use OCA\DAV\Files\PublicFiles\ShareNode; +use OCA\DAV\Files\PublicFiles\PublicSharedRootNode; use OCP\Share\IManager; use OCP\Share\IShare; use PHPUnit\Framework\MockObject\MockObject; @@ -71,8 +71,8 @@ public function providesCheckData() { $validResult = [true, 'principals/system/public']; $authHeaderMissing = [false, 'No \'Authorization: Basic\' header found. Either the client didn\'t send one, or the server is misconfigured']; $wrongUserOrPassword = [false, 'Username or password was incorrect']; - $shareWithoutPassword = $this->createShareNode(); - $shareWithPassword = $this->createShareNode('123456'); + $shareWithoutPassword = $this->createPublicSharedRootNode(); + $shareWithPassword = $this->createPublicSharedRootNode('123456'); return [ 'not a share node' => [$validResult, null], @@ -87,8 +87,8 @@ public function providesCheckData() { /** * @return MockObject */ - private function createShareNode($password = null) { - $shareWithoutPassword = $this->createMock(ShareNode::class); + private function createPublicSharedRootNode($password = null) { + $shareWithoutPassword = $this->createMock(PublicSharedRootNode::class); $share = $this->createMock(IShare::class); if ($password) { $share->method('getPassword')->willReturn($password);