X Tutup
Skip to content

Commit 3c29599

Browse files
author
David Christofas
committed
pre-signed download urls for password protected public links
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.
1 parent 2056240 commit 3c29599

File tree

5 files changed

+134
-0
lines changed

5 files changed

+134
-0
lines changed

apps/dav/lib/Files/PublicFiles/PublicFilesPlugin.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,35 @@ public function propFind(PropFind $propFind, INode $node) {
147147
$propFind->handle(FilesPlugin::SIZE_PROPERTYNAME, static function () use ($node) {
148148
return $node->getNode()->getSize();
149149
});
150+
$server = $this->server;
151+
$propFind->handle(FilesPlugin::DOWNLOADURL_PROPERTYNAME, static function () use ($node, $server) {
152+
if ($node->getNode()->getType() === FileInfo::TYPE_FOLDER) {
153+
return "";
154+
}
155+
$path = $server->getBaseUri() . $server->getRequestUri();
156+
$nodeName = $node->getNode()->getName();
157+
if (\substr($path, -\strlen($nodeName)) !== $nodeName) {
158+
$path .= '/' . $nodeName;
159+
}
160+
161+
$share = $node->getShare();
162+
if ($share->getPassword() === null) {
163+
return $path;
164+
}
165+
166+
$validUntil = new \DateTime();
167+
$validUntil->add(new \DateInterval("PT30M")); // valid for 30 minutes
168+
$key = \hash_hkdf('sha256', $share->getPassword());
169+
170+
if ($share->getNode()->getType() === FileInfo::TYPE_FOLDER) {
171+
$shareRoot = $share->getNode()->getPath();
172+
$shared_resource_path = \substr($node->getNode()->getPath(), \strlen($shareRoot));
173+
} else {
174+
$shared_resource_path = '/' . $share->getNode()->getName();
175+
}
176+
$s = new PublicShareSigner($share->getToken(), $shared_resource_path, $validUntil, $key);
177+
return $path . '?signature=' . $s->getSignature() . '&expires=' . \urlencode($validUntil->format(\DateTime::ATOM));
178+
});
150179
}
151180
}
152181

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* @author David Christofas <dchristofas@owncloud.com>
4+
*
5+
* @copyright Copyright (c) 2021, ownCloud GmbH
6+
* @license AGPL-3.0
7+
*
8+
* This code is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License, version 3,
10+
* as published by the Free Software Foundation.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License, version 3,
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>
19+
*
20+
*/
21+
22+
namespace OCA\DAV\Files\PublicFiles;
23+
24+
class PublicShareSigner {
25+
private $token;
26+
private $fileName;
27+
private $validUntil;
28+
private $signingKey;
29+
30+
public function __construct(String $token, String $fileName, \DateTime $validUntil, String $signingKey) {
31+
$this->token = $token;
32+
$this->fileName = $fileName;
33+
$this->validUntil = $validUntil->format(\DateTime::ATOM);
34+
$this->signingKey = $signingKey;
35+
}
36+
37+
public function getSignature() {
38+
return \hash_hmac('sha512/256', \implode('|', [$this->token, $this->fileName, $this->validUntil]), $this->signingKey, false);
39+
}
40+
}

apps/dav/lib/Files/PublicFiles/PublicSharingAuth.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ public function check(RequestInterface $request, ResponseInterface $response) {
101101
return [true, 'principals/system/public'];
102102
}
103103

104+
// Clients which don't use cookie based session authentication and want
105+
// to use anchor tags `<a href...` to download password protected files
106+
// can't add the basic authentication header.
107+
// They can use pre-signed urls instead.
108+
$query = $request->getQueryParameters();
109+
if (isset($query['signature'], $query['expires'])) {
110+
$sig = $query['signature'];
111+
$validUntil = \DateTime::createFromFormat(\DateTime::ATOM, $query['expires']);
112+
$now = new \DateTime();
113+
if ($now < $validUntil) {
114+
$key = \hash_hkdf('sha256', $this->share->getPassword());
115+
$resource_path = \explode($this->share->getToken(), $request->getPath())[1];
116+
$s = new PublicShareSigner($this->share->getToken(), $resource_path, $validUntil, $key);
117+
if (\hash_equals($s->getSignature(), $sig)) {
118+
return [true, 'principals/system/public'];
119+
}
120+
}
121+
}
122+
104123
try {
105124
return parent::check($request, $response);
106125
} catch (LoginException $e) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
/**
3+
* @author David Christofas <dchristofas@owncloud.com>
4+
*
5+
* @copyright Copyright (c) 2021, ownCloud GmbH
6+
* @license AGPL-3.0
7+
*
8+
* This code is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License, version 3,
10+
* as published by the Free Software Foundation.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License, version 3,
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>
19+
*
20+
*/
21+
22+
namespace OCA\DAV\Tests\Unit\Files\PublicFiles;
23+
24+
use OCA\DAV\Files\PublicFiles\PublicShareSigner;
25+
use Test\TestCase;
26+
27+
class PublicShareSignerTest extends TestCase {
28+
public function testGet() {
29+
$s = new PublicShareSigner('someToken', 'someFileName', new \DateTime(), 'somekey');
30+
$hash = $s->getSignature();
31+
self::assertIsString($hash);
32+
self::assertEquals(64, \strlen($hash));
33+
}
34+
35+
public function testVerify() {
36+
$expectedHash = 'd67966402971bd3eb18aea62faf122a30e2dd5c9101aa9e106a56574cc535c6c';
37+
$date = \DateTime::createFromFormat(\DateTime::ATOM, '2009-01-03T18:15:05Z');
38+
$s = new PublicShareSigner('someToken', 'someFileName', $date, 'somekey');
39+
self::assertEquals($expectedHash, $s->getSignature());
40+
}
41+
}

changelog/unreleased/38376

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Enhancement: Implement pre-signed download urls for public links
2+
3+
Added pre-signed download urls for password protected public links to support clients which don't use cookies.
4+
5+
https://github.com/owncloud/core/pull/38376

0 commit comments

Comments
 (0)
X Tutup