Skip to content

Commit

Permalink
Merge pull request #38376 from owncloud/owncloud-web-password-public-…
Browse files Browse the repository at this point in the history
…share

pre-signed download urls for password protected public links
  • Loading branch information
micbar authored Mar 5, 2021
2 parents 8abe964 + 6ea3fb0 commit 33ebff6
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 0 deletions.
51 changes: 51 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,57 @@ public function propFind(PropFind $propFind, INode $node) {
$propFind->handle(FilesPlugin::SIZE_PROPERTYNAME, static function () use ($node) {
return $node->getNode()->getSize();
});
if ($node->getNode()->getType() === FileInfo::TYPE_FILE) {
$server = $this->server;
$propFind->handle(FilesPlugin::DOWNLOADURL_PROPERTYNAME, static function () use ($node, $server) {
$share = $node->getShare();
$shareNode = $share->getNode();
// We want to get the relative path of the shared file.
// If the shared resource is a folder e.g.
// - <shared folder>/
// - subfolder/
// - meme.jpg
// - somefile.txt
// And we want the path of 'meme.jpg' we expect the resource
// path to be '/subfolder/meme.jpg'.
// If the shared resource is a file we can just take the
// name of that file prefixed with a slash like '/cool.gif'
if ($shareNode->getType() === FileInfo::TYPE_FOLDER) {
$shareRoot = $shareNode->getPath();
$sharedResourcePath = \substr($node->getNode()->getPath(), \strlen($shareRoot));
} else {
$sharedResourcePath = '/' . $shareNode->getName();
}

$path = $server->getBaseUri() . $server->getRequestUri();
// Let's assume we have this share
// - <shared folder>/
// - subfolder/
// - meme.jpg
// - somefile.txt
// If the PROPFIND request is done against
// 'remote.php/dav/public-files/{token}/subfolder/meme.jpg'
// then we don't need to append the file name for response as
// it is already in the path.
// Otherwise if the PROPFIND is done against
// 'remote.php/dav/public-files/{token}/subfolder/'
// then we need to append the file name to the path.
if (\substr($path, -\strlen($sharedResourcePath)) !== $sharedResourcePath) {
$path .= '/' . $node->getNode()->getName();
}

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

$validUntil = new \DateTime();
$validUntil->add(new \DateInterval("PT30M")); // valid for 30 minutes
$key = \hash_hkdf('sha256', $share->getPassword());

$s = new PublicShareSigner($share->getToken(), $sharedResourcePath, $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 (\hash_equals($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 33ebff6

Please sign in to comment.