diff --git a/apps/cloud_federation_api/lib/Capabilities.php b/apps/cloud_federation_api/lib/Capabilities.php index 0348f6e7c110c..166babb4ba87b 100644 --- a/apps/cloud_federation_api/lib/Capabilities.php +++ b/apps/cloud_federation_api/lib/Capabilities.php @@ -8,26 +8,14 @@ */ namespace OCA\CloudFederationAPI; -use NCU\Security\Signature\Exceptions\IdentityNotFoundException; -use NCU\Security\Signature\Exceptions\SignatoryException; -use OC\OCM\OCMSignatoryManager; +use OC\OCM\OCMDiscoveryService; use OCP\Capabilities\ICapability; use OCP\Capabilities\IInitialStateExcludedCapability; -use OCP\IAppConfig; -use OCP\IURLGenerator; use OCP\OCM\Exceptions\OCMArgumentException; -use OCP\OCM\IOCMProvider; -use Psr\Log\LoggerInterface; class Capabilities implements ICapability, IInitialStateExcludedCapability { - public const API_VERSION = '1.1'; // informative, real version. - public function __construct( - private IURLGenerator $urlGenerator, - private IAppConfig $appConfig, - private IOCMProvider $provider, - private readonly OCMSignatoryManager $ocmSignatoryManager, - private readonly LoggerInterface $logger, + private readonly OCMDiscoveryService $ocmDiscoveryService, ) { } @@ -54,38 +42,7 @@ public function __construct( * @throws OCMArgumentException */ public function getCapabilities() { - $url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare'); - $pos = strrpos($url, '/'); - if ($pos === false) { - throw new OCMArgumentException('generated route should contain a slash character'); - } - - $this->provider->setEnabled(true); - $this->provider->setApiVersion(self::API_VERSION); - $this->provider->setEndPoint(substr($url, 0, $pos)); - - $resource = $this->provider->createNewResourceType(); - $resource->setName('file') - ->setShareTypes(['user', 'group']) - ->setProtocols(['webdav' => '/public.php/webdav/']); - - $this->provider->addResourceType($resource); - - // Adding a public key to the ocm discovery - try { - if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) { - /** - * @experimental 31.0.0 - * @psalm-suppress UndefinedInterfaceMethod - */ - $this->provider->setSignatory($this->ocmSignatoryManager->getLocalSignatory()); - } else { - $this->logger->debug('ocm public key feature disabled'); - } - } catch (SignatoryException|IdentityNotFoundException $e) { - $this->logger->warning('cannot generate local signatory', ['exception' => $e]); - } - - return ['ocm' => $this->provider->jsonSerialize()]; + $provider = $this->ocmDiscoveryService->getLocalOCMProvider(false); + return ['ocm' => $provider->jsonSerialize()]; } } diff --git a/core/Controller/OCMController.php b/core/Controller/OCMController.php index 40d53cf7a97d2..9ad45ea9d8f35 100644 --- a/core/Controller/OCMController.php +++ b/core/Controller/OCMController.php @@ -10,6 +10,7 @@ namespace OC\Core\Controller; use Exception; +use OC\OCM\OCMDiscoveryService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\FrontpageRoute; @@ -17,11 +18,8 @@ use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\DataResponse; -use OCP\Capabilities\ICapability; use OCP\IAppConfig; use OCP\IRequest; -use OCP\Server; -use Psr\Container\ContainerExceptionInterface; use Psr\Log\LoggerInterface; /** @@ -33,6 +31,7 @@ class OCMController extends Controller { public function __construct( IRequest $request, private readonly IAppConfig $appConfig, + private readonly OCMDiscoveryService $ocmDiscoveryService, private LoggerInterface $logger, ) { parent::__construct('core', $request); @@ -55,29 +54,16 @@ public function __construct( #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)] public function discovery(): DataResponse { try { - $cap = Server::get( - $this->appConfig->getValueString( - 'core', 'ocm_providers', - \OCA\CloudFederationAPI\Capabilities::class, - lazy: true - ) - ); - - if (!($cap instanceof ICapability)) { - throw new Exception('loaded class does not implements OCP\Capabilities\ICapability'); - } - return new DataResponse( - $cap->getCapabilities()['ocm'] ?? ['enabled' => false], + $this->ocmDiscoveryService->getLocalOCMProvider()->jsonSerialize(), Http::STATUS_OK, [ 'X-NEXTCLOUD-OCM-PROVIDERS' => true, 'Content-Type' => 'application/json' ] ); - } catch (ContainerExceptionInterface|Exception $e) { + } catch (Exception $e) { $this->logger->error('issue during OCM discovery request', ['exception' => $e]); - return new DataResponse( ['message' => '/ocm-provider/ not supported'], Http::STATUS_INTERNAL_SERVER_ERROR diff --git a/lib/private/OCM/Model/OCMProvider.php b/lib/private/OCM/Model/OCMProvider.php index f4b0ac584de4d..b1b52582e7c34 100644 --- a/lib/private/OCM/Model/OCMProvider.php +++ b/lib/private/OCM/Model/OCMProvider.php @@ -10,8 +10,6 @@ namespace OC\OCM\Model; use NCU\Security\Signature\Model\Signatory; -use OCP\EventDispatcher\IEventDispatcher; -use OCP\OCM\Events\ResourceTypeRegisterEvent; use OCP\OCM\Exceptions\OCMArgumentException; use OCP\OCM\Exceptions\OCMProviderException; use OCP\OCM\IOCMProvider; @@ -27,11 +25,8 @@ class OCMProvider implements IOCMProvider { /** @var IOCMResource[] */ private array $resourceTypes = []; private ?Signatory $signatory = null; - private bool $emittedEvent = false; - public function __construct( - protected IEventDispatcher $dispatcher, - ) { + public function __construct() { } /** @@ -122,12 +117,6 @@ public function setResourceTypes(array $resourceTypes): static { * @return IOCMResource[] */ public function getResourceTypes(): array { - if (!$this->emittedEvent) { - $this->emittedEvent = true; - $event = new ResourceTypeRegisterEvent($this); - $this->dispatcher->dispatchTyped($event); - } - return $this->resourceTypes; } diff --git a/lib/private/OCM/OCMDiscoveryService.php b/lib/private/OCM/OCMDiscoveryService.php index af6124163720e..03442b9b2cc65 100644 --- a/lib/private/OCM/OCMDiscoveryService.php +++ b/lib/private/OCM/OCMDiscoveryService.php @@ -11,11 +11,18 @@ use GuzzleHttp\Exception\ConnectException; use JsonException; +use NCU\Security\Signature\Exceptions\IdentityNotFoundException; +use NCU\Security\Signature\Exceptions\SignatoryException; +use OC\OCM\Model\OCMProvider; use OCP\AppFramework\Http; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Http\Client\IClientService; +use OCP\IAppConfig; use OCP\ICache; use OCP\ICacheFactory; use OCP\IConfig; +use OCP\IURLGenerator; +use OCP\OCM\Events\ResourceTypeRegisterEvent; use OCP\OCM\Exceptions\OCMProviderException; use OCP\OCM\IOCMDiscoveryService; use OCP\OCM\IOCMProvider; @@ -26,18 +33,25 @@ */ class OCMDiscoveryService implements IOCMDiscoveryService { private ICache $cache; + public const API_VERSION = '1.1.0'; + + private ?IOCMProvider $localProvider = null; + /** @var array */ + private array $remoteProviders = []; public function __construct( ICacheFactory $cacheFactory, private IClientService $clientService, - private IConfig $config, - private IOCMProvider $provider, + private IEventDispatcher $eventDispatcher, + protected IConfig $config, + private IAppConfig $appConfig, + private IURLGenerator $urlGenerator, + private OCMSignatoryManager $ocmSignatoryManager, private LoggerInterface $logger, ) { $this->cache = $cacheFactory->createDistributed('ocm-discovery'); } - /** * @param string $remote * @param bool $skipCache @@ -56,6 +70,12 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider } } + if (array_key_exists($remote, $this->remoteProviders)) { + return $this->remoteProviders[$remote]; + } + + $provider = new OCMProvider(); + if (!$skipCache) { try { $cached = $this->cache->get($remote); @@ -63,10 +83,11 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider throw new OCMProviderException('Previous discovery failed.'); } - $this->provider->import(json_decode($cached ?? '', true, 8, JSON_THROW_ON_ERROR) ?? []); - return $this->provider; + $provider->import(json_decode($cached ?? '', true, 8, JSON_THROW_ON_ERROR) ?? []); + $this->remoteProviders[$remote] = $provider; + return $provider; } catch (JsonException|OCMProviderException $e) { - // we ignore cache on issues + $this->logger->warning('cache issue on ocm discovery', ['exception' => $e]); } } @@ -81,15 +102,20 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider } $response = $client->get($remote . '/ocm-provider/', $options); + $body = null; if ($response->getStatusCode() === Http::STATUS_OK) { $body = $response->getBody(); // update provider with data returned by the request - $this->provider->import(json_decode($body, true, 8, JSON_THROW_ON_ERROR) ?? []); + $provider->import(json_decode($body, true, 8, JSON_THROW_ON_ERROR) ?? []); $this->cache->set($remote, $body, 60 * 60 * 24); + $this->remoteProviders[$remote] = $provider; + return $provider; } - } catch (JsonException|OCMProviderException $e) { + + throw new OCMProviderException('invalid remote ocm endpoint'); + } catch (JsonException|OCMProviderException) { $this->cache->set($remote, false, 5 * 60); - throw new OCMProviderException('data returned by remote seems invalid - ' . ($body ?? '')); + throw new OCMProviderException('data returned by remote seems invalid - status:' . $response->getStatusCode() . ' - ' . ($body ?? '')); } catch (\Exception $e) { $this->cache->set($remote, false, 5 * 60); $this->logger->warning('error while discovering ocm provider', [ @@ -98,7 +124,57 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider ]); throw new OCMProviderException('error while requesting remote ocm provider'); } + } + + /** + * @return IOCMProvider + */ + public function getLocalOCMProvider(bool $fullDetails = true): IOCMProvider { + if ($this->localProvider !== null) { + return $this->localProvider; + } - return $this->provider; + $provider = new OCMProvider(); + + $url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare'); + $pos = strrpos($url, '/'); + if ($pos === false) { + $this->logger->debug('generated route should contain a slash character'); + return $provider; + } + + $provider->setEnabled(true); + $provider->setApiVersion(self::API_VERSION); + $provider->setEndPoint(substr($url, 0, $pos)); + + $resource = $provider->createNewResourceType(); + $resource->setName('file') + ->setShareTypes(['user', 'group']) + ->setProtocols(['webdav' => '/public.php/webdav/']); + $provider->addResourceType($resource); + + if ($fullDetails) { + // Adding a public key to the ocm discovery + try { + if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) { + /** + * @experimental 31.0.0 + * @psalm-suppress UndefinedInterfaceMethod + */ + $provider->setSignatory($this->ocmSignatoryManager->getLocalSignatory()); + } else { + $this->logger->debug('ocm public key feature disabled'); + } + } catch (SignatoryException|IdentityNotFoundException $e) { + $this->logger->warning('cannot generate local signatory', ['exception' => $e]); + } + } + + $event = new ResourceTypeRegisterEvent($provider); + $this->eventDispatcher->dispatchTyped($event); + + $this->localProvider = $provider; + return $provider; } + } diff --git a/lib/private/OCM/OCMSignatoryManager.php b/lib/private/OCM/OCMSignatoryManager.php index 3b2cc595507fe..0acdd17863503 100644 --- a/lib/private/OCM/OCMSignatoryManager.php +++ b/lib/private/OCM/OCMSignatoryManager.php @@ -20,6 +20,9 @@ use OCP\IAppConfig; use OCP\IURLGenerator; use OCP\OCM\Exceptions\OCMProviderException; +use OCP\Server; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; use Psr\Log\LoggerInterface; /** @@ -41,7 +44,6 @@ public function __construct( private readonly ISignatureManager $signatureManager, private readonly IURLGenerator $urlGenerator, private readonly Manager $identityProofManager, - private readonly OCMDiscoveryService $ocmDiscoveryService, private readonly LoggerInterface $logger, ) { } @@ -144,7 +146,7 @@ private function generateKeyId(): string { */ public function getRemoteSignatory(string $remote): ?Signatory { try { - $ocmProvider = $this->ocmDiscoveryService->discover($remote, true); + $ocmProvider = Server::get(OCMDiscoveryService::class)->discover($remote, true); /** * @experimental 31.0.0 * @psalm-suppress UndefinedInterfaceMethod @@ -152,7 +154,7 @@ public function getRemoteSignatory(string $remote): ?Signatory { $signatory = $ocmProvider->getSignatory(); $signatory?->setSignatoryType(SignatoryType::TRUSTED); return $signatory; - } catch (OCMProviderException $e) { + } catch (NotFoundExceptionInterface|ContainerExceptionInterface|OCMProviderException $e) { $this->logger->warning('fail to get remote signatory', ['exception' => $e, 'remote' => $remote]); return null; } diff --git a/lib/private/Server.php b/lib/private/Server.php index be9d7595e4221..d3dd2791e04a9 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -1275,7 +1275,7 @@ public function __construct($webRoot, \OC\Config $config) { $this->registerAlias(IPhoneNumberUtil::class, PhoneNumberUtil::class); - $this->registerAlias(IOCMProvider::class, OCMProvider::class); + $this->registerDeprecatedAlias(IOCMProvider::class, OCMProvider::class); $this->registerAlias(ISetupCheckManager::class, SetupCheckManager::class);