diff --git a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php index b150bb43c13..48f24782d80 100644 --- a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php +++ b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php @@ -7,9 +7,11 @@ namespace OCA\CloudFederationAPI\Controller; +use OC\Authentication\Token\PublicKeyTokenProvider; use OC\OCM\OCMSignatoryManager; use OCA\CloudFederationAPI\Config; use OCA\CloudFederationAPI\Db\FederatedInviteMapper; +use OCA\CloudFederationAPI\Db\OcmTokenMapMapper; use OCA\CloudFederationAPI\Events\FederatedInviteAcceptedEvent; use OCA\CloudFederationAPI\ResponseDefinitions; use OCP\AppFramework\Controller; @@ -31,6 +33,7 @@ use OCP\Federation\ICloudFederationFactory; use OCP\Federation\ICloudFederationProviderManager; use OCP\Federation\ICloudIdManager; use OCP\Federation\ISignedCloudFederationProvider; +use OCP\Federation\IValidationAwareCloudFederationProvider; use OCP\IAppConfig; use OCP\IGroupManager; use OCP\IRequest; @@ -40,6 +43,7 @@ use OCP\OCM\Events\OCMNotificationReceivedEvent; use OCP\OCM\IOCMDiscoveryService; use OCP\Security\Signature\Exceptions\IncomingRequestException; use OCP\Security\Signature\IIncomingSignedRequest; +use OCP\Server; use OCP\Share\Exceptions\ShareNotFound; use OCP\Util; use Psr\Log\LoggerInterface; @@ -86,7 +90,7 @@ class RequestHandlerController extends Controller { * @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, options: array} $protocol e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]] + * @param array{name: string, options?: array, webdav?: array} $protocol Old format: ['name' => 'webdav', 'options' => ['sharedSecret' => '...', 'permissions' => '...']] or New format: ['name' => 'webdav', 'webdav' => ['uri' => '...', 'sharedSecret' => '...', 'permissions' => [...]]] * @param string $shareType 'group' or 'user' share * @param string $resourceType 'file', 'calendar',... * @@ -95,6 +99,10 @@ class RequestHandlerController extends Controller { * 201: The notification was successfully received. The display name of the recipient might be returned in the body * 400: Bad request due to invalid parameters, e.g. when `shareWith` is not found or required properties are missing * 501: Share type or the resource type is not supported + * + * @psalm-suppress InvalidReturnType + * @psalm-suppress InvalidReturnStatement + * @psalm-suppress LessSpecificReturnStatement */ #[PublicPage] #[NoCSRFRequired] @@ -121,9 +129,6 @@ class RequestHandlerController extends Controller { || $shareType === null || !is_array($protocol) || !isset($protocol['name']) - || !isset($protocol['options']) - || !is_array($protocol['options']) - || !isset($protocol['options']['sharedSecret']) ) { return new JSONResponse( [ @@ -134,6 +139,20 @@ class RequestHandlerController extends Controller { ); } + $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']); + + if (!$hasOldFormat && !$hasNewFormat) { + 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( @@ -143,6 +162,7 @@ class RequestHandlerController extends Controller { } $cloudId = $this->cloudIdManager->resolveCloudId($shareWith); + $shareWithCloudId = $shareWith; // preserve full cloud ID for factory capability discovery $shareWith = $cloudId->getUser(); if ($shareType === 'user') { @@ -209,14 +229,28 @@ class RequestHandlerController extends Controller { try { $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType); - $share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType); + // Pass the original cloud ID so the factory can discover capabilities without warning. + // Then reset shareWith to the local username that shareReceived() needs for user lookup. + $share = $this->factory->getCloudFederationShare($shareWithCloudId, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType); + $share->setShareWith($shareWith); $share->setProtocol($protocol); + if ($provider instanceof IValidationAwareCloudFederationProvider) { + $provider->validateShare($share); + } $provider->shareReceived($share); - } catch (ProviderDoesNotExistsException|ProviderCouldNotAddShareException $e) { + } catch (BadRequestException $e) { + return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST); + } catch (ProviderDoesNotExistsException $e) { return new JSONResponse( ['message' => $e->getMessage()], Http::STATUS_NOT_IMPLEMENTED ); + } catch (ProviderCouldNotAddShareException $e) { + $status = $e->getCode() ?: Http::STATUS_NOT_IMPLEMENTED; + return new JSONResponse( + ['message' => $e->getMessage()], + $status + ); } catch (\Exception $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); return new JSONResponse( @@ -509,6 +543,12 @@ class RequestHandlerController extends Controller { $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType); if ($provider instanceof ISignedCloudFederationProvider || $provider instanceof \NCU\Federation\ISignedCloudFederationProvider) { $identity = $provider->getFederationIdFromSharedSecret($sharedSecret, $notification); + if ($identity === '') { + $tokenProvider = Server::get(PublicKeyTokenProvider::class); + $accessTokenDb = $tokenProvider->getToken($sharedSecret); + $mapping = Server::get(OcmTokenMapMapper::class)->getByAccessTokenId($accessTokenDb->getId()); + $identity = $provider->getFederationIdFromSharedSecret($mapping->getRefreshToken(), $notification); + } } else { $this->logger->debug('cloud federation provider {provider} does not implements ISignedCloudFederationProvider', ['provider' => $provider::class]); return; diff --git a/apps/cloud_federation_api/openapi.json b/apps/cloud_federation_api/openapi.json index 695fa5fb560..b4656447709 100644 --- a/apps/cloud_federation_api/openapi.json +++ b/apps/cloud_federation_api/openapi.json @@ -161,7 +161,7 @@ }, "protocol": { "type": "object", - "description": "Old format: ['name' => 'webdav', 'options' => ['sharedSecret' => '...', 'permissions' => '...']] or New format: ['name' => 'webdav', 'webdav' => ['uri' => '...', 'sharedSecret' => '...', 'permissions' => [...]]] or Multi format: ['name' => 'multi', 'webdav' => [...]]", + "description": "Old format: ['name' => 'webdav', 'options' => ['sharedSecret' => '...', 'permissions' => '...']] or New format: ['name' => 'webdav', 'webdav' => ['uri' => '...', 'sharedSecret' => '...', 'permissions' => [...]]]", "required": [ "name" ], diff --git a/openapi.json b/openapi.json index 4e5683d7005..a2e4f93e8a7 100644 --- a/openapi.json +++ b/openapi.json @@ -18375,7 +18375,7 @@ }, "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' => [...]]]", "required": [ "name", "options"