From b0c801c764b75fc6f3240ab0028e3d3d8116f493 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 13 Apr 2018 13:44:57 +0200 Subject: [PATCH 01/12] Add directdownload table Signed-off-by: Roeland Jago Douma --- apps/dav/appinfo/info.xml | 2 +- .../Version1005Date20180413093149.php | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 apps/dav/lib/Migration/Version1005Date20180413093149.php diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 3afef530e7..4952fa3353 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ WebDAV WebDAV endpoint WebDAV endpoint - 1.5.0 + 1.5.1 agpl owncloud.org DAV diff --git a/apps/dav/lib/Migration/Version1005Date20180413093149.php b/apps/dav/lib/Migration/Version1005Date20180413093149.php new file mode 100644 index 0000000000..cd6aeca6b4 --- /dev/null +++ b/apps/dav/lib/Migration/Version1005Date20180413093149.php @@ -0,0 +1,80 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Migration; + +use Doctrine\DBAL\Types\Type; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +class Version1005Date20180413093149 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) { + + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + if (!$schema->hasTable('directlink')) { + $table = $schema->createTable('directlink'); + + $table->addColumn('id',Type::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('user_id', Type::STRING, [ + 'notnull' => false, + 'length' => 64, + ]); + $table->addColumn('file_id', Type::BIGINT, [ + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('token', Type::STRING, [ + 'notnull' => false, + 'length' => 60, + ]); + $table->addColumn('expiration', Type::BIGINT, [ + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + + $table->setPrimaryKey(['id'], 'directlink_id_idx'); + $table->addIndex(['token'], 'directlink_token_idx'); + $table->addIndex(['expiration'], 'directlink_expiration_idx'); + + return $schema; + } + } +} From 9ab5b6a4f1f00fa83d5f4f05ca6157a5b47e35a9 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 13 Apr 2018 13:58:24 +0200 Subject: [PATCH 02/12] Add DB mapping Signed-off-by: Roeland Jago Douma --- apps/dav/lib/Db/Direct.php | 58 ++++++++++++++++++++++++++++++++ apps/dav/lib/Db/DirectMapper.php | 35 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 apps/dav/lib/Db/Direct.php create mode 100644 apps/dav/lib/Db/DirectMapper.php diff --git a/apps/dav/lib/Db/Direct.php b/apps/dav/lib/Db/Direct.php new file mode 100644 index 0000000000..ef588b1ec3 --- /dev/null +++ b/apps/dav/lib/Db/Direct.php @@ -0,0 +1,58 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Db; + +use OCP\AppFramework\Db\Entity; + +/** + * @method string getUserId() + * @method void setUserId(string $userId) + * @method int getFileId() + * @method void setFileId(int $fileId) + * @method string getToken() + * @method void setToken(string $token) + * @method int getExpiration() + * @method void setExpiration(int $expiration) + */ +class Direct extends Entity { + /** @var string */ + protected $userId; + + /** @var int */ + protected $fileId; + + /** @var string */ + protected $token; + + /** @var int */ + protected $expiration; + + public function __construct() { + $this->addType('userId', 'string'); + $this->addType('fileId', 'int'); + $this->addType('token', 'string'); + $this->addType('expiration', 'int'); + } +} diff --git a/apps/dav/lib/Db/DirectMapper.php b/apps/dav/lib/Db/DirectMapper.php new file mode 100644 index 0000000000..1e0e6396f3 --- /dev/null +++ b/apps/dav/lib/Db/DirectMapper.php @@ -0,0 +1,35 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Db; + +use OCP\AppFramework\Db\Mapper; +use OCP\IDBConnection; + +class DirectMapper extends Mapper { + + public function __construct(IDBConnection $db, string $tableName, string $entityClass = null) { + parent::__construct($db, 'directlink', Direct::class); + } +} From f984664bee136075556963af56999e0315cbb4bb Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 13 Apr 2018 14:04:41 +0200 Subject: [PATCH 03/12] First step of DAV endpoint Signed-off-by: Roeland Jago Douma --- apps/dav/appinfo/routes.php | 5 +- apps/dav/appinfo/v2/direct.php | 39 ++++++++ .../composer/composer/autoload_classmap.php | 8 ++ .../dav/composer/composer/autoload_static.php | 8 ++ apps/dav/lib/Controller/DirectController.php | 61 +++++++++++++ apps/dav/lib/Direct/DirectFile.php | 79 ++++++++++++++++ apps/dav/lib/Direct/DirectHome.php | 89 +++++++++++++++++++ apps/dav/lib/Direct/Server.php | 33 +++++++ apps/dav/lib/Direct/ServerFactory.php | 51 +++++++++++ remote.php | 1 + 10 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 apps/dav/appinfo/v2/direct.php create mode 100644 apps/dav/lib/Controller/DirectController.php create mode 100644 apps/dav/lib/Direct/DirectFile.php create mode 100644 apps/dav/lib/Direct/DirectHome.php create mode 100644 apps/dav/lib/Direct/Server.php create mode 100644 apps/dav/lib/Direct/ServerFactory.php diff --git a/apps/dav/appinfo/routes.php b/apps/dav/appinfo/routes.php index e6785bfd4e..2aaeda9896 100644 --- a/apps/dav/appinfo/routes.php +++ b/apps/dav/appinfo/routes.php @@ -25,5 +25,8 @@ return [ 'routes' => [ ['name' => 'birthday_calendar#enable', 'url' => '/enableBirthdayCalendar', 'verb' => 'POST'], ['name' => 'birthday_calendar#disable', 'url' => '/disableBirthdayCalendar', 'verb' => 'POST'], - ] + ], + 'ocs' => [ + ['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'], + ], ]; diff --git a/apps/dav/appinfo/v2/direct.php b/apps/dav/appinfo/v2/direct.php new file mode 100644 index 0000000000..4ee22398c7 --- /dev/null +++ b/apps/dav/appinfo/v2/direct.php @@ -0,0 +1,39 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +// no php execution timeout for webdav +if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { + @set_time_limit(0); +} +ignore_user_abort(true); + +// Turn off output buffering to prevent memory problems +\OC_Util::obEnd(); + +$requestUri = \OC::$server->getRequest()->getRequestUri(); + +$serverFactory = new \OCA\DAV\Direct\ServerFactory(\OC::$server->getConfig()); +$server = $serverFactory->createServer($baseuri, $requestUri); + +$server->exec(); diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 0bf6b25751..1f48e74ca4 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -109,6 +109,7 @@ return array( 'OCA\\DAV\\Connector\\Sabre\\TagList' => $baseDir . '/../lib/Connector/Sabre/TagList.php', 'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => $baseDir . '/../lib/Connector/Sabre/TagsPlugin.php', 'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php', + 'OCA\\DAV\\Controller\\DirectController' => $baseDir . '/../lib/Controller/DirectController.php', 'OCA\\DAV\\DAV\\CustomPropertiesBackend' => $baseDir . '/../lib/DAV/CustomPropertiesBackend.php', 'OCA\\DAV\\DAV\\GroupPrincipalBackend' => $baseDir . '/../lib/DAV/GroupPrincipalBackend.php', 'OCA\\DAV\\DAV\\PublicAuth' => $baseDir . '/../lib/DAV/PublicAuth.php', @@ -118,6 +119,12 @@ return array( 'OCA\\DAV\\DAV\\Sharing\\Xml\\Invite' => $baseDir . '/../lib/DAV/Sharing/Xml/Invite.php', 'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest' => $baseDir . '/../lib/DAV/Sharing/Xml/ShareRequest.php', 'OCA\\DAV\\DAV\\SystemPrincipalBackend' => $baseDir . '/../lib/DAV/SystemPrincipalBackend.php', + 'OCA\\DAV\\Db\\Direct' => $baseDir . '/../lib/Db/Direct.php', + 'OCA\\DAV\\Db\\DirectMapper' => $baseDir . '/../lib/Db/DirectMapper.php', + 'OCA\\DAV\\Direct\\DirectFile' => $baseDir . '/../lib/Direct/DirectFile.php', + 'OCA\\DAV\\Direct\\DirectHome' => $baseDir . '/../lib/Direct/DirectHome.php', + 'OCA\\DAV\\Direct\\Server' => $baseDir . '/../lib/Direct/Server.php', + 'OCA\\DAV\\Direct\\ServerFactory' => $baseDir . '/../lib/Direct/ServerFactory.php', 'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => $baseDir . '/../lib/Files/BrowserErrorPagePlugin.php', 'OCA\\DAV\\Files\\FileSearchBackend' => $baseDir . '/../lib/Files/FileSearchBackend.php', 'OCA\\DAV\\Files\\FilesHome' => $baseDir . '/../lib/Files/FilesHome.php', @@ -133,6 +140,7 @@ return array( 'OCA\\DAV\\Migration\\Version1004Date20170919104507' => $baseDir . '/../lib/Migration/Version1004Date20170919104507.php', 'OCA\\DAV\\Migration\\Version1004Date20170924124212' => $baseDir . '/../lib/Migration/Version1004Date20170924124212.php', 'OCA\\DAV\\Migration\\Version1004Date20170926103422' => $baseDir . '/../lib/Migration/Version1004Date20170926103422.php', + 'OCA\\DAV\\Migration\\Version1005Date20180413093149' => $baseDir . '/../lib/Migration/Version1005Date20180413093149.php', 'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php', 'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php', 'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 4be9b75969..cba72bc03c 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -124,6 +124,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Connector\\Sabre\\TagList' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagList.php', 'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagsPlugin.php', 'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php', + 'OCA\\DAV\\Controller\\DirectController' => __DIR__ . '/..' . '/../lib/Controller/DirectController.php', 'OCA\\DAV\\DAV\\CustomPropertiesBackend' => __DIR__ . '/..' . '/../lib/DAV/CustomPropertiesBackend.php', 'OCA\\DAV\\DAV\\GroupPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/GroupPrincipalBackend.php', 'OCA\\DAV\\DAV\\PublicAuth' => __DIR__ . '/..' . '/../lib/DAV/PublicAuth.php', @@ -133,6 +134,12 @@ class ComposerStaticInitDAV 'OCA\\DAV\\DAV\\Sharing\\Xml\\Invite' => __DIR__ . '/..' . '/../lib/DAV/Sharing/Xml/Invite.php', 'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest' => __DIR__ . '/..' . '/../lib/DAV/Sharing/Xml/ShareRequest.php', 'OCA\\DAV\\DAV\\SystemPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/SystemPrincipalBackend.php', + 'OCA\\DAV\\Db\\Direct' => __DIR__ . '/..' . '/../lib/Db/Direct.php', + 'OCA\\DAV\\Db\\DirectMapper' => __DIR__ . '/..' . '/../lib/Db/DirectMapper.php', + 'OCA\\DAV\\Direct\\DirectFile' => __DIR__ . '/..' . '/../lib/Direct/DirectFile.php', + 'OCA\\DAV\\Direct\\DirectHome' => __DIR__ . '/..' . '/../lib/Direct/DirectHome.php', + 'OCA\\DAV\\Direct\\Server' => __DIR__ . '/..' . '/../lib/Direct/Server.php', + 'OCA\\DAV\\Direct\\ServerFactory' => __DIR__ . '/..' . '/../lib/Direct/ServerFactory.php', 'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => __DIR__ . '/..' . '/../lib/Files/BrowserErrorPagePlugin.php', 'OCA\\DAV\\Files\\FileSearchBackend' => __DIR__ . '/..' . '/../lib/Files/FileSearchBackend.php', 'OCA\\DAV\\Files\\FilesHome' => __DIR__ . '/..' . '/../lib/Files/FilesHome.php', @@ -148,6 +155,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\Version1004Date20170919104507' => __DIR__ . '/..' . '/../lib/Migration/Version1004Date20170919104507.php', 'OCA\\DAV\\Migration\\Version1004Date20170924124212' => __DIR__ . '/..' . '/../lib/Migration/Version1004Date20170924124212.php', 'OCA\\DAV\\Migration\\Version1004Date20170926103422' => __DIR__ . '/..' . '/../lib/Migration/Version1004Date20170926103422.php', + 'OCA\\DAV\\Migration\\Version1005Date20180413093149' => __DIR__ . '/..' . '/../lib/Migration/Version1005Date20180413093149.php', 'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php', 'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php', 'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php', diff --git a/apps/dav/lib/Controller/DirectController.php b/apps/dav/lib/Controller/DirectController.php new file mode 100644 index 0000000000..28fdf88575 --- /dev/null +++ b/apps/dav/lib/Controller/DirectController.php @@ -0,0 +1,61 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Controller; + +use OCA\DAV\Db\DirectMapper; +use OCP\AppFramework\OCSController; +use OCP\Files\IRootFolder; +use OCP\IDBConnection; +use OCP\IRequest; +use OCP\IUserManager; + +class DirectController extends OCSController { + + /** @var IRootFolder */ + private $rootFolder; + + /** @var string */ + private $userId; + + /** @var DirectMapper */ + private $mapper; + + + public function __construct(string $appName, + IRequest $request, + IRootFolder $rootFolder, + string $userId, + DirectMapper $mapper) { + parent::__construct($appName, $request); + + $this->rootFolder = $rootFolder; + $this->userId = $userId; + $this->mapper = $mapper; + } + + public function getUrl(int $fileId) { + + } +} diff --git a/apps/dav/lib/Direct/DirectFile.php b/apps/dav/lib/Direct/DirectFile.php new file mode 100644 index 0000000000..d3ee2abe3b --- /dev/null +++ b/apps/dav/lib/Direct/DirectFile.php @@ -0,0 +1,79 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Direct; + +use OCP\Files\File; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\IFile; + +class DirectFile implements IFile { + /** @var File */ + private $file; + + public function __construct(File $file) { + $this->file = $file; + } + + function put($data) { + throw new Forbidden(); + } + + function get() { + // TODO: Implement get() method. + } + + function getContentType() { + // TODO: Implement getContentType() method. + } + + function getETag() { + return $this->file->getEtag(); + // TODO: Implement getETag() method. + } + + function getSize() { + return $this->file->getSize(); + // TODO: Implement getSize() method. + } + + function delete() { + throw new Forbidden(); + // TODO: Implement delete() method. + } + + function getName() { + return $this->file->getName(); + } + + function setName($name) { + throw new Forbidden(); + } + + function getLastModified() { + return $this->file->getMTime(); + // TODO: Implement getLastModified() method. + } + +} diff --git a/apps/dav/lib/Direct/DirectHome.php b/apps/dav/lib/Direct/DirectHome.php new file mode 100644 index 0000000000..05c72aa005 --- /dev/null +++ b/apps/dav/lib/Direct/DirectHome.php @@ -0,0 +1,89 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Direct; + +use OCP\Files\File; +use OCP\Files\IRootFolder; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; + +class DirectHome implements ICollection { + + /** @var IRootFolder */ + private $rootFolder; + + public function __construct(IRootFolder $rootFolder) { + $this->rootFolder = $rootFolder; + } + + function createFile($name, $data = null) { + throw new Forbidden(); + } + + function createDirectory($name) { + throw new Forbidden(); + } + + function getChild($name) { + throw new NotFound(); + } + + function getChildren() { + $adminFolder = $this->rootFolder->getUserFolder('admin'); + + $listing = $adminFolder->getDirectoryListing(); + + $res = []; + foreach ($listing as $file) { + if ($file instanceof File) { + $res[] = new DirectFile($file); + } + } + + return $res; + } + + function childExists($name) { + return false; + } + + function delete() { + throw new Forbidden(); + } + + function getName() { + return 'direct'; + } + + function setName($name) { + throw new Forbidden(); + } + + function getLastModified() { + return 0; + } + +} diff --git a/apps/dav/lib/Direct/Server.php b/apps/dav/lib/Direct/Server.php new file mode 100644 index 0000000000..373dc2dca5 --- /dev/null +++ b/apps/dav/lib/Direct/Server.php @@ -0,0 +1,33 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Direct; + +class Server extends \Sabre\DAV\Server { + public function __construct($treeOrNode = null) { + parent::__construct($treeOrNode); + self::$exposeVersion = false; + $this->enablePropfindDepthInfinityf = false; + } +} diff --git a/apps/dav/lib/Direct/ServerFactory.php b/apps/dav/lib/Direct/ServerFactory.php new file mode 100644 index 0000000000..85c1555021 --- /dev/null +++ b/apps/dav/lib/Direct/ServerFactory.php @@ -0,0 +1,51 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Direct; + +use OCP\IConfig; + +class ServerFactory { + /** @var IConfig */ + private $config; + + public function __construct(IConfig $config) { + $this->config = $config; + } + + public function createServer(string $baseURI, + string $requestURI) { + $home = new DirectHome(\OC::$server->getRootFolder()); + $server = new Server($home); + + $server->httpRequest->setUrl($requestURI); + $server->setBaseUri($baseURI); + + $server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config)); + + return $server; + + + } +} diff --git a/remote.php b/remote.php index 8879a30029..a14ff6ac30 100644 --- a/remote.php +++ b/remote.php @@ -100,6 +100,7 @@ function resolveService($service) { 'carddav' => 'dav/appinfo/v1/carddav.php', 'contacts' => 'dav/appinfo/v1/carddav.php', 'files' => 'dav/appinfo/v1/webdav.php', + 'direct' => 'dav/appinfo/v2/direct.php', ]; if (isset($services[$service])) { return $services[$service]; From 5c6d3b4f41d420e2ea84ba71c8173dcf931a77c6 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 13 Apr 2018 16:54:24 +0200 Subject: [PATCH 04/12] Request a direct link Signed-off-by: Roeland Jago Douma --- apps/dav/lib/Controller/DirectController.php | 56 ++++++++++++++++++-- apps/dav/lib/Db/DirectMapper.php | 2 +- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/apps/dav/lib/Controller/DirectController.php b/apps/dav/lib/Controller/DirectController.php index 28fdf88575..c357771f3f 100644 --- a/apps/dav/lib/Controller/DirectController.php +++ b/apps/dav/lib/Controller/DirectController.php @@ -24,12 +24,16 @@ declare(strict_types=1); namespace OCA\DAV\Controller; +use OCA\DAV\Db\Direct; use OCA\DAV\Db\DirectMapper; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\AppFramework\OCSController; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\IRootFolder; -use OCP\IDBConnection; use OCP\IRequest; -use OCP\IUserManager; +use OCP\IURLGenerator; +use OCP\Security\ISecureRandom; class DirectController extends OCSController { @@ -42,20 +46,64 @@ class DirectController extends OCSController { /** @var DirectMapper */ private $mapper; + /** @var ISecureRandom */ + private $random; + + /** @var ITimeFactory */ + private $timeFactory; + + /** @var IURLGenerator */ + private $urlGenerator; + public function __construct(string $appName, IRequest $request, IRootFolder $rootFolder, string $userId, - DirectMapper $mapper) { + DirectMapper $mapper, + ISecureRandom $random, + ITimeFactory $timeFactory, + IURLGenerator $urlGenerator) { parent::__construct($appName, $request); $this->rootFolder = $rootFolder; $this->userId = $userId; $this->mapper = $mapper; + $this->random = $random; + $this->timeFactory = $timeFactory; + $this->urlGenerator = $urlGenerator; } - public function getUrl(int $fileId) { + /** + * @NoAdminRequired + */ + public function getUrl(int $fileId): DataResponse { + $userFolder = $this->rootFolder->getUserFolder($this->userId); + $files = $userFolder->getById($fileId); + + if ($files === []) { + throw new OCSNotFoundException(); + } + + $file = array_shift($files); + + //TODO: try to get a url from the backend (like S3) + + + // Fallback to our default implemenation + $direct = new Direct(); + $direct->setUserId($this->userId); + $direct->setFileId($fileId); + + $token = $this->random->generate(60, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS); + $direct->setToken($token); + $direct->setExpiration($this->timeFactory->getTime() + 60*60*8); + + $this->mapper->insert($direct); + + return new DataResponse([ + 'url' => $this->urlGenerator->getAbsoluteURL('remote.php/direct/'.$token) + ]); } } diff --git a/apps/dav/lib/Db/DirectMapper.php b/apps/dav/lib/Db/DirectMapper.php index 1e0e6396f3..917fca2f92 100644 --- a/apps/dav/lib/Db/DirectMapper.php +++ b/apps/dav/lib/Db/DirectMapper.php @@ -29,7 +29,7 @@ use OCP\IDBConnection; class DirectMapper extends Mapper { - public function __construct(IDBConnection $db, string $tableName, string $entityClass = null) { + public function __construct(IDBConnection $db) { parent::__construct($db, 'directlink', Direct::class); } } From b3e7865d9b3a88647107220fd9ac08f0d80ae1cd Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 13 Apr 2018 17:11:25 +0200 Subject: [PATCH 05/12] Dav endpoint returns proper data Signed-off-by: Roeland Jago Douma --- apps/dav/appinfo/v2/direct.php | 7 +++- apps/dav/lib/Db/DirectMapper.php | 21 +++++++++++ apps/dav/lib/Direct/DirectFile.php | 51 ++++++++++++++++++++++----- apps/dav/lib/Direct/DirectHome.php | 18 ++++++++-- apps/dav/lib/Direct/ServerFactory.php | 8 +++-- 5 files changed, 90 insertions(+), 15 deletions(-) diff --git a/apps/dav/appinfo/v2/direct.php b/apps/dav/appinfo/v2/direct.php index 4ee22398c7..633d69a3c3 100644 --- a/apps/dav/appinfo/v2/direct.php +++ b/apps/dav/appinfo/v2/direct.php @@ -34,6 +34,11 @@ ignore_user_abort(true); $requestUri = \OC::$server->getRequest()->getRequestUri(); $serverFactory = new \OCA\DAV\Direct\ServerFactory(\OC::$server->getConfig()); -$server = $serverFactory->createServer($baseuri, $requestUri); +$server = $serverFactory->createServer( + $baseuri, + $requestUri, + \OC::$server->getRootFolder(), + \OC::$server->query(\OCA\DAV\Db\DirectMapper::class) +); $server->exec(); diff --git a/apps/dav/lib/Db/DirectMapper.php b/apps/dav/lib/Db/DirectMapper.php index 917fca2f92..806b145823 100644 --- a/apps/dav/lib/Db/DirectMapper.php +++ b/apps/dav/lib/Db/DirectMapper.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace OCA\DAV\Db; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Mapper; use OCP\IDBConnection; @@ -32,4 +33,24 @@ class DirectMapper extends Mapper { public function __construct(IDBConnection $db) { parent::__construct($db, 'directlink', Direct::class); } + + public function getByToken(string $token): Direct { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from('directlink') + ->where( + $qb->expr()->eq('token', $qb->createNamedParameter($token)) + ); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new DoesNotExistException(); + } + + return Direct::fromRow($data); + } } diff --git a/apps/dav/lib/Direct/DirectFile.php b/apps/dav/lib/Direct/DirectFile.php index d3ee2abe3b..d327a1752b 100644 --- a/apps/dav/lib/Direct/DirectFile.php +++ b/apps/dav/lib/Direct/DirectFile.php @@ -24,16 +24,26 @@ declare(strict_types=1); namespace OCA\DAV\Direct; +use OCA\DAV\Db\Direct; use OCP\Files\File; +use OCP\Files\IRootFolder; use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; use Sabre\DAV\IFile; class DirectFile implements IFile { + /** @var Direct */ + private $direct; + + /** @var IRootFolder */ + private $rootFolder; + /** @var File */ private $file; - public function __construct(File $file) { - $this->file = $file; + public function __construct(Direct $direct, IRootFolder $rootFolder) { + $this->direct = $direct; + $this->rootFolder = $rootFolder; } function put($data) { @@ -41,30 +51,35 @@ class DirectFile implements IFile { } function get() { - // TODO: Implement get() method. + $this->getFile(); + + return $this->file->fopen('rb'); } function getContentType() { - // TODO: Implement getContentType() method. + $this->getFile(); + + return $this->file->getMimeType(); } function getETag() { + $this->getFile(); + return $this->file->getEtag(); - // TODO: Implement getETag() method. } function getSize() { + $this->getFile(); + return $this->file->getSize(); - // TODO: Implement getSize() method. } function delete() { throw new Forbidden(); - // TODO: Implement delete() method. } function getName() { - return $this->file->getName(); + return $this->direct->getToken(); } function setName($name) { @@ -72,8 +87,26 @@ class DirectFile implements IFile { } function getLastModified() { + $this->getFile(); + return $this->file->getMTime(); - // TODO: Implement getLastModified() method. + } + + private function getFile() { + if ($this->file === null) { + $userFolder = $this->rootFolder->getUserFolder($this->direct->getUserId()); + $files = $userFolder->getById($this->direct->getFileId()); + + //TODO check expiration + + if ($files === []) { + throw new NotFound(); + } + + $this->file = array_shift($files); + } + + return $this->file; } } diff --git a/apps/dav/lib/Direct/DirectHome.php b/apps/dav/lib/Direct/DirectHome.php index 05c72aa005..3440a55dc1 100644 --- a/apps/dav/lib/Direct/DirectHome.php +++ b/apps/dav/lib/Direct/DirectHome.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace OCA\DAV\Direct; +use OCA\DAV\Db\DirectMapper; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\Files\File; use OCP\Files\IRootFolder; use Sabre\DAV\Exception\Forbidden; @@ -35,8 +37,12 @@ class DirectHome implements ICollection { /** @var IRootFolder */ private $rootFolder; - public function __construct(IRootFolder $rootFolder) { + /** @var DirectMapper */ + private $mapper; + + public function __construct(IRootFolder $rootFolder, DirectMapper $mapper) { $this->rootFolder = $rootFolder; + $this->mapper = $mapper; } function createFile($name, $data = null) { @@ -47,8 +53,14 @@ class DirectHome implements ICollection { throw new Forbidden(); } - function getChild($name) { - throw new NotFound(); + public function getChild($name) { + try { + $direct = $this->mapper->getByToken($name); + + return new DirectFile($direct, $this->rootFolder); + } catch (DoesNotExistException $e) { + throw new NotFound(); + } } function getChildren() { diff --git a/apps/dav/lib/Direct/ServerFactory.php b/apps/dav/lib/Direct/ServerFactory.php index 85c1555021..9869e69710 100644 --- a/apps/dav/lib/Direct/ServerFactory.php +++ b/apps/dav/lib/Direct/ServerFactory.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace OCA\DAV\Direct; +use OCA\DAV\Db\DirectMapper; +use OCP\Files\IRootFolder; use OCP\IConfig; class ServerFactory { @@ -35,8 +37,10 @@ class ServerFactory { } public function createServer(string $baseURI, - string $requestURI) { - $home = new DirectHome(\OC::$server->getRootFolder()); + string $requestURI, + IRootFolder $rootFolder, + DirectMapper $mapper) { + $home = new DirectHome($rootFolder, $mapper); $server = new Server($home); $server->httpRequest->setUrl($requestURI); From 164d9988567ea46e88c6a968520a71b9b91315b8 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 13 Apr 2018 17:28:36 +0200 Subject: [PATCH 06/12] No listing Signed-off-by: Roeland Jago Douma --- apps/dav/lib/Direct/DirectHome.php | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/apps/dav/lib/Direct/DirectHome.php b/apps/dav/lib/Direct/DirectHome.php index 3440a55dc1..9a3ee58224 100644 --- a/apps/dav/lib/Direct/DirectHome.php +++ b/apps/dav/lib/Direct/DirectHome.php @@ -29,6 +29,7 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\Files\File; use OCP\Files\IRootFolder; use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; @@ -59,23 +60,14 @@ class DirectHome implements ICollection { return new DirectFile($direct, $this->rootFolder); } catch (DoesNotExistException $e) { + //TODO: throttle the ip to avoid brute forcing + throw new NotFound(); } } function getChildren() { - $adminFolder = $this->rootFolder->getUserFolder('admin'); - - $listing = $adminFolder->getDirectoryListing(); - - $res = []; - foreach ($listing as $file) { - if ($file instanceof File) { - $res[] = new DirectFile($file); - } - } - - return $res; + throw new MethodNotAllowed('Listing members of this collection is disabled'); } function childExists($name) { From 6a385dd20bad8d0e6c7d923f77eea7b5f719fddd Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 13 Apr 2018 17:40:52 +0200 Subject: [PATCH 07/12] Add directDownload support of storage Signed-off-by: Roeland Jago Douma --- apps/dav/lib/Controller/DirectController.php | 27 ++++++++++++-------- apps/dav/lib/Direct/DirectHome.php | 1 - 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/apps/dav/lib/Controller/DirectController.php b/apps/dav/lib/Controller/DirectController.php index c357771f3f..3b8b0d1e2a 100644 --- a/apps/dav/lib/Controller/DirectController.php +++ b/apps/dav/lib/Controller/DirectController.php @@ -87,23 +87,28 @@ class DirectController extends OCSController { } $file = array_shift($files); + $storage = $file->getStorage(); + $directDownload = $storage->getDirectDownload($file->getInternalPath()); - //TODO: try to get a url from the backend (like S3) + if (isset($directDownload['url'])) { + $url = $directDownload['url']; + } else { + // Fallback to our default implemenation + $direct = new Direct(); + $direct->setUserId($this->userId); + $direct->setFileId($fileId); + $token = $this->random->generate(60, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); + $direct->setToken($token); + $direct->setExpiration($this->timeFactory->getTime() + 60 * 60 * 8); - // Fallback to our default implemenation - $direct = new Direct(); - $direct->setUserId($this->userId); - $direct->setFileId($fileId); + $this->mapper->insert($direct); - $token = $this->random->generate(60, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS); - $direct->setToken($token); - $direct->setExpiration($this->timeFactory->getTime() + 60*60*8); - - $this->mapper->insert($direct); + $url = $this->urlGenerator->getAbsoluteURL('remote.php/direct/'.$token); + } return new DataResponse([ - 'url' => $this->urlGenerator->getAbsoluteURL('remote.php/direct/'.$token) + 'url' => $url, ]); } } diff --git a/apps/dav/lib/Direct/DirectHome.php b/apps/dav/lib/Direct/DirectHome.php index 9a3ee58224..247cca7a3c 100644 --- a/apps/dav/lib/Direct/DirectHome.php +++ b/apps/dav/lib/Direct/DirectHome.php @@ -26,7 +26,6 @@ namespace OCA\DAV\Direct; use OCA\DAV\Db\DirectMapper; use OCP\AppFramework\Db\DoesNotExistException; -use OCP\Files\File; use OCP\Files\IRootFolder; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\MethodNotAllowed; From 042340ccf6e7d6408390b91f6904de0425bb3c07 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Mon, 23 Apr 2018 22:15:29 +0200 Subject: [PATCH 08/12] Check if a direct link is expired Signed-off-by: Roeland Jago Douma --- apps/dav/lib/Direct/DirectFile.php | 20 +++++++++---------- apps/dav/lib/Direct/DirectHome.php | 32 ++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/apps/dav/lib/Direct/DirectFile.php b/apps/dav/lib/Direct/DirectFile.php index d327a1752b..947352c514 100644 --- a/apps/dav/lib/Direct/DirectFile.php +++ b/apps/dav/lib/Direct/DirectFile.php @@ -46,47 +46,47 @@ class DirectFile implements IFile { $this->rootFolder = $rootFolder; } - function put($data) { + public function put($data) { throw new Forbidden(); } - function get() { + public function get() { $this->getFile(); return $this->file->fopen('rb'); } - function getContentType() { + public function getContentType() { $this->getFile(); return $this->file->getMimeType(); } - function getETag() { + public function getETag() { $this->getFile(); return $this->file->getEtag(); } - function getSize() { + public function getSize() { $this->getFile(); return $this->file->getSize(); } - function delete() { + public function delete() { throw new Forbidden(); } - function getName() { + public function getName() { return $this->direct->getToken(); } - function setName($name) { + public function setName($name) { throw new Forbidden(); } - function getLastModified() { + public function getLastModified() { $this->getFile(); return $this->file->getMTime(); @@ -97,8 +97,6 @@ class DirectFile implements IFile { $userFolder = $this->rootFolder->getUserFolder($this->direct->getUserId()); $files = $userFolder->getById($this->direct->getFileId()); - //TODO check expiration - if ($files === []) { throw new NotFound(); } diff --git a/apps/dav/lib/Direct/DirectHome.php b/apps/dav/lib/Direct/DirectHome.php index 247cca7a3c..f56815746a 100644 --- a/apps/dav/lib/Direct/DirectHome.php +++ b/apps/dav/lib/Direct/DirectHome.php @@ -26,6 +26,7 @@ namespace OCA\DAV\Direct; use OCA\DAV\Db\DirectMapper; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\IRootFolder; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\MethodNotAllowed; @@ -40,23 +41,34 @@ class DirectHome implements ICollection { /** @var DirectMapper */ private $mapper; - public function __construct(IRootFolder $rootFolder, DirectMapper $mapper) { + /** @var ITimeFactory */ + private $timeFactory; + + public function __construct(IRootFolder $rootFolder, + DirectMapper $mapper, + ITimeFactory $timeFactory) { $this->rootFolder = $rootFolder; $this->mapper = $mapper; + $this->timeFactory = $timeFactory; } - function createFile($name, $data = null) { + public function createFile($name, $data = null) { throw new Forbidden(); } - function createDirectory($name) { + public function createDirectory($name) { throw new Forbidden(); } - public function getChild($name) { + public function getChild($name): DirectFile { try { $direct = $this->mapper->getByToken($name); + // Expired + if ($direct->getExpiration() >= $this->timeFactory->getTime()) { + throw new NotFound(); + } + return new DirectFile($direct, $this->rootFolder); } catch (DoesNotExistException $e) { //TODO: throttle the ip to avoid brute forcing @@ -65,27 +77,27 @@ class DirectHome implements ICollection { } } - function getChildren() { + public function getChildren() { throw new MethodNotAllowed('Listing members of this collection is disabled'); } - function childExists($name) { + public function childExists($name): bool { return false; } - function delete() { + public function delete() { throw new Forbidden(); } - function getName() { + public function getName(): string { return 'direct'; } - function setName($name) { + public function setName($name) { throw new Forbidden(); } - function getLastModified() { + public function getLastModified(): int { return 0; } From b6c58e75b754fc7a5f6873b51934be16a8365d8f Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Mon, 23 Apr 2018 22:32:41 +0200 Subject: [PATCH 09/12] Add backgroundjob to cleanup expired direct links Signed-off-by: Roeland Jago Douma --- apps/dav/appinfo/info.xml | 6 ++- .../composer/composer/autoload_classmap.php | 1 + .../dav/composer/composer/autoload_static.php | 1 + .../BackgroundJob/CleanupDirectLinksJob.php | 50 +++++++++++++++++++ apps/dav/lib/Db/DirectMapper.php | 18 ++++++- 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 apps/dav/lib/BackgroundJob/CleanupDirectLinksJob.php diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 4952fa3353..d31851fe17 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ WebDAV WebDAV endpoint WebDAV endpoint - 1.5.1 + 1.5.2 agpl owncloud.org DAV @@ -19,6 +19,10 @@ + + OCA\DAV\BackgroundJob\CleanupDirectLinksJob + + OCA\DAV\Migration\FixBirthdayCalendarComponent diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 1f48e74ca4..50689568eb 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -11,6 +11,7 @@ return array( 'OCA\\DAV\\Avatars\\AvatarHome' => $baseDir . '/../lib/Avatars/AvatarHome.php', 'OCA\\DAV\\Avatars\\AvatarNode' => $baseDir . '/../lib/Avatars/AvatarNode.php', 'OCA\\DAV\\Avatars\\RootCollection' => $baseDir . '/../lib/Avatars/RootCollection.php', + 'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => $baseDir . '/../lib/BackgroundJob/CleanupDirectLinksJob.php', 'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => $baseDir . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => $baseDir . '/../lib/CalDAV/Activity/Filter/Calendar.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index cba72bc03c..760ca3426f 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -26,6 +26,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Avatars\\AvatarHome' => __DIR__ . '/..' . '/../lib/Avatars/AvatarHome.php', 'OCA\\DAV\\Avatars\\AvatarNode' => __DIR__ . '/..' . '/../lib/Avatars/AvatarNode.php', 'OCA\\DAV\\Avatars\\RootCollection' => __DIR__ . '/..' . '/../lib/Avatars/RootCollection.php', + 'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectLinksJob.php', 'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Filter/Calendar.php', diff --git a/apps/dav/lib/BackgroundJob/CleanupDirectLinksJob.php b/apps/dav/lib/BackgroundJob/CleanupDirectLinksJob.php new file mode 100644 index 0000000000..ba1049071a --- /dev/null +++ b/apps/dav/lib/BackgroundJob/CleanupDirectLinksJob.php @@ -0,0 +1,50 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\BackgroundJob; + +use OC\BackgroundJob\TimedJob; +use OCA\DAV\Db\DirectMapper; +use OCP\AppFramework\Utility\ITimeFactory; + +class CleanupDirectLinksJob extends TimedJob { + /** @var ITimeFactory */ + private $timeFactory; + + /** @var DirectMapper */ + private $mapper; + + public function __construct(ITimeFactory $timeFactory, DirectMapper $mapper) { + $this->setInterval(60*60*24); + + $this->timeFactory = $timeFactory; + $this->mapper = $mapper; + } + + protected function run($argument) { + // Delete all shares expired 24 hours ago + $this->mapper->deleteExpired($this->timeFactory->getTime() - 60*60*24); + } + +} diff --git a/apps/dav/lib/Db/DirectMapper.php b/apps/dav/lib/Db/DirectMapper.php index 806b145823..d0db4b8287 100644 --- a/apps/dav/lib/Db/DirectMapper.php +++ b/apps/dav/lib/Db/DirectMapper.php @@ -34,6 +34,11 @@ class DirectMapper extends Mapper { parent::__construct($db, 'directlink', Direct::class); } + /** + * @param string $token + * @return Direct + * @throws DoesNotExistException + */ public function getByToken(string $token): Direct { $qb = $this->db->getQueryBuilder(); @@ -48,9 +53,20 @@ class DirectMapper extends Mapper { $cursor->closeCursor(); if ($data === false) { - throw new DoesNotExistException(); + throw new DoesNotExistException('Direct link with token does not exist'); } return Direct::fromRow($data); } + + public function deleteExpired(int $expiration) { + $qb = $this->db->getQueryBuilder(); + + $qb->delete('directlink') + ->where( + $qb->expr()->lt('expiration', $qb->createNamedParameter($expiration)) + ); + + $qb->execute(); + } } From 392337fa13028be2ef03f0f9d09ac224d8aa6818 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Mon, 23 Apr 2018 22:43:08 +0200 Subject: [PATCH 10/12] Throttle requests to unknown tokens Signed-off-by: Roeland Jago Douma --- apps/dav/appinfo/v2/direct.php | 5 ++++- apps/dav/lib/Direct/DirectHome.php | 18 ++++++++++++++++-- apps/dav/lib/Direct/ServerFactory.php | 10 ++++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/dav/appinfo/v2/direct.php b/apps/dav/appinfo/v2/direct.php index 633d69a3c3..3762a62830 100644 --- a/apps/dav/appinfo/v2/direct.php +++ b/apps/dav/appinfo/v2/direct.php @@ -38,7 +38,10 @@ $server = $serverFactory->createServer( $baseuri, $requestUri, \OC::$server->getRootFolder(), - \OC::$server->query(\OCA\DAV\Db\DirectMapper::class) + \OC::$server->query(\OCA\DAV\Db\DirectMapper::class), + \OC::$server->query(\OCP\AppFramework\Utility\ITimeFactory::class), + \OC::$server->getBruteForceThrottler(), + \OC::$server->getRequest() ); $server->exec(); diff --git a/apps/dav/lib/Direct/DirectHome.php b/apps/dav/lib/Direct/DirectHome.php index f56815746a..393adaddc9 100644 --- a/apps/dav/lib/Direct/DirectHome.php +++ b/apps/dav/lib/Direct/DirectHome.php @@ -24,10 +24,12 @@ declare(strict_types=1); namespace OCA\DAV\Direct; +use OC\Security\Bruteforce\Throttler; use OCA\DAV\Db\DirectMapper; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\IRootFolder; +use OCP\IRequest; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\Exception\NotFound; @@ -44,12 +46,22 @@ class DirectHome implements ICollection { /** @var ITimeFactory */ private $timeFactory; + /** @var Throttler */ + private $throttler; + + /** @var IRequest */ + private $request; + public function __construct(IRootFolder $rootFolder, DirectMapper $mapper, - ITimeFactory $timeFactory) { + ITimeFactory $timeFactory, + Throttler $throttler, + IRequest $request) { $this->rootFolder = $rootFolder; $this->mapper = $mapper; $this->timeFactory = $timeFactory; + $this->throttler = $throttler; + $this->request = $request; } public function createFile($name, $data = null) { @@ -71,7 +83,9 @@ class DirectHome implements ICollection { return new DirectFile($direct, $this->rootFolder); } catch (DoesNotExistException $e) { - //TODO: throttle the ip to avoid brute forcing + // Since the token space is so huge only throttle on non exsisting token + $this->throttler->registerAttempt('directlink', $this->request->getRemoteAddress()); + $this->throttler->sleepDelay($this->request->getRemoteAddress(), 'directlink'); throw new NotFound(); } diff --git a/apps/dav/lib/Direct/ServerFactory.php b/apps/dav/lib/Direct/ServerFactory.php index 9869e69710..618f6889fd 100644 --- a/apps/dav/lib/Direct/ServerFactory.php +++ b/apps/dav/lib/Direct/ServerFactory.php @@ -24,9 +24,12 @@ declare(strict_types=1); namespace OCA\DAV\Direct; +use OC\Security\Bruteforce\Throttler; use OCA\DAV\Db\DirectMapper; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\IRootFolder; use OCP\IConfig; +use OCP\IRequest; class ServerFactory { /** @var IConfig */ @@ -39,8 +42,11 @@ class ServerFactory { public function createServer(string $baseURI, string $requestURI, IRootFolder $rootFolder, - DirectMapper $mapper) { - $home = new DirectHome($rootFolder, $mapper); + DirectMapper $mapper, + ITimeFactory $timeFactory, + Throttler $throttler, + IRequest $request): Server { + $home = new DirectHome($rootFolder, $mapper, $timeFactory, $throttler, $request); $server = new Server($home); $server->httpRequest->setUrl($requestURI); From d5222d68f0180cb7072c28975fea95ac2a445e3d Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 25 Apr 2018 22:59:44 +0200 Subject: [PATCH 11/12] Add tests for DirectFile and DirectHome Signed-off-by: Roeland Jago Douma --- apps/dav/lib/Direct/DirectHome.php | 2 +- apps/dav/tests/unit/Direct/DirectFileTest.php | 132 +++++++++++++ apps/dav/tests/unit/Direct/DirectHomeTest.php | 182 ++++++++++++++++++ 3 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 apps/dav/tests/unit/Direct/DirectFileTest.php create mode 100644 apps/dav/tests/unit/Direct/DirectHomeTest.php diff --git a/apps/dav/lib/Direct/DirectHome.php b/apps/dav/lib/Direct/DirectHome.php index 393adaddc9..e0246c83de 100644 --- a/apps/dav/lib/Direct/DirectHome.php +++ b/apps/dav/lib/Direct/DirectHome.php @@ -77,7 +77,7 @@ class DirectHome implements ICollection { $direct = $this->mapper->getByToken($name); // Expired - if ($direct->getExpiration() >= $this->timeFactory->getTime()) { + if ($direct->getExpiration() < $this->timeFactory->getTime()) { throw new NotFound(); } diff --git a/apps/dav/tests/unit/Direct/DirectFileTest.php b/apps/dav/tests/unit/Direct/DirectFileTest.php new file mode 100644 index 0000000000..2203e7c768 --- /dev/null +++ b/apps/dav/tests/unit/Direct/DirectFileTest.php @@ -0,0 +1,132 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Tests\Unit\Direct; + +use OCA\DAV\Db\Direct; +use OCA\DAV\Direct\DirectFile; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use Sabre\DAV\Exception\Forbidden; +use Test\TestCase; + +class DirectFileTest extends TestCase { + + /** @var Direct */ + private $direct; + + /** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */ + private $rootFolder; + + /** @var Folder|\PHPUnit_Framework_MockObject_MockObject */ + private $userFolder; + + /** @var File|\PHPUnit_Framework_MockObject_MockObject */ + private $file; + + /** @var DirectFile */ + private $directFile; + + public function setUp() { + parent::setUp(); + + $this->direct = Direct::fromParams([ + 'userId' => 'directUser', + 'token' => 'directToken', + 'fileId' => 42, + ]); + + $this->rootFolder = $this->createMock(IRootFolder::class); + + $this->userFolder = $this->createMock(Folder::class); + $this->rootFolder->method('getUserFolder') + ->with('directUser') + ->willReturn($this->userFolder); + + $this->file = $this->createMock(File::class); + $this->userFolder->method('getById') + ->with(42) + ->willReturn([$this->file]); + + $this->directFile = new DirectFile($this->direct, $this->rootFolder); + } + + public function testPut() { + $this->expectException(Forbidden::class); + + $this->directFile->put('foo'); + } + + public function testGet() { + $this->file->expects($this->once()) + ->method('fopen') + ->with('rb'); + $this->directFile->get(); + } + + public function testGetContentType() { + $this->file->method('getMimeType') + ->willReturn('direct/type'); + + $this->assertSame('direct/type', $this->directFile->getContentType()); + } + + public function testGetETag() { + $this->file->method('getEtag') + ->willReturn('directEtag'); + + $this->assertSame('directEtag', $this->directFile->getETag()); + } + + public function testGetSize() { + $this->file->method('getSize') + ->willReturn(42); + + $this->assertSame(42, $this->directFile->getSize()); + } + + public function testDelete() { + $this->expectException(Forbidden::class); + + $this->directFile->delete(); + } + + public function testGetName() { + $this->assertSame('directToken', $this->directFile->getName()); + } + + public function testSetName() { + $this->expectException(Forbidden::class); + + $this->directFile->setName('foobar'); + } + + public function testGetLastModified() { + $this->file->method('getMTime') + ->willReturn(42); + + $this->assertSame(42, $this->directFile->getLastModified()); + } +} diff --git a/apps/dav/tests/unit/Direct/DirectHomeTest.php b/apps/dav/tests/unit/Direct/DirectHomeTest.php new file mode 100644 index 0000000000..dbbfb1fe1f --- /dev/null +++ b/apps/dav/tests/unit/Direct/DirectHomeTest.php @@ -0,0 +1,182 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Tests\Unit\Direct; + +use OC\Security\Bruteforce\Throttler; +use OCA\DAV\Db\Direct; +use OCA\DAV\Db\DirectMapper; +use OCA\DAV\Direct\DirectFile; +use OCA\DAV\Direct\DirectHome; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IRootFolder; +use OCP\IRequest; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\MethodNotAllowed; +use Sabre\DAV\Exception\NotFound; +use Test\TestCase; + +class DirectHomeTest extends TestCase { + + /** @var DirectMapper|\PHPUnit_Framework_MockObject_MockObject */ + private $directMapper; + + /** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */ + private $rootFolder; + + /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */ + private $timeFactory; + + /** @var Throttler|\PHPUnit_Framework_MockObject_MockObject */ + private $throttler; + + /** @var IRequest */ + private $request; + + /** @var DirectHome */ + private $directHome; + + public function setUp() { + parent::setUp(); + + $this->directMapper = $this->createMock(DirectMapper::class); + $this->rootFolder = $this->createMock(IRootFolder::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->throttler = $this->createMock(Throttler::class); + $this->request = $this->createMock(IRequest::class); + + $this->timeFactory->method('getTime') + ->willReturn(42); + + $this->request->method('getRemoteAddress') + ->willReturn('1.2.3.4'); + + $this->directHome = new DirectHome( + $this->rootFolder, + $this->directMapper, + $this->timeFactory, + $this->throttler, + $this->request + ); + } + + public function testCreateFile() { + $this->expectException(Forbidden::class); + + $this->directHome->createFile('foo', 'bar'); + } + + public function testCreateDirectory() { + $this->expectException(Forbidden::class); + + $this->directHome->createDirectory('foo'); + } + + public function testGetChildren() { + $this->expectException(MethodNotAllowed::class); + + $this->directHome->getChildren(); + } + + public function testChildExists() { + $this->assertFalse($this->directHome->childExists('foo')); + } + + public function testDelete() { + $this->expectException(Forbidden::class); + + $this->directHome->delete(); + } + + public function testGetName() { + $this->assertSame('direct', $this->directHome->getName()); + } + + public function testSetName() { + $this->expectException(Forbidden::class); + + $this->directHome->setName('foo'); + } + + public function testGetLastModified() { + $this->assertSame(0, $this->directHome->getLastModified()); + } + + public function testGetChildValid() { + $direct = Direct::fromParams([ + 'expiration' => 100, + ]); + + $this->directMapper->method('getByToken') + ->with('longtoken') + ->willReturn($direct); + + $this->throttler->expects($this->never()) + ->method($this->anything()); + + $result = $this->directHome->getChild('longtoken'); + $this->assertInstanceOf(DirectFile::class, $result); + } + + public function testGetChildExpired() { + $direct = Direct::fromParams([ + 'expiration' => 41, + ]); + + $this->directMapper->method('getByToken') + ->with('longtoken') + ->willReturn($direct); + + $this->throttler->expects($this->never()) + ->method($this->anything()); + + $this->expectException(NotFound::class); + + $this->directHome->getChild('longtoken'); + } + + public function testGetChildInvalid() { + $this->directMapper->method('getByToken') + ->with('longtoken') + ->willThrowException(new DoesNotExistException('not found')); + + $this->throttler->expects($this->once()) + ->method('registerAttempt') + ->with( + 'directlink', + '1.2.3.4' + ); + $this->throttler->expects($this->once()) + ->method('sleepDelay') + ->with( + '1.2.3.4', + 'directlink' + ); + + $this->expectException(NotFound::class); + + $this->directHome->getChild('longtoken'); + } +} From 1c75ddac45845d9e91997e6253e36b9ae2556e91 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Thu, 26 Apr 2018 10:34:57 +0200 Subject: [PATCH 12/12] Improve the directContoller * Tests * No directdownload from storage yet (as it is not tested at all) * No direct links for folders Signed-off-by: Roeland Jago Douma --- apps/dav/lib/Controller/DirectController.php | 35 ++-- .../unit/Controller/DirectControllerTest.php | 155 ++++++++++++++++++ 2 files changed, 172 insertions(+), 18 deletions(-) create mode 100644 apps/dav/tests/unit/Controller/DirectControllerTest.php diff --git a/apps/dav/lib/Controller/DirectController.php b/apps/dav/lib/Controller/DirectController.php index 3b8b0d1e2a..2a14e4db2c 100644 --- a/apps/dav/lib/Controller/DirectController.php +++ b/apps/dav/lib/Controller/DirectController.php @@ -27,9 +27,11 @@ namespace OCA\DAV\Controller; use OCA\DAV\Db\Direct; use OCA\DAV\Db\DirectMapper; use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSBadRequestException; use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\AppFramework\OCSController; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\File; use OCP\Files\IRootFolder; use OCP\IRequest; use OCP\IURLGenerator; @@ -87,26 +89,23 @@ class DirectController extends OCSController { } $file = array_shift($files); - $storage = $file->getStorage(); - $directDownload = $storage->getDirectDownload($file->getInternalPath()); - - if (isset($directDownload['url'])) { - $url = $directDownload['url']; - } else { - // Fallback to our default implemenation - $direct = new Direct(); - $direct->setUserId($this->userId); - $direct->setFileId($fileId); - - $token = $this->random->generate(60, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); - $direct->setToken($token); - $direct->setExpiration($this->timeFactory->getTime() + 60 * 60 * 8); - - $this->mapper->insert($direct); - - $url = $this->urlGenerator->getAbsoluteURL('remote.php/direct/'.$token); + if (!($file instanceof File)) { + throw new OCSBadRequestException('Direct download only works for files'); } + //TODO: at some point we should use the directdownlaod function of storages + $direct = new Direct(); + $direct->setUserId($this->userId); + $direct->setFileId($fileId); + + $token = $this->random->generate(60, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); + $direct->setToken($token); + $direct->setExpiration($this->timeFactory->getTime() + 60 * 60 * 8); + + $this->mapper->insert($direct); + + $url = $this->urlGenerator->getAbsoluteURL('remote.php/direct/'.$token); + return new DataResponse([ 'url' => $url, ]); diff --git a/apps/dav/tests/unit/Controller/DirectControllerTest.php b/apps/dav/tests/unit/Controller/DirectControllerTest.php new file mode 100644 index 0000000000..e52c67ac30 --- /dev/null +++ b/apps/dav/tests/unit/Controller/DirectControllerTest.php @@ -0,0 +1,155 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Tests\Unit\DAV\Controller; + +use OCA\DAV\Controller\DirectController; +use OCA\DAV\Db\Direct; +use OCA\DAV\Db\DirectMapper; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\AppFramework\OCS\OCSNotFoundException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\Security\ISecureRandom; +use Test\TestCase; + +class DirectControllerTest extends TestCase { + + /** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */ + private $rootFolder; + + /** @var DirectMapper|\PHPUnit_Framework_MockObject_MockObject */ + private $directMapper; + + /** @var ISecureRandom|\PHPUnit_Framework_MockObject_MockObject */ + private $random; + + /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */ + private $timeFactory; + + /** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */ + private $urlGenerator; + + /** @var DirectController */ + private $controller; + + public function setUp() { + parent::setUp(); + + $this->rootFolder = $this->createMock(IRootFolder::class); + $this->directMapper = $this->createMock(DirectMapper::class); + $this->random = $this->createMock(ISecureRandom::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + + $this->controller = new DirectController( + 'dav', + $this->createMock(IRequest::class), + $this->rootFolder, + 'awesomeUser', + $this->directMapper, + $this->random, + $this->timeFactory, + $this->urlGenerator + ); + } + + public function testGetUrlNonExistingFileId() { + $userFolder = $this->createMock(Folder::class); + $this->rootFolder->method('getUserFolder') + ->with('awesomeUser') + ->willReturn($userFolder); + + $userFolder->method('getById') + ->with(101) + ->willReturn([]); + + $this->expectException(OCSNotFoundException::class); + $this->controller->getUrl(101); + } + + public function testGetUrlForFolder() { + $userFolder = $this->createMock(Folder::class); + $this->rootFolder->method('getUserFolder') + ->with('awesomeUser') + ->willReturn($userFolder); + + $folder = $this->createMock(Folder::class); + + $userFolder->method('getById') + ->with(101) + ->willReturn([$folder]); + + $this->expectException(OCSBadRequestException::class); + $this->controller->getUrl(101); + } + + public function testGetUrlValid() { + $userFolder = $this->createMock(Folder::class); + $this->rootFolder->method('getUserFolder') + ->with('awesomeUser') + ->willReturn($userFolder); + + $file = $this->createMock(File::class); + + $this->timeFactory->method('getTime') + ->willReturn(42); + + $userFolder->method('getById') + ->with(101) + ->willReturn([$file]); + + $this->random->method('generate') + ->with( + 60, + ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS + )->willReturn('superduperlongtoken'); + + $this->directMapper->expects($this->once()) + ->method('insert') + ->willReturnCallback(function (Direct $direct) { + $this->assertSame('awesomeUser', $direct->getUserId()); + $this->assertSame(101, $direct->getFileId()); + $this->assertSame('superduperlongtoken', $direct->getToken()); + $this->assertSame(42 + 60*60*8, $direct->getExpiration()); + }); + + $this->urlGenerator->method('getAbsoluteURL') + ->willReturnCallback(function(string $url) { + return 'https://my.nextcloud/'.$url; + }); + + $result = $this->controller->getUrl(101); + + $this->assertInstanceOf(DataResponse::class, $result); + $this->assertSame([ + 'url' => 'https://my.nextcloud/remote.php/direct/superduperlongtoken', + ], $result->getData()); + } +}