Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
51 changes: 4 additions & 47 deletions apps/cloud_federation_api/lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {
}

Expand All @@ -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()];
}
}
22 changes: 4 additions & 18 deletions core/Controller/OCMController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,16 @@
namespace OC\Core\Controller;

use Exception;
use OC\OCM\OCMDiscoveryService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
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;

/**
Expand All @@ -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);
Expand All @@ -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
Expand Down
13 changes: 1 addition & 12 deletions lib/private/OCM/Model/OCMProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
}

/**
Expand Down Expand Up @@ -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;
}

Expand Down
96 changes: 86 additions & 10 deletions lib/private/OCM/OCMDiscoveryService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,18 +33,25 @@
*/
class OCMDiscoveryService implements IOCMDiscoveryService {
private ICache $cache;
public const API_VERSION = '1.1.0';

private ?IOCMProvider $localProvider = null;
/** @var array<string, IOCMProvider> */
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
Expand All @@ -56,17 +70,24 @@ 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);
if ($cached === false) {
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]);
}
}

Expand All @@ -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', [
Expand All @@ -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;
}

}
8 changes: 5 additions & 3 deletions lib/private/OCM/OCMSignatoryManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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,
) {
}
Expand Down Expand Up @@ -144,15 +146,15 @@ 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
*/
$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;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading