Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add directdownload endpoint #9178

Merged
merged 12 commits into from
May 1, 2018
6 changes: 5 additions & 1 deletion apps/dav/appinfo/info.xml
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
<name>WebDAV</name>
<summary>WebDAV endpoint</summary>
<description>WebDAV endpoint</description>
<version>1.5.0</version>
<version>1.5.2</version>
<licence>agpl</licence>
<author>owncloud.org</author>
<namespace>DAV</namespace>
@@ -19,6 +19,10 @@
<nextcloud min-version="14" max-version="14" />
</dependencies>

<background-jobs>
<job>OCA\DAV\BackgroundJob\CleanupDirectLinksJob</job>
</background-jobs>

<repair-steps>
<post-migration>
<step>OCA\DAV\Migration\FixBirthdayCalendarComponent</step>
5 changes: 4 additions & 1 deletion apps/dav/appinfo/routes.php
Original file line number Diff line number Diff line change
@@ -25,5 +25,8 @@
'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'],
],
];
47 changes: 47 additions & 0 deletions apps/dav/appinfo/v2/direct.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

// 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,
\OC::$server->getRootFolder(),
\OC::$server->query(\OCA\DAV\Db\DirectMapper::class),
\OC::$server->query(\OCP\AppFramework\Utility\ITimeFactory::class),
\OC::$server->getBruteForceThrottler(),
\OC::$server->getRequest()
);

$server->exec();
9 changes: 9 additions & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
'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',
@@ -109,6 +110,7 @@
'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 +120,12 @@
'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 +141,7 @@
'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',
9 changes: 9 additions & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
@@ -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',
@@ -124,6 +125,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 +135,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 +156,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',
50 changes: 50 additions & 0 deletions apps/dav/lib/BackgroundJob/CleanupDirectLinksJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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);
}

}
113 changes: 113 additions & 0 deletions apps/dav/lib/Controller/DirectController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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;
use OCP\Security\ISecureRandom;

class DirectController extends OCSController {

/** @var IRootFolder */
private $rootFolder;

/** @var string */
private $userId;

/** @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,
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;
}

/**
* @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);
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,
]);
}
}
58 changes: 58 additions & 0 deletions apps/dav/lib/Db/Direct.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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');
}
}
72 changes: 72 additions & 0 deletions apps/dav/lib/Db/DirectMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\DAV\Db;

use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Mapper;
use OCP\IDBConnection;

class DirectMapper extends Mapper {

public function __construct(IDBConnection $db) {
parent::__construct($db, 'directlink', Direct::class);
}

/**
* @param string $token
* @return Direct
* @throws DoesNotExistException
*/
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('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();
}
}
110 changes: 110 additions & 0 deletions apps/dav/lib/Direct/DirectFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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(Direct $direct, IRootFolder $rootFolder) {
$this->direct = $direct;
$this->rootFolder = $rootFolder;
}

public function put($data) {
throw new Forbidden();
}

public function get() {
$this->getFile();

return $this->file->fopen('rb');
}

public function getContentType() {
$this->getFile();

return $this->file->getMimeType();
}

public function getETag() {
$this->getFile();

return $this->file->getEtag();
}

public function getSize() {
$this->getFile();

return $this->file->getSize();
}

public function delete() {
throw new Forbidden();
}

public function getName() {
return $this->direct->getToken();
}

public function setName($name) {
throw new Forbidden();
}

public function getLastModified() {
$this->getFile();

return $this->file->getMTime();
}

private function getFile() {
if ($this->file === null) {
$userFolder = $this->rootFolder->getUserFolder($this->direct->getUserId());
$files = $userFolder->getById($this->direct->getFileId());

if ($files === []) {
throw new NotFound();
}

$this->file = array_shift($files);
}

return $this->file;
}

}
118 changes: 118 additions & 0 deletions apps/dav/lib/Direct/DirectHome.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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;
use Sabre\DAV\ICollection;

class DirectHome implements ICollection {

/** @var IRootFolder */
private $rootFolder;

/** @var DirectMapper */
private $mapper;

/** @var ITimeFactory */
private $timeFactory;

/** @var Throttler */
private $throttler;

/** @var IRequest */
private $request;

public function __construct(IRootFolder $rootFolder,
DirectMapper $mapper,
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) {
throw new Forbidden();
}

public function createDirectory($name) {
throw new Forbidden();
}

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) {
// 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();
}
}

public function getChildren() {
throw new MethodNotAllowed('Listing members of this collection is disabled');
}

public function childExists($name): bool {
return false;
}

public function delete() {
throw new Forbidden();
}

public function getName(): string {
return 'direct';
}

public function setName($name) {
throw new Forbidden();
}

public function getLastModified(): int {
return 0;
}

}
33 changes: 33 additions & 0 deletions apps/dav/lib/Direct/Server.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\DAV\Direct;

class Server extends \Sabre\DAV\Server {
public function __construct($treeOrNode = null) {
parent::__construct($treeOrNode);
self::$exposeVersion = false;
$this->enablePropfindDepthInfinityf = false;
}
}
61 changes: 61 additions & 0 deletions apps/dav/lib/Direct/ServerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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 */
private $config;

public function __construct(IConfig $config) {
$this->config = $config;
}

public function createServer(string $baseURI,
string $requestURI,
IRootFolder $rootFolder,
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);
$server->setBaseUri($baseURI);

$server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config));

return $server;


}
}
80 changes: 80 additions & 0 deletions apps/dav/lib/Migration/Version1005Date20180413093149.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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;
}
}
}
155 changes: 155 additions & 0 deletions apps/dav/tests/unit/Controller/DirectControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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());
}
}
132 changes: 132 additions & 0 deletions apps/dav/tests/unit/Direct/DirectFileTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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());
}
}
182 changes: 182 additions & 0 deletions apps/dav/tests/unit/Direct/DirectHomeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\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');
}
}
1 change: 1 addition & 0 deletions remote.php
Original file line number Diff line number Diff line change
@@ -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];