Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
71fd68b
feat: Bearer auth aware Sabre HTTP client
enriquepablo Dec 23, 2025
046105d
feat(dav): Add token endpoint to exchange refresh tokens for access t…
enriquepablo Dec 23, 2025
a413da0
feat(dav): Add Bearer auth backend for webdav requests
enriquepablo Dec 23, 2025
ca53d2e
feat(dav): New method doTryTokenLogin to allow to try token login wit…
enriquepablo Dec 23, 2025
3fcfff8
feat(federatedfilesharing): Create permanent refresh token when creat…
enriquepablo Dec 23, 2025
cd4e445
feat(federatedfilesharing): When a remote requests a share with a tok…
enriquepablo Dec 23, 2025
8c3883c
feat(files_sharing): When requesting a remote share with bearer auth,…
enriquepablo Dec 23, 2025
1f1ea36
feat: adapt to guzzle api
enriquepablo Dec 23, 2025
4c0e3ca
feat(cloud_federation_api): adapt to new format for share creation
enriquepablo Dec 23, 2025
b831314
feat(cloud_federation_api): support multi protocol for share creation
enriquepablo Jan 8, 2026
9643221
fix(dav): data sent to token endpoint must be application/x-www-form-…
enriquepablo Jan 8, 2026
3fd9626
fix(dav): when receiving a share, account for the must-exchange-token…
enriquepablo Jan 9, 2026
09a227b
fix(federatedfilesharing): POSTs to token endpoint should be signed
enriquepablo Jan 12, 2026
6f1a907
fix(federatedfilesharing): POSTs to token endpoint MUST be signed
enriquepablo Jan 12, 2026
ca0106b
fix: federated share provider tests
enriquepablo Jan 8, 2026
f93799b
fix: share manager test
enriquepablo Jan 8, 2026
a1029d7
fix(federatedfilesharing): fix federated share provider tests
enriquepablo Jan 19, 2026
76858fd
fix(federatedfilesharing): fixing federated share provider tests
enriquepablo Jan 19, 2026
a78da19
fix(federatedfilesharing): fixing federated share provider tests
enriquepablo Jan 19, 2026
fab3be8
fix: fixing code style
enriquepablo Jan 19, 2026
ce09a18
fix: fixing openapi specs
enriquepablo Jan 19, 2026
cd9d7c4
fix: fix psalm issues
enriquepablo Jan 19, 2026
8d20062
fix: reorder import
enriquepablo Jan 19, 2026
e420056
fix(dav): do not import from NCU ns
enriquepablo Jan 19, 2026
4d92476
fix: fix sqlite integration tests
enriquepablo Jan 23, 2026
eb14f88
fix(federatedfilesharing): order of imports
enriquepablo Jan 23, 2026
72e714a
fix: fix session tests using Session::loginWithToken
enriquepablo Jan 23, 2026
26e9feb
fix: fix public key token provider test
enriquepablo Jan 23, 2026
90be277
fix: Fixed undefined $request variable
enriquepablo Jan 26, 2026
7f0715f
fix(files_external): Added missing doTryTokenLogin() method to implem…
enriquepablo Jan 26, 2026
5d61de4
fix: Fixed parent::getType() to use ->getter('type') to avoid Psalm m…
enriquepablo Jan 26, 2026
1133eb4
fix: Added getTokenEndPoint() and setTokenEndPoint() methods that sho…
enriquepablo Jan 26, 2026
647c8c3
fix: fix session tests
enriquepablo Jan 28, 2026
e72275b
fix(federatedfilesharing): remove unused import
enriquepablo Jan 29, 2026
2e5a70f
fix: fix user session tests
enriquepablo Jan 29, 2026
8a6a85a
fix: update 3rdparty submodule
enriquepablo Jan 29, 2026
b2c9b2d
test: test token controller
enriquepablo Jan 29, 2026
7afabf7
test: test doTryTokenLogin method
enriquepablo Jan 29, 2026
5d50d75
fix(dav): remove unused import in TokenController test
enriquepablo Feb 2, 2026
0b9b1e2
feat(dav): refresh expired tokens
enriquepablo Feb 2, 2026
d8cf07b
fix(files_sharing): refactor refreshing access tokens
enriquepablo Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace OCA\CloudFederationAPI\Controller;

use OC\Authentication\Token\PublicKeyTokenProvider;
use OC\OCM\OCMSignatoryManager;
use OCA\CloudFederationAPI\Config;
use OCA\CloudFederationAPI\Db\FederatedInviteMapper;
Expand Down Expand Up @@ -43,6 +44,7 @@
use OCP\Security\Signature\Exceptions\SignatoryNotFoundException;
use OCP\Security\Signature\IIncomingSignedRequest;
use OCP\Security\Signature\ISignatureManager;
use OCP\Server;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Util;
use Psr\Log\LoggerInterface;
Expand Down Expand Up @@ -91,7 +93,7 @@ public function __construct(
* @param string|null $ownerDisplayName Display name of the user who shared the item
* @param string|null $sharedBy Provider specific UID of the user who shared the resource
* @param string|null $sharedByDisplayName Display name of the user who shared the resource
* @param array{name: list<string>, options: array<string, mixed>} $protocol e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]]
* @param array{name: string, options?: array<string, mixed>, webdav?: array<string, mixed>} $protocol Old format: ['name' => 'webdav', 'options' => ['sharedSecret' => '...', 'permissions' => '...']] or New format: ['name' => 'webdav', 'webdav' => ['uri' => '...', 'sharedSecret' => '...', 'permissions' => [...]]] or Multi format: ['name' => 'multi', 'webdav' => [...]]
* @param string $shareType 'group' or 'user' share
* @param string $resourceType 'file', 'calendar',...
*
Expand Down Expand Up @@ -126,9 +128,6 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
|| $shareType === null
|| !is_array($protocol)
|| !isset($protocol['name'])
|| !isset($protocol['options'])
|| !is_array($protocol['options'])
|| !isset($protocol['options']['sharedSecret'])
) {
return new JSONResponse(
[
Expand All @@ -139,6 +138,33 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
);
}

$protocolName = $protocol['name'];
$hasOldFormat = isset($protocol['options']) && is_array($protocol['options']) && isset($protocol['options']['sharedSecret']);
$hasNewFormat = isset($protocol[$protocolName]) && is_array($protocol[$protocolName]) && isset($protocol[$protocolName]['sharedSecret']);

// For multi-protocol, we only consider webdav
$hasMultiFormat = false;
if ($protocolName === 'multi') {
if (isset($protocol['webdav']) && is_array($protocol['webdav']) && isset($protocol['webdav']['sharedSecret'])) {
$hasMultiFormat = true;
$protocol = [
'name' => 'webdav',
'webdav' => $protocol['webdav']
];
$protocolName = 'webdav';
}
}

if (!$hasOldFormat && !$hasNewFormat && !$hasMultiFormat) {
return new JSONResponse(
[
'message' => 'Missing sharedSecret in protocol',
'validationErrors' => [],
],
Http::STATUS_BAD_REQUEST
);
}

$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
if (!in_array($shareType, $supportedShareTypes)) {
return new JSONResponse(
Expand Down Expand Up @@ -490,6 +516,12 @@ private function confirmNotificationIdentity(
$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
if ($provider instanceof ISignedCloudFederationProvider) {
$identity = $provider->getFederationIdFromSharedSecret($sharedSecret, $notification);
if ($identity === '') {
$tokenProvider = Server::get(PublicKeyTokenProvider::class);
$accessTokenDb = $tokenProvider->getToken($sharedSecret);
$refreshToken = $accessTokenDb->getUID();
$identity = $provider->getFederationIdFromSharedSecret($refreshToken, $notification);
}
} else {
$this->logger->debug('cloud federation provider {provider} does not implements ISignedCloudFederationProvider', ['provider' => $provider::class]);
return;
Expand Down
16 changes: 9 additions & 7 deletions apps/cloud_federation_api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,23 +161,25 @@
},
"protocol": {
"type": "object",
"description": "e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]]",
"description": "Old format: ['name' => 'webdav', 'options' => ['sharedSecret' => '...', 'permissions' => '...']] or New format: ['name' => 'webdav', 'webdav' => ['uri' => '...', 'sharedSecret' => '...', 'permissions' => [...]]] or Multi format: ['name' => 'multi', 'webdav' => [...]]",
"required": [
"name",
"options"
"name"
],
"properties": {
"name": {
"type": "array",
"items": {
"type": "string"
}
"type": "string"
},
"options": {
"type": "object",
"additionalProperties": {
"type": "object"
}
},
"webdav": {
"type": "object",
"additionalProperties": {
"type": "object"
}
}
}
},
Expand Down
16 changes: 14 additions & 2 deletions apps/dav/appinfo/v1/publicwebdav.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use OC\Files\Storage\Wrapper\PermissionsMask;
use OC\Files\View;
use OCA\DAV\Connector\LegacyPublicAuth;
use OCA\DAV\Connector\Sabre\BearerAuth;
use OCA\DAV\Connector\Sabre\ServerFactory;
use OCA\DAV\Files\Sharing\FilesDropPlugin;
use OCA\DAV\Files\Sharing\PublicLinkCheckPlugin;
Expand Down Expand Up @@ -49,7 +50,14 @@
Server::get(ISession::class),
Server::get(IThrottler::class)
);
$bearerAuthBackend = new BearerAuth(
Server::get(IUserSession::class),
Server::get(ISession::class),
Server::get(IRequest::class),
Server::get(IConfig::class),
);
$authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend);
$authPlugin->addBackend($bearerAuthBackend);

/** @var IEventDispatcher $eventDispatcher */
$eventDispatcher = Server::get(IEventDispatcher::class);
Expand Down Expand Up @@ -80,6 +88,7 @@
$authPlugin,
function (\Sabre\DAV\Server $server) use (
$authBackend,
$bearerAuthBackend,
$linkCheckPlugin,
$filesDropPlugin
) {
Expand All @@ -90,8 +99,11 @@ function (\Sabre\DAV\Server $server) use (
// this is what is thrown when trying to access a non-existing share
throw new \Sabre\DAV\Exception\NotAuthenticated();
}

$share = $authBackend->getShare();
try {
$share = $authBackend->getShare();
} catch (AssertionError $e) {
$share = $bearerAuthBackend->getShare();
}
$owner = $share->getShareOwner();
$isReadable = $share->getPermissions() & Constants::PERMISSION_READ;
$fileId = $share->getNodeId();
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@
'OCA\\DAV\\Controller\\ExampleContentController' => $baseDir . '/../lib/Controller/ExampleContentController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => $baseDir . '/../lib/Controller/InvitationResponseController.php',
'OCA\\DAV\\Controller\\OutOfOfficeController' => $baseDir . '/../lib/Controller/OutOfOfficeController.php',
'OCA\\DAV\\Controller\\TokenController' => $baseDir . '/../lib/Controller/TokenController.php',
'OCA\\DAV\\Controller\\UpcomingEventsController' => $baseDir . '/../lib/Controller/UpcomingEventsController.php',
'OCA\\DAV\\DAV\\CustomPropertiesBackend' => $baseDir . '/../lib/DAV/CustomPropertiesBackend.php',
'OCA\\DAV\\DAV\\GroupPrincipalBackend' => $baseDir . '/../lib/DAV/GroupPrincipalBackend.php',
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Controller\\ExampleContentController' => __DIR__ . '/..' . '/../lib/Controller/ExampleContentController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => __DIR__ . '/..' . '/../lib/Controller/InvitationResponseController.php',
'OCA\\DAV\\Controller\\OutOfOfficeController' => __DIR__ . '/..' . '/../lib/Controller/OutOfOfficeController.php',
'OCA\\DAV\\Controller\\TokenController' => __DIR__ . '/..' . '/../lib/Controller/TokenController.php',
'OCA\\DAV\\Controller\\UpcomingEventsController' => __DIR__ . '/..' . '/../lib/Controller/UpcomingEventsController.php',
'OCA\\DAV\\DAV\\CustomPropertiesBackend' => __DIR__ . '/..' . '/../lib/DAV/CustomPropertiesBackend.php',
'OCA\\DAV\\DAV\\GroupPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/GroupPrincipalBackend.php',
Expand Down
21 changes: 19 additions & 2 deletions apps/dav/lib/Connector/Sabre/BearerAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
use OCP\IRequest;
use OCP\ISession;
use OCP\IUserSession;
use OCP\Server;
use OCP\Share\IManager;
use OCP\Share\IShare;
use Sabre\DAV\Auth\Backend\AbstractBearer;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
Expand All @@ -23,6 +26,7 @@ public function __construct(
private IRequest $request,
private IConfig $config,
private string $principalPrefix = 'principals/users/',
private string $token = '',
) {
// setup realm
$defaults = new Defaults();
Expand All @@ -40,17 +44,30 @@ private function setupUserFs($userId) {
*/
public function validateBearerToken($bearerToken) {
\OC_Util::setupFS();
$this->token = $bearerToken;

if (!$this->userSession->isLoggedIn()) {
$loggedIn = $this->userSession->isLoggedIn();
if (!$loggedIn) {
$this->userSession->tryTokenLogin($this->request);
$loggedIn = $this->userSession->isLoggedIn();
}
if ($this->userSession->isLoggedIn()) {
if (!$loggedIn) {
$this->userSession->doTryTokenLogin($bearerToken);
$loggedIn = $this->userSession->isLoggedIn();
}
if ($loggedIn) {
return $this->setupUserFs($this->userSession->getUser()->getUID());
}

return false;
}

public function getShare(): IShare {
$shareManager = Server::get(IManager::class);
$share = $shareManager->getShareByToken($this->token);
return $share;
}

/**
* \Sabre\DAV\Auth\Backend\AbstractBearer::challenge sets an WWW-Authenticate
* header which some DAV clients can't handle. Thus we override this function
Expand Down
Loading
Loading