diff --git a/apps/cloud_federation_api/appinfo/info.xml b/apps/cloud_federation_api/appinfo/info.xml index 57a8d9b50a0..81343cb49bf 100644 --- a/apps/cloud_federation_api/appinfo/info.xml +++ b/apps/cloud_federation_api/appinfo/info.xml @@ -9,7 +9,7 @@ Cloud Federation API Enable clouds to communicate with each other and exchange data The Cloud Federation API enables various Nextcloud instances to communicate with each other and to exchange data. - 1.15.0 + 1.16.0 agpl Bjoern Schiessle CloudFederationAPI diff --git a/apps/cloud_federation_api/composer/composer/InstalledVersions.php b/apps/cloud_federation_api/composer/composer/InstalledVersions.php index 6d29bff66aa..51e734a774b 100644 --- a/apps/cloud_federation_api/composer/composer/InstalledVersions.php +++ b/apps/cloud_federation_api/composer/composer/InstalledVersions.php @@ -32,11 +32,6 @@ class InstalledVersions */ private static $installed; - /** - * @var bool - */ - private static $installedIsLocalDir; - /** * @var bool|null */ @@ -314,12 +309,6 @@ class InstalledVersions { self::$installed = $data; self::$installedByVendor = array(); - - // when using reload, we disable the duplicate protection to ensure that self::$installed data is - // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, - // so we have to assume it does not, and that may result in duplicate data being returned when listing - // all installed packages for example - self::$installedIsLocalDir = false; } /** @@ -333,27 +322,19 @@ class InstalledVersions } $installed = array(); - $copiedLocalDir = false; if (self::$canGetVendors) { - $selfDir = strtr(__DIR__, '\\', '/'); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { - $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ $required = require $vendorDir.'/composer/installed.php'; - self::$installedByVendor[$vendorDir] = $required; - $installed[] = $required; - if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { - self::$installed = $required; - self::$installedIsLocalDir = true; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; } } - if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { - $copiedLocalDir = true; - } } } @@ -369,7 +350,7 @@ class InstalledVersions } } - if (self::$installed !== array() && !$copiedLocalDir) { + if (self::$installed !== array()) { $installed[] = self::$installed; } diff --git a/apps/cloud_federation_api/composer/composer/autoload_classmap.php b/apps/cloud_federation_api/composer/composer/autoload_classmap.php index dd096ebf563..aa0c7c5ad79 100644 --- a/apps/cloud_federation_api/composer/composer/autoload_classmap.php +++ b/apps/cloud_federation_api/composer/composer/autoload_classmap.php @@ -11,5 +11,8 @@ return array( 'OCA\\CloudFederationAPI\\Capabilities' => $baseDir . '/../lib/Capabilities.php', 'OCA\\CloudFederationAPI\\Config' => $baseDir . '/../lib/Config.php', 'OCA\\CloudFederationAPI\\Controller\\RequestHandlerController' => $baseDir . '/../lib/Controller/RequestHandlerController.php', + 'OCA\\CloudFederationAPI\\Migration\\Version1016Date202502262004' => $baseDir . '/../lib/Migration/Version1016Date202502262004.php', 'OCA\\CloudFederationAPI\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php', + 'OCA\\CloudFederationApi\\Events\\OCMInvitationAcceptedEvent' => $baseDir . '/../lib/Events/OCMInvitationAcceptedEvent.php', + 'OCA\\CloudFederationApi\\OCMInvitation' => $baseDir . '/../lib/OCMInvitation.php', ); diff --git a/apps/cloud_federation_api/composer/composer/autoload_static.php b/apps/cloud_federation_api/composer/composer/autoload_static.php index 75557a20126..133580e463e 100644 --- a/apps/cloud_federation_api/composer/composer/autoload_static.php +++ b/apps/cloud_federation_api/composer/composer/autoload_static.php @@ -26,7 +26,10 @@ class ComposerStaticInitCloudFederationAPI 'OCA\\CloudFederationAPI\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php', 'OCA\\CloudFederationAPI\\Config' => __DIR__ . '/..' . '/../lib/Config.php', 'OCA\\CloudFederationAPI\\Controller\\RequestHandlerController' => __DIR__ . '/..' . '/../lib/Controller/RequestHandlerController.php', + 'OCA\\CloudFederationAPI\\Migration\\Version1016Date202502262004' => __DIR__ . '/..' . '/../lib/Migration/Version1016Date202502262004.php', 'OCA\\CloudFederationAPI\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php', + 'OCA\\CloudFederationApi\\Events\\OCMInvitationAcceptedEvent' => __DIR__ . '/..' . '/../lib/Events/OCMInvitationAcceptedEvent.php', + 'OCA\\CloudFederationApi\\OCMInvitation' => __DIR__ . '/..' . '/../lib/OCMInvitation.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php index 6a1c66661e9..636f78d0a9d 100644 --- a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php +++ b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php @@ -1,8 +1,10 @@ factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType); $share->setProtocol($protocol); $provider->shareReceived($share); - } catch (ProviderDoesNotExistsException|ProviderCouldNotAddShareException $e) { + } catch (ProviderDoesNotExistsException | ProviderCouldNotAddShareException $e) { return new JSONResponse( ['message' => $e->getMessage()], Http::STATUS_NOT_IMPLEMENTED @@ -243,7 +252,8 @@ class RequestHandlerController extends Controller { #[PublicPage] #[NoCSRFRequired] #[BruteForceProtection(action: 'inviteAccepted')] - public function inviteAccepted(string $recipientProvider, string $token, string $userId, string $email, string $name): JSONResponse { + public function inviteAccepted(string $recipientProvider, string $token, string $userId, string $email, string $name): JSONResponse + { $this->logger->debug('Invite accepted for ' . $userId . ' with token ' . $token . ' and email ' . $email . ' and name ' . $name); /** @var IQueryBuilder $qb */ @@ -255,6 +265,7 @@ class RequestHandlerController extends Controller { $data = $result->fetch(); $result->closeCursor(); $found_for_this_user = false; + $updated = new DateTime("now"); if ($data) { $found_for_this_user = $data['recipient_user_id'] === $userId && isset($data['user_id']); } @@ -265,17 +276,22 @@ class RequestHandlerController extends Controller { $response->throttle(); return $response; } - if(!$this->trustedServers->isTrustedServer($recipientProvider)) { + if (!$this->trustedServers->isTrustedServer($recipientProvider)) { $response = ['message' => 'Remote server not trusted', 'error' => true]; $status = Http::STATUS_FORBIDDEN; - return new JSONResponse($response,$status); + return new JSONResponse($response, $status); } // Note: Not implementing 404 Invitation token does not exist, instead using 400 - if ($data['accepted'] === true ) { + if ($data['accepted'] === true) { $response = ['message' => 'Invite already accepted', 'error' => true]; $status = Http::STATUS_CONFLICT; - return new JSONResponse($response,$status); + return new JSONResponse($response, $status); + } + if ($data['expiresAt'] < $updated) { + $response = ['message' => 'Invitation expired', 'error' => true]; + $status = Http::STATUS_BAD_REQUEST; + return new JSONResponse($response, $status); } $localUser = $this->userManager->get($data['user_id']); @@ -284,7 +300,6 @@ class RequestHandlerController extends Controller { $response = ['userID' => $data['user_id'], 'email' => $sharedFromEmail, 'name' => $sharedFromDisplayName]; $status = Http::STATUS_OK; - $updated = new DateTime("now"); $qb->update('federated_invites') ->set('accepted', $qb->createNamedParameter(true)) ->set('acceptedAt', $qb->createNamedParameter($updated)) @@ -295,7 +310,21 @@ class RequestHandlerController extends Controller { ->where($qb->expr()->eq('token', $qb->createNamedParameter($token))); $qb->executeStatement(); - return new JSONResponse($response,$status); + $invitation = new OCMInvitation( + accepted: true, + recipient_email: $email, + recipient_name: $name, + recipient_user_id: $userId, + recipient_provider: $recipientProvider, + token: $token, + user_id: $data['user_id'], + acceptedAt: $updated, + createdAt: $data['createdAt'], + expiresAt: $data['expiresAt'] + ); + $this->dispatcher->dispatchTyped(new OCMInvitationAcceptedEvent($invitation)); + + return new JSONResponse($response, $status); } /** @@ -316,9 +345,11 @@ class RequestHandlerController extends Controller { #[NoCSRFRequired] #[PublicPage] #[BruteForceProtection(action: 'receiveFederatedShareNotification')] - public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) { + public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) + { // check if all required parameters are set - if ($notificationType === null || + if ( + $notificationType === null || $resourceType === null || $providerId === null || !is_array($notification) @@ -394,7 +425,8 @@ class RequestHandlerController extends Controller { * @param string $uid * @return string mixed */ - private function mapUid($uid) { + private function mapUid($uid) + { // FIXME this should be a method in the user management instead $this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]); Util::emitHook( @@ -417,12 +449,13 @@ class RequestHandlerController extends Controller { * @return IIncomingSignedRequest|null null if remote does not (and never did) support signed request * @throws IncomingRequestException */ - private function getSignedRequest(): ?IIncomingSignedRequest { + private function getSignedRequest(): ?IIncomingSignedRequest + { try { $signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager); $this->logger->debug('signed request available', ['signedRequest' => $signedRequest]); return $signedRequest; - } catch (SignatureNotFoundException|SignatoryNotFoundException $e) { + } catch (SignatureNotFoundException | SignatoryNotFoundException $e) { $this->logger->debug('remote does not support signed request', ['exception' => $e]); // remote does not support signed request. // currently we still accept unsigned request until lazy appconfig @@ -452,7 +485,8 @@ class RequestHandlerController extends Controller { * * @throws IncomingRequestException */ - private function confirmSignedOrigin(?IIncomingSignedRequest $signedRequest, string $key, string $value): void { + private function confirmSignedOrigin(?IIncomingSignedRequest $signedRequest, string $key, string $value): void + { if ($signedRequest === null) { $instance = $this->getHostFromFederationId($value); try { @@ -516,7 +550,8 @@ class RequestHandlerController extends Controller { * @return void * @throws IncomingRequestException */ - private function confirmNotificationEntry(?IIncomingSignedRequest $signedRequest, string $entry): void { + private function confirmNotificationEntry(?IIncomingSignedRequest $signedRequest, string $entry): void + { $instance = $this->getHostFromFederationId($entry); if ($signedRequest === null) { try { @@ -535,7 +570,8 @@ class RequestHandlerController extends Controller { * @return string * @throws IncomingRequestException */ - private function getHostFromFederationId(string $entry): string { + private function getHostFromFederationId(string $entry): string + { if (!str_contains($entry, '@')) { throw new IncomingRequestException('entry ' . $entry . ' does not contains @'); } diff --git a/apps/cloud_federation_api/lib/Events/OCMInvitationAcceptedEvent.php b/apps/cloud_federation_api/lib/Events/OCMInvitationAcceptedEvent.php new file mode 100644 index 00000000000..d1ebcdaf7d8 --- /dev/null +++ b/apps/cloud_federation_api/lib/Events/OCMInvitationAcceptedEvent.php @@ -0,0 +1,26 @@ + + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCA\CloudFederationApi\Events; + +use OCP\EventDispatcher\Event; +use OCA\CloudFederationApi\OCMInvitation; + +class OCMInvitationAcceptedEvent extends Event +{ + public function __construct( + private OCMInvitation $invitation + ) { + parent::__construct(); + } + + public function getInvitation(): OCMInvitation + { + return $this->invitation; + } +} diff --git a/apps/cloud_federation_api/lib/Migration/Version1015Date202502262004.php b/apps/cloud_federation_api/lib/Migration/Version1016Date202502262004.php similarity index 97% rename from apps/cloud_federation_api/lib/Migration/Version1015Date202502262004.php rename to apps/cloud_federation_api/lib/Migration/Version1016Date202502262004.php index 436bdcb7f5d..ab4bf627c14 100644 --- a/apps/cloud_federation_api/lib/Migration/Version1015Date202502262004.php +++ b/apps/cloud_federation_api/lib/Migration/Version1016Date202502262004.php @@ -15,7 +15,7 @@ use OCP\DB\Types; use OCP\Migration\IOutput; use OCP\Migration\SimpleMigrationStep; -class Version1015Date202502262004 extends SimpleMigrationStep +class Version1016Date202502262004 extends SimpleMigrationStep { /** diff --git a/apps/cloud_federation_api/lib/OCMInvitation.php b/apps/cloud_federation_api/lib/OCMInvitation.php new file mode 100644 index 00000000000..ac3dfe3ca53 --- /dev/null +++ b/apps/cloud_federation_api/lib/OCMInvitation.php @@ -0,0 +1,18 @@ + array($baseDir . '/lib/private'), 'OCP\\' => array($baseDir . '/lib/public'), 'NCU\\' => array($baseDir . '/lib/unstable'), - 'Bamarni\\Composer\\Bin\\' => array($vendorDir . '/bamarni/composer-bin-plugin/src'), '' => array($baseDir . '/lib/private/legacy'), ); diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 956fa820ac5..7a38da17c82 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -21,10 +21,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 array ( 'NCU\\' => 4, ), - 'B' => - array ( - 'Bamarni\\Composer\\Bin\\' => 21, - ), ); public static $prefixDirsPsr4 = array ( @@ -44,10 +40,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 array ( 0 => __DIR__ . '/../../..' . '/lib/unstable', ), - 'Bamarni\\Composer\\Bin\\' => - array ( - 0 => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src', - ), ); public static $fallbackDirsPsr4 = array (