mirror of
https://github.com/nextcloud/server.git
synced 2026-06-18 21:21:48 -04:00
feat(cloud_federation_api): accept new protocol envelope and delegate validation
Accept both the legacy options.sharedSecret envelope and the new protocol[name].sharedSecret form. Preserve the original cloud ID so the factory can discover capabilities, then reset shareWith to the local username for user lookup. Delegate per-protocol validation to providers via the new IValidationAwareCloudFederationProvider interface, with split exception handling: BadRequestException -> 400, ProviderCouldNotAddShareException -> the exception's own HTTP status (501 fallback). In the notification handler, fall back to looking up the refresh token via OcmTokenMapMapper when the access token cannot identify the federation. Co-authored-by: Micke Nordin <kano@sunet.se> Signed-off-by: Micke Nordin <kano@sunet.se> Signed-off-by: Enrique Pérez Arnaud <enrique@cazalla.net>
This commit is contained in:
parent
789ff6a8a3
commit
3956c4e9be
3 changed files with 48 additions and 8 deletions
|
|
@ -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<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' => [...]]]
|
||||
* @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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue