Skip to content

Commit b85516b

Browse files
committed
feat(files_sharing): When requesting a remote share with bearer auth, get an access token to use as bearer token
Signed-off-by: Enrique Pérez Arnaud <enrique@cazalla.net>
1 parent 7e7961f commit b85516b

2 files changed

Lines changed: 113 additions & 7 deletions

File tree

apps/files_sharing/lib/External/Storage.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,16 @@ public function __construct($options) {
6666
$ocmProvider = $discoveryService->discover($this->cloudId->getRemote());
6767
$webDavEndpoint = $ocmProvider->extractProtocolEntry('file', 'webdav');
6868
$remote = $ocmProvider->getEndPoint();
69+
$authType = \Sabre\DAV\Client::AUTH_BASIC;
70+
$capabilities = $ocmProvider->getCapabilities();
71+
if (in_array('exchange-token', $capabilities)) {
72+
$authType = \OC\Files\Storage\BearerAuthAwareSabreClient::AUTH_BEARER;
73+
}
6974
} catch (OCMProviderException|OCMArgumentException $e) {
7075
$this->logger->notice('exception while retrieving webdav endpoint', ['exception' => $e]);
7176
$webDavEndpoint = '/public.php/webdav';
7277
$remote = $this->cloudId->getRemote();
78+
$authType = \Sabre\DAV\Client::AUTH_BASIC;
7379
}
7480

7581
$host = parse_url($remote, PHP_URL_HOST);
@@ -92,8 +98,9 @@ public function __construct($options) {
9298
'host' => $host,
9399
'root' => $webDavEndpoint,
94100
'user' => $options['token'],
95-
'authType' => \Sabre\DAV\Client::AUTH_BASIC,
96-
'password' => (string)$options['password']
101+
'authType' => $authType,
102+
'password' => (string)$options['password'],
103+
'discoveryService' => $discoveryService,
97104
]
98105
);
99106
}

lib/private/Files/Storage/DAV.php

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
use Exception;
1111
use Icewind\Streams\CallbackWrapper;
1212
use Icewind\Streams\IteratorDirectory;
13+
use NCU\Security\Signature\ISignatureManager;
1314
use OC\Files\Filesystem;
1415
use OC\MemCache\ArrayCache;
16+
use OC\OCM\OCMSignatoryManager;
1517
use OCP\AppFramework\Http;
1618
use OCP\Constants;
1719
use OCP\Diagnostics\IEventLogger;
@@ -22,8 +24,12 @@
2224
use OCP\Files\StorageNotAvailableException;
2325
use OCP\Http\Client\IClient;
2426
use OCP\Http\Client\IClientService;
27+
use OCP\IAppConfig;
2528
use OCP\ICertificateManager;
2629
use OCP\IConfig;
30+
use OCP\OCM\Exceptions\OCMArgumentException;
31+
use OCP\OCM\Exceptions\OCMProviderException;
32+
use OCP\OCM\IOCMDiscoveryService;
2733
use OCP\Server;
2834
use OCP\Util;
2935
use Psr\Http\Message\ResponseInterface;
@@ -34,7 +40,7 @@
3440
use Sabre\HTTP\ClientHttpException;
3541
use Sabre\HTTP\RequestInterface;
3642

37-
/*
43+
/**
3844
* Class BearerAuthAwareSabreClient
3945
*
4046
* This is an extension of the Sabre HTTP Client
@@ -104,6 +110,10 @@ class DAV extends Common {
104110
protected LoggerInterface $logger;
105111
protected IEventLogger $eventLogger;
106112
protected IMimeTypeDetector $mimeTypeDetector;
113+
protected IOCMDiscoveryService $discoveryService;
114+
protected ISignatureManager $signatureManager;
115+
protected OCMSignatoryManager $signatoryManager;
116+
protected IAppConfig $appConfig;
107117

108118
/** @var int */
109119
private $timeout;
@@ -126,6 +136,11 @@ class DAV extends Common {
126136
public function __construct(array $parameters) {
127137
$this->statCache = new ArrayCache();
128138
$this->httpClientService = Server::get(IClientService::class);
139+
if (isset($parameters['discoveryService'])) {
140+
$this->discoveryService = $parameters['discoveryService'];
141+
} else {
142+
$this->discoveryService = Server::get(IOCMDiscoveryService::class);
143+
}
129144
if (isset($parameters['host']) && isset($parameters['user']) && isset($parameters['password'])) {
130145
$host = $parameters['host'];
131146
//remove leading http[s], will be generated in createBaseUri()
@@ -164,6 +179,9 @@ public function __construct(array $parameters) {
164179
// This timeout value will be used for the download and upload of files
165180
$this->timeout = Server::get(IConfig::class)->getSystemValueInt('davstorage.request_timeout', IClient::DEFAULT_REQUEST_TIMEOUT);
166181
$this->mimeTypeDetector = \OC::$server->getMimeTypeDetector();
182+
$this->signatureManager = Server::get(ISignatureManager::class);
183+
$this->signatoryManager = Server::get(OCMSignatoryManager::class);
184+
$this->appConfig = Server::get(IAppConfig::class);
167185
}
168186

169187
protected function init(): void {
@@ -172,9 +190,82 @@ protected function init(): void {
172190
}
173191
$this->ready = true;
174192

193+
// If using Bearer auth, exchange refresh token for access token
194+
$userName = $this->user;
195+
if ($this->authType !== null && ($this->authType & BearerAuthAwareSabreClient::AUTH_BEARER)) {
196+
try {
197+
$host = 'https://' . $this->host;
198+
$ocmProvider = $this->discoveryService->discover($host);
199+
$tokenEndpoint = $ocmProvider->getTokenEndPoint();
200+
201+
if ($tokenEndPoint === '') {
202+
$this->logger->error('OCM provider response missing tokenEndPoint', ['app' => 'dav']);
203+
throw new StorageNotAvailableException('Could not discover token endpoint');
204+
}
205+
206+
$client = $this->httpClientService->newClient();
207+
$payload = [
208+
'grant_type' => 'authorization_code',
209+
'client_id' => 'receiver.example.org',
210+
'code' => $this->user,
211+
];
212+
213+
$options = [
214+
'body' => json_encode($payload),
215+
'headers' => [
216+
'Content-Type' => 'application/json',
217+
],
218+
'timeout' => 10,
219+
'connect_timeout' => 10,
220+
];
221+
222+
// Try signing the request
223+
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
224+
try {
225+
$options = $this->signatureManager->signOutgoingRequestIClientPayload(
226+
$this->signatoryManager,
227+
$options,
228+
'post',
229+
$tokenEndpoint
230+
);
231+
$this->logger->debug('Token request signed successfully', ['app' => 'dav']);
232+
} catch (\Exception $e) {
233+
$this->logger->warning('Failed to sign token request, continuing without signature', [
234+
'app' => 'dav',
235+
'exception' => $e,
236+
'endpoint' => $tokenEndpoint,
237+
]);
238+
}
239+
}
240+
241+
$response = $client->post($tokenEndpoint, $options);
242+
243+
$body = $response->getBody();
244+
$data = json_decode($body, true);
245+
246+
if (isset($data['access_token'])) {
247+
$userName = $data['access_token'];
248+
$this->user = $userName;
249+
$this->logger->debug('Successfully exchanged refresh token for access token', ['app' => 'dav']);
250+
} else {
251+
$this->logger->error('Failed to get access token from response', ['app' => 'dav']);
252+
throw new StorageNotAvailableException('Could not obtain access token');
253+
}
254+
} catch (OCMProviderException|OCMArgumentException $e) {
255+
$this->logger->error('OCM provider response missing tokenEndPoint', ['app' => 'dav']);
256+
throw new StorageNotAvailableException('Could not discover token endpoint');
257+
} catch (\Exception $e) {
258+
$this->logger->error('Error exchanging refresh token for access token: ' . $e->getMessage(), [
259+
'app' => 'dav',
260+
'exception' => $e,
261+
]);
262+
throw new StorageNotAvailableException('Could not obtain access token: ' . $e->getMessage());
263+
}
264+
}
265+
175266
$settings = [
176267
'baseUri' => $this->createBaseUri(),
177-
'userName' => $this->user,
268+
'userName' => $userName,
178269
'password' => $this->password,
179270
];
180271
if ($this->authType !== null) {
@@ -186,7 +277,7 @@ protected function init(): void {
186277
$settings['proxy'] = $proxy;
187278
}
188279

189-
$this->client = new Client($settings);
280+
$this->client = new BearerAuthAwareSabreClient($settings);
190281
$this->client->setThrowExceptions(true);
191282

192283
if ($this->secure === true) {
@@ -362,10 +453,14 @@ public function fopen(string $path, string $mode) {
362453
case 'r':
363454
case 'rb':
364455
try {
456+
$auth = [$this->user, $this->password];
457+
if ($this->authType === BearerAuthAwareSabreClient::AUTH_BEARER) {
458+
$auth = [$this->user, '', 'bearer'];
459+
}
365460
$response = $this->httpClientService
366461
->newClient()
367462
->get($this->createBaseUri() . $this->encodePath($path), [
368-
'auth' => [$this->user, $this->password],
463+
'auth' => $auth,
369464
'stream' => true,
370465
// set download timeout for users with slow connections or large files
371466
'timeout' => $this->timeout
@@ -512,11 +607,15 @@ protected function uploadFile(string $path, string $target): void {
512607
$this->statCache->remove($target);
513608
$source = fopen($path, 'r');
514609

610+
$auth = [$this->user, $this->password];
611+
if ($this->authType === BearerAuthAwareSabreClient::AUTH_BEARER) {
612+
$auth = [$this->user, '', 'bearer'];
613+
}
515614
$this->httpClientService
516615
->newClient()
517616
->put($this->createBaseUri() . $this->encodePath($target), [
518617
'body' => $source,
519-
'auth' => [$this->user, $this->password],
618+
'auth' => $auth,
520619
// set upload timeout for users with slow connections or large files
521620
'timeout' => $this->timeout
522621
]);

0 commit comments

Comments
 (0)