Skip to content

Commit

Permalink
pre-signed download urls for password protected public links
Browse files Browse the repository at this point in the history
To support clients which don't use cookies I implemented pre-signed urls
for password protected public links. The share password is used as the
signing key and the signed url is then added to the propfind response in
the field `downloadURL` which was added before but never used. This
change allows owncloud Web to implement a more efficient download
mechanism for password protected link shares.
  • Loading branch information
David Christofas committed Feb 17, 2021
1 parent 2056240 commit 759b637
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 0 deletions.
24 changes: 24 additions & 0 deletions apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,30 @@ public function propFind(PropFind $propFind, INode $node) {
$propFind->handle(FilesPlugin::SIZE_PROPERTYNAME, static function () use ($node) {
return $node->getNode()->getSize();
});
$server = $this->server;
$propFind->handle(FilesPlugin::DOWNLOADURL_PROPERTYNAME, static function () use ($node, $server) {
if ($node->getNode()->getType() === FileInfo::TYPE_FOLDER) {
return "";
}
$path = $server->getBaseUri() . $server->getRequestUri();
$nodeName = $node->getNode()->getName();
if (\substr($path, -\strlen($nodeName)) !== $nodeName) {
$path .= '/' . $nodeName;
}

$share = $node->getShare();
if ($share->getPassword() === null) {
return $path;
}

$validUntil = new \DateTime();
$validUntil->add(new \DateInterval("PT30M")); // valid for 30 minutes
$key = \hash_hkdf('sha256', $share->getPassword());
$shareRoot = $share->getNode()->getPath();
$shared_resource_path = \substr($node->getNode()->getPath(), \strlen($shareRoot));
$s = new PublicShareSigner($share->getToken(), $shared_resource_path, $validUntil, $key);
return $path . '?signature=' . $s->getSignature() . '&expires=' . \urlencode($validUntil->format(\DateTime::ATOM));
});
}
}

Expand Down
40 changes: 40 additions & 0 deletions apps/dav/lib/Files/PublicFiles/PublicShareSigner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/**
* @author David Christofas <[email protected]>
*
* @copyright Copyright (c) 2021, ownCloud GmbH
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\DAV\Files\PublicFiles;

class PublicShareSigner {
private $token;
private $fileName;
private $validUntil;
private $signingKey;

public function __construct(String $token, String $fileName, \DateTime $validUntil, String $signingKey) {
$this->token = $token;
$this->fileName = $fileName;
$this->validUntil = $validUntil->format(\DateTime::ATOM);
$this->signingKey = $signingKey;
}

public function getSignature() {
return \hash_hmac('sha512/256', \implode('|', [$this->token, $this->fileName, $this->validUntil]), $this->signingKey, false);
}
}
19 changes: 19 additions & 0 deletions apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,25 @@ public function check(RequestInterface $request, ResponseInterface $response) {
return [true, 'principals/system/public'];
}

// Clients which don't use cookie based session authentication and want
// to use anchor tags `<a href...` to download password protected files
// can't add the basic authentication header.
// They can use pre-signed urls instead.
$query = $request->getQueryParameters();
if (isset($query['signature'], $query['expires'])) {
$sig = $query['signature'];
$validUntil = \DateTime::createFromFormat(\DateTime::ATOM, $query['expires']);
$now = new \DateTime();
if ($now < $validUntil) {
$key = \hash_hkdf('sha256', $this->share->getPassword());
$resource_path = \explode($this->share->getToken(), $request->getPath())[1];
$s = new PublicShareSigner($this->share->getToken(), $resource_path, $validUntil, $key);
if ($s->getSignature() === $sig) {
return [true, 'principals/system/public'];
}
}
}

try {
return parent::check($request, $response);
} catch (LoginException $e) {
Expand Down
41 changes: 41 additions & 0 deletions apps/dav/tests/unit/Files/PublicFiles/PublicShareSignerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
/**
* @author David Christofas <[email protected]>
*
* @copyright Copyright (c) 2021, ownCloud GmbH
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\DAV\Tests\Unit\Files\PublicFiles;

use OCA\DAV\Files\PublicFiles\PublicShareSigner;
use Test\TestCase;

class PublicShareSignerTest extends TestCase {
public function testGet() {
$s = new PublicShareSigner('someToken', 'someFileName', new \DateTime(), 'somekey');
$hash = $s->getSignature();
self::assertIsString($hash);
self::assertEquals(64, \strlen($hash));
}

public function testVerify() {
$expectedHash = 'd67966402971bd3eb18aea62faf122a30e2dd5c9101aa9e106a56574cc535c6c';
$date = \DateTime::createFromFormat(\DateTime::ATOM, '2009-01-03T18:15:05Z');
$s = new PublicShareSigner('someToken', 'someFileName', $date, 'somekey');
self::assertEquals($expectedHash, $s->getSignature());
}
}
5 changes: 5 additions & 0 deletions changelog/unreleased/38376
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Implement pre-signed download urls for public links

Added pre-signed download urls for password protected public links to support clients which don't use cookies.

https://github.com/owncloud/core/pull/38376

0 comments on commit 759b637

Please sign in to comment.