X Tutup
Skip to content

Commit db2cad6

Browse files
committed
Support signed urls
1 parent 8095322 commit db2cad6

File tree

11 files changed

+239
-10
lines changed

11 files changed

+239
-10
lines changed

apps/dav/appinfo/v1/caldav.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
\OC::$server->getRequest(),
4040
\OC::$server->getTwoFactorAuthManager(),
4141
\OC::$server->getAccountModuleManager(),
42+
\OC::$server->getConfig(),
4243
'principals/'
4344
);
4445
$principalBackend = new Principal(

apps/dav/appinfo/v1/carddav.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
\OC::$server->getRequest(),
4040
\OC::$server->getTwoFactorAuthManager(),
4141
\OC::$server->getAccountModuleManager(),
42+
\OC::$server->getConfig(),
4243
'principals/'
4344
);
4445
$principalBackend = new Principal(

apps/dav/appinfo/v1/webdav.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
\OC::$server->getRequest(),
4949
\OC::$server->getTwoFactorAuthManager(),
5050
\OC::$server->getAccountModuleManager(),
51+
\OC::$server->getConfig(),
5152
'principals/'
5253
);
5354
$requestUri = \OC::$server->getRequest()->getRequestUri();

apps/dav/lib/Connector/Sabre/Auth.php

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@
2929
*/
3030
namespace OCA\DAV\Connector\Sabre;
3131

32-
use Exception;
3332
use OC\AppFramework\Http\Request;
3433
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
3534
use OC\Authentication\TwoFactorAuth\Manager;
3635
use OC\Authentication\AccountModule\Manager as AccountModuleManager;
36+
use OC\Security\SignedUrl\Verifier;
3737
use OC\User\LoginException;
3838
use OC\User\Session;
3939
use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden;
4040
use OCP\Authentication\Exceptions\AccountCheckException;
41+
use OCP\IConfig;
4142
use OCP\IRequest;
4243
use OCP\ISession;
4344
use Sabre\DAV\Auth\Backend\AbstractBasic;
@@ -47,34 +48,38 @@
4748
use Sabre\HTTP\ResponseInterface;
4849

4950
class Auth extends AbstractBasic {
50-
const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND';
51+
public const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND';
5152

5253
/** @var ISession */
5354
private $session;
5455
/** @var Session */
5556
private $userSession;
5657
/** @var IRequest */
5758
private $request;
58-
/** @var string */
59-
private $currentUser;
6059
/** @var Manager */
6160
private $twoFactorManager;
6261
/** @var AccountModuleManager */
6362
private $accountModuleManager;
63+
/**
64+
* @var IConfig
65+
*/
66+
private $config;
6467

6568
/**
6669
* @param ISession $session
6770
* @param Session $userSession
6871
* @param IRequest $request
6972
* @param Manager $twoFactorManager
7073
* @param AccountModuleManager $accountModuleManager
74+
* @param IConfig $config
7175
* @param string $principalPrefix
7276
*/
7377
public function __construct(ISession $session,
7478
Session $userSession,
7579
IRequest $request,
7680
Manager $twoFactorManager,
7781
AccountModuleManager $accountModuleManager,
82+
IConfig $config,
7883
$principalPrefix = 'principals/users/') {
7984
$this->session = $session;
8085
$this->userSession = $userSession;
@@ -86,6 +91,7 @@ public function __construct(ISession $session,
8691
// setup realm
8792
$defaults = new \OC_Defaults();
8893
$this->realm = $defaults->getName();
94+
$this->config = $config;
8995
}
9096

9197
/**
@@ -158,7 +164,7 @@ public function check(RequestInterface $request, ResponseInterface $response) {
158164
throw new NotAuthenticated($e->getMessage(), $e->getCode(), $e);
159165
} catch (NotAuthenticated $e) {
160166
throw $e;
161-
} catch (Exception $e) {
167+
} catch (\Exception $e) {
162168
$class = \get_class($e);
163169
$msg = $e->getMessage();
164170
throw new ServiceUnavailable("$class: $msg");
@@ -205,6 +211,7 @@ private function requiresCSRFCheck() {
205211
* @return array
206212
* @throws NotAuthenticated
207213
* @throws ServiceUnavailable
214+
* @throws LoginException
208215
*/
209216
private function auth(RequestInterface $request, ResponseInterface $response) {
210217
$forcedLogout = false;
@@ -230,7 +237,6 @@ private function auth(RequestInterface $request, ResponseInterface $response) {
230237
$this->checkAccountModule($user);
231238
$uid = $user->getUID();
232239
\OC_Util::setupFS($uid);
233-
$this->currentUser = $uid;
234240
$this->session->close();
235241
return [true, $this->principalPrefix . $uid];
236242
}
@@ -246,14 +252,37 @@ private function auth(RequestInterface $request, ResponseInterface $response) {
246252
$startPos = \strrpos($data[1], '/') + 1;
247253
$data[1] = \substr_replace($data[1], $user->getUID(), $startPos);
248254
}
255+
256+
// signed url handling
257+
$verifier = new Verifier($request, $this->config);
258+
if ($verifier->isSignedRequest()) {
259+
if (!$verifier->signedRequestIsValid()) {
260+
return [false, 'Invalid url signature'];
261+
}
262+
// TODO: setup session ???
263+
$urlCredential = $verifier->getUrlCredential();
264+
$user = \OC::$server->getUserManager()->get($urlCredential);
265+
if ($user === null) {
266+
$message = \OC::$server->getL10N('dav')->t('User unknown');
267+
throw new LoginException($message);
268+
}
269+
if (!$user->isEnabled()) {
270+
$message = \OC::$server->getL10N('dav')->t('User disabled');
271+
throw new LoginException($message);
272+
}
273+
$this->userSession->setUser($user);
274+
\OC_Util::setupFS($urlCredential);
275+
$this->session->close();
276+
return [true, $this->principalPrefix . $urlCredential];
277+
}
249278
return $data;
250279
}
251280

252281
/**
253282
* @param $user
254283
* @throws ServiceUnavailable
255284
*/
256-
private function checkAccountModule($user) {
285+
private function checkAccountModule($user): void {
257286
if ($user === null) {
258287
throw new \UnexpectedValueException('No user in session');
259288
}
@@ -264,7 +293,7 @@ private function checkAccountModule($user) {
264293
}
265294
}
266295

267-
public function challenge(RequestInterface $request, ResponseInterface $response) {
296+
public function challenge(RequestInterface $request, ResponseInterface $response): void {
268297
$schema = 'Basic';
269298
// do not re-authenticate over ajax, use dummy auth name to prevent browser popup
270299
if (\in_array('XMLHttpRequest', \explode(',', $request->getHeader('X-Requested-With')), true)) {

apps/dav/lib/Server.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ public function __construct(IRequest $request, $baseUri) {
119119
OC::$server->getUserSession(),
120120
OC::$server->getRequest(),
121121
OC::$server->getTwoFactorAuthManager(),
122-
OC::$server->getAccountModuleManager()
122+
OC::$server->getAccountModuleManager(),
123+
\OC::$server->getConfig()
123124
);
124125

125126
// Set URL explicitly due to reverse-proxy situations

apps/dav/tests/unit/Connector/Sabre/AuthTest.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use OC\User\LoginException;
3434
use OC\User\Session;
3535
use OCA\DAV\Connector\Sabre\Auth;
36+
use OCP\IConfig;
3637
use OCP\IRequest;
3738
use OCP\ISession;
3839
use OCP\IUser;
@@ -61,6 +62,10 @@ class AuthTest extends TestCase {
6162
private $twoFactorManager;
6263
/** @var AccountModuleManager | MockObject */
6364
private $accountModuleManager;
65+
/**
66+
* @var IConfig|MockObject
67+
*/
68+
private $config;
6469

6570
public function setUp(): void {
6671
parent::setUp();
@@ -69,12 +74,14 @@ public function setUp(): void {
6974
$this->request = $this->createMock(IRequest::class);
7075
$this->twoFactorManager = $this->createMock(Manager::class);
7176
$this->accountModuleManager = $this->createMock(AccountModuleManager::class);
77+
$this->config = $this->createMock(IConfig::class);
7278
$this->auth = new Auth(
7379
$this->session,
7480
$this->userSession,
7581
$this->request,
7682
$this->twoFactorManager,
77-
$this->accountModuleManager
83+
$this->accountModuleManager,
84+
$this->config
7885
);
7986
}
8087

core/Controller/CloudController.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,25 @@ public function getCurrentUser() {
7171
];
7272
return ['data' => $data];
7373
}
74+
75+
/**
76+
* @NoAdminRequired
77+
* @NoCSRFRequired
78+
* @CORS
79+
*
80+
* @return array
81+
* @throws \OCP\PreConditionNotMetException
82+
*/
83+
public function getSigningKey(): array {
84+
$userId = \OC_User::getUser();
85+
$signingKey = \OC::$server->getConfig()->getUserValue($userId, 'core', 'signing-key', null);
86+
if ($signingKey === null) {
87+
$signingKey = \OC::$server->getSecureRandom()->generate(64);
88+
\OC::$server->getConfig()->setUserValue($userId, 'core', 'signing-key', $signingKey, null);
89+
}
90+
return ['data' => [
91+
'user' => $userId,
92+
'signing-key' => $signingKey
93+
]];
94+
}
7495
}

core/routes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
'ocs' => [
6060
['root' => '/cloud', 'name' => 'Cloud#getCapabilities', 'url' => '/capabilities', 'verb' => 'GET'],
6161
['root' => '/cloud', 'name' => 'Cloud#getCurrentUser', 'url' => '/user', 'verb' => 'GET'],
62+
['root' => '/cloud', 'name' => 'Cloud#getSigningKey', 'url' => '/user/signing-key', 'verb' => 'GET'],
6263
['root' => '/cloud', 'name' => 'Roles#getRoles', 'url' => '/roles', 'verb' => 'GET'],
6364
['root' => '/cloud', 'name' => 'UserSync#syncUser', 'url' => '/user-sync/{userId}', 'verb' => 'POST'],
6465
]

lib/private/OCS/CoreCapabilities.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function getCapabilities() {
5454
'pollinterval' => $this->config->getSystemValue('pollinterval', 60),
5555
'webdav-root' => $this->config->getSystemValue('webdav-root', 'remote.php/webdav'),
5656
'status' => Util::getStatusInfo(true),
57+
'support-url-signing' => true,
5758
]
5859
];
5960
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
/**
3+
* @author Thomas Müller <thomas.mueller@tmit.eu>
4+
*
5+
* @copyright Copyright (c) 2020, 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 OC\Security\SignedUrl;
23+
24+
use OCP\IConfig;
25+
use Sabre\HTTP\RequestInterface;
26+
27+
class Verifier {
28+
29+
/**
30+
* @var RequestInterface
31+
*/
32+
private $request;
33+
/**
34+
* @var IConfig
35+
*/
36+
private $config;
37+
/**
38+
* @var \DateTime|null
39+
*/
40+
private $now;
41+
42+
public function __construct(RequestInterface $request, IConfig $config, \DateTime $now = null) {
43+
$this->request = $request;
44+
$this->config = $config;
45+
$this->now = $now ?? new \DateTime();
46+
}
47+
48+
public function isSignedRequest(): bool {
49+
$params = $this->getQueryParameters();
50+
return isset($params['OC-Signature']);
51+
}
52+
53+
public function signedRequestIsValid(): bool {
54+
$params = $this->getQueryParameters();
55+
if (!isset($params['OC-Signature'], $params['OC-Credential'], $params['OC-Date'], $params['OC-Expires'], $params['OC-Verb'])) {
56+
return false;
57+
}
58+
$urlSignature = $params['OC-Signature'];
59+
$urlCredential = $params['OC-Credential'];
60+
$urlDate = $params['OC-Date'];
61+
$urlExpires = $params['OC-Expires'];
62+
$urlVerb = $params['OC-Verb'];
63+
64+
unset($params['OC-Signature']);
65+
66+
$qp = \http_build_query($params);
67+
$url = \Sabre\Uri\parse($this->getAbsoluteUrl());
68+
$url['query'] = $qp;
69+
$url = \Sabre\Uri\build($url);
70+
71+
$signingKey = $this->config->getUserValue($urlCredential, 'core', 'signing-key');
72+
73+
$hash = \hash_pbkdf2("sha512", $url, $signingKey, 10000, 64, false);
74+
if ($hash !== $urlSignature) {
75+
return false;
76+
}
77+
if (\strtoupper($this->getMethod()) !== \strtoupper($urlVerb)) {
78+
return false;
79+
}
80+
$date = new \DateTime($urlDate);
81+
$date->add(new \DateInterval("PT${urlExpires}S"));
82+
return !($date < $this->now);
83+
}
84+
85+
private function getQueryParameters(): array {
86+
return $this->request->getQueryParameters();
87+
}
88+
89+
public function getUrlCredential(): string {
90+
$params = $this->getQueryParameters();
91+
if (!isset($params['OC-Credential'])) {
92+
throw new \LogicException('OC-Credential not set');
93+
}
94+
95+
return $params['OC-Credential'];
96+
}
97+
98+
private function getAbsoluteUrl(): string {
99+
return $this->request->getAbsoluteUrl();
100+
}
101+
102+
private function getMethod(): string {
103+
return $this->request->getMethod();
104+
}
105+
}

0 commit comments

Comments
 (0)
X Tutup