diff --git a/apps/dav/lib/DAV/PublicAuth.php b/apps/dav/lib/DAV/PublicAuth.php index 8b854691cc84..6ae6aeecab54 100644 --- a/apps/dav/lib/DAV/PublicAuth.php +++ b/apps/dav/lib/DAV/PublicAuth.php @@ -29,9 +29,6 @@ class PublicAuth implements BackendInterface { /** @var string[] */ private $publicURLs; - /** - * @param string[] $publicURLs - */ public function __construct() { $this->publicURLs = [ 'public-calendars', diff --git a/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php b/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php new file mode 100644 index 000000000000..4bd32313812f --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/IPublicSharedNode.php @@ -0,0 +1,48 @@ + + * + * @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 - common interface of all files and folders + * in a shared node + * + * @package OCA\DAV\Files\PublicFiles + */ +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/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 new file mode 100644 index 000000000000..f4a98e7c3c7b --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php @@ -0,0 +1,108 @@ + + * + * @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 - 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'; + 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 PublicSharedRootNode) { + $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/PublicSharedRootNode.php b/apps/dav/lib/Files/PublicFiles/PublicSharedRootNode.php new file mode 100644 index 000000000000..6ba63c86d714 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/PublicSharedRootNode.php @@ -0,0 +1,154 @@ + + * + * @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\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; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\INode; + +/** + * Class PublicSharedRootNode - root node of a public share + * + * @package OCA\DAV\Files\PublicFiles + */ +class PublicSharedRootNode extends Collection { + use NodeFactoryTrait; + + /** @var IShare */ + private $share; + + /** + * PublicSharedRootNode constructor. + * + * @param IShare $share + */ + public function __construct(IShare $share) { + $this->share = $share; + } + /** + * Returns an array with all the child nodes + * + * @return INode[] + */ + 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 $this->nodeFactory($node, $this->share); + }, $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('Creating a folder in a file is not allowed'); + } + try { + $this->share->getNode()->newFolder($name); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to create directory'); + } + } + + /** + * @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'); + } + if ($this->share->getNodeType() !== 'folder') { + 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 file'); + } catch (InvalidPathException $ex) { + throw new Forbidden('Permission denied to create file'); + } catch (NotFoundException $ex) { + throw new Forbidden('Permission denied to create file'); + } + } + + 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. + * + * This is used to generate the url. + * + * @return string + */ + public function getName() { + return $this->share->getToken(); + } + + 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/PublicSharingAuth.php b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php new file mode 100644 index 000000000000..dc1744df8cc4 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php @@ -0,0 +1,141 @@ + + * + * @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\Exception\NotFound; +use Sabre\DAV\INode; +use Sabre\DAV\Server; +use Sabre\HTTP\RequestInterface; +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 */ + private $server; + /** @var IShare */ + private $share; + /** @var IManager */ + private $shareManager; + + /** + * PublicSharingAuth constructor. + * + * @param Server $server + * @param IManager $manager + */ + 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 + * @throws NotFound + */ + public function check(RequestInterface $request, ResponseInterface $response) { + $node = $this->resolveShare($request->getPath()); + if (!$node instanceof PublicSharedRootNode) { + 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) { + // intentionally left empty - no need to challenge the user here + } + + /** + * 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); + } + + /** + * @param string $path + * @return INode|null + * @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/RootCollection.php b/apps/dav/lib/Files/PublicFiles/RootCollection.php new file mode 100644 index 000000000000..f502a72da244 --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/RootCollection.php @@ -0,0 +1,93 @@ + + * + * @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 - 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 */ + 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; + + public function __construct() { + $this->l10n = \OC::$server->getL10N('dav'); + $this->shareManager = \OC::$server->getShareManager(); + } + + /** + * @inheritdoc + */ + public function getName() { + return 'public-files'; + } + + /** + * @inheritdoc + */ + public function getChild($name) { + try { + $share = $this->shareManager->getShareByToken($name); + return new PublicSharedRootNode($share); + } catch (ShareNotFound $ex) { + throw new NotFound(); + } + } + + /** + * @inheritdoc + */ + 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(static function (IShare $share) { + return new PublicSharedRootNode($share); + }, $shares); + } +} diff --git a/apps/dav/lib/Files/PublicFiles/SharedFile.php b/apps/dav/lib/Files/PublicFiles/SharedFile.php new file mode 100644 index 000000000000..dc09bd35c9ae --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/SharedFile.php @@ -0,0 +1,74 @@ + + * + * @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\Files\IFileNode; +use OCP\Files\NotPermittedException; +use OCP\Share\IShare; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\File; +use Sabre\DAVACL\IACL; + +/** + * Class SharedFile - represents a file living in a public shared folder + * + * @package OCA\DAV\Files\PublicFiles + */ +class SharedFile extends File implements IACL, IFileNode, IPublicSharedNode { + use SharedNodeTrait; + + /** @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; + $this->node = $file; + $this->share = $share; + } + + public function get() { + try { + return $this->file->fopen('r'); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to read this file'); + } + } + + public function getContentType() { + return $this->file->getMimeType(); + } + + public function put($data) { + try { + $this->file->putContent($data); + return $this->file->getEtag(); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to change data'); + } + } +} diff --git a/apps/dav/lib/Files/PublicFiles/SharedFolder.php b/apps/dav/lib/Files/PublicFiles/SharedFolder.php new file mode 100644 index 000000000000..39a069c6d6ca --- /dev/null +++ b/apps/dav/lib/Files/PublicFiles/SharedFolder.php @@ -0,0 +1,111 @@ + + * + * @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\Folder; +use OCP\Files\NotPermittedException; +use OCP\Share\IShare; +use Sabre\DAV\Collection; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAVACL\IACL; + +/** + * Class SharedFolder - represents a folder living in a public shared folder + * + * @package OCA\DAV\Files\PublicFiles + */ +class SharedFolder extends Collection implements IACL, IPublicSharedNode { + use SharedNodeTrait, NodeFactoryTrait; + + /** @var Folder */ + private $folder; + + /** + * SharedFolder constructor. + * + * @param Folder $folder + * @param IShare $share + */ + public function __construct(Folder $folder, IShare $share) { + $this->folder = $folder; + $this->node = $folder; + $this->share = $share; + } + + /** + * @inheritdoc + */ + public function getChildren() { + $nodes = $this->folder->getDirectoryListing(); + return \array_map(function ($node) { + return $this->nodeFactory($node, $this->share); + }, $nodes); + } + + public function createDirectory($name) { + try { + $this->folder->newFolder($name); + } catch (NotPermittedException $ex) { + throw new Forbidden('Permission denied to create directory'); + } + } + + public function createFile($name, $data = null) { + $file = $this->folder->newFile($name); + $file->putContent($data); + } + + 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_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; + } +} 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/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/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 01c4100eb73f..31e791273e79 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,15 +58,21 @@ use OCA\DAV\Files\BrowserErrorPagePlugin; 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; 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 { @@ -78,37 +89,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 @@ -118,8 +129,12 @@ 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())); + $this->server->addPlugin(new PublicLinkEventsPlugin(\OC::$server->getEventDispatcher())); + } $authPlugin->addBackend(new PublicAuth()); $this->server->addPlugin($authPlugin); @@ -131,16 +146,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 @@ -158,67 +174,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) ) ); @@ -227,9 +243,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); @@ -237,9 +253,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); @@ -251,18 +267,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 )); @@ -270,25 +286,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); @@ -300,7 +316,7 @@ public function __construct(IRequest $request, $baseUri) { } public function exec() { - $this->server->exec(); + $this->server->start(); } /** @@ -309,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; } diff --git a/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.php b/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.php new file mode 100644 index 000000000000..e803434896ea --- /dev/null +++ b/apps/dav/tests/unit/Files/PublicFiles/PublicFilesPluginTest.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\PublicSharedRootNode; +use OCP\Share\IShare; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Sabre\DAV\Xml\Property\GetLastModified; +use Test\TestCase; + +class PublicFilesPluginTest 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 testPropFindPublicSharedRootNode($expectedMethod, $expectedMethodReturn, $prop, $methodReturnValue = null) { + if ($methodReturnValue === null) { + $methodReturnValue = $expectedMethodReturn; + } + $node = $this->createMock(PublicSharedRootNode::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], + ]; + } +} 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..5b908bbb54b7 --- /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\PublicSharedRootNode; +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->createPublicSharedRootNode(); + $shareWithPassword = $this->createPublicSharedRootNode('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 createPublicSharedRootNode($password = null) { + $shareWithoutPassword = $this->createMock(PublicSharedRootNode::class); + $share = $this->createMock(IShare::class); + if ($password) { + $share->method('getPassword')->willReturn($password); + } + $shareWithoutPassword->method('getShare')->willReturn($share); + return $shareWithoutPassword; + } +} 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'); }); 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; + } } 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();